1. 项目背景与需求分析
最近在开发一个Android应用时,遇到了需要给图片添加水印的需求。作为一个常见的功能,我最初的想法是直接引入现成的水印库来快速实现。但在实际开发过程中,却经历了一系列意想不到的问题,最终导致我放弃了使用第三方水印库的方案。
水印功能在移动应用中非常普遍,主要用于:
- 保护图片版权
- 标记图片来源
- 防止内容被滥用
- 添加拍摄时间、地点等元信息
在Android平台上实现水印主要有两种方式:
- 使用Canvas直接在Bitmap上绘制
- 通过第三方库封装好的API实现
我最初选择了第二种方案,主要是考虑到:
- 节省开发时间
- 避免重复造轮子
- 利用库提供的丰富功能(如多种水印样式、动态调整等)
2. 水印库选型与评估
在Java生态中,有几个比较知名的Android水印库:
2.1 候选库列表
-
WatermarkView
- 优点:轻量级,API简单
- 缺点:功能较为基础
-
Android-Watermark
- 优点:支持多种水印样式
- 缺点:文档不完善
-
WatermarkGenerator
- 优点:性能优化好
- 缺点:依赖较多
2.2 选型标准
我主要考虑了以下几个因素:
- 兼容性:需要支持Android 5.0及以上版本
- 性能:处理大图时不卡顿
- 灵活性:支持自定义水印位置、透明度、旋转等
- 维护状态:GitHub上最近有更新
经过对比,我初步选择了Android-Watermark,因为它提供了最接近我需求的功能集。
3. 集成过程与遇到的问题
3.1 基础集成步骤
按照文档,集成过程应该很简单:
- 在build.gradle中添加依赖:
groovy复制implementation 'com.github.pengqin:android-watermark:1.0.3'
- 在代码中使用:
java复制WatermarkBuilder.create(this, bitmap)
.setText("Confidential")
.setTextSize(30)
.setTextColor(Color.RED)
.setRotation(-30)
.build();
3.2 实际遇到的问题
但在实际集成时,遇到了几个严重问题:
-
API不兼容:库的最新版本使用了Java 8特性,而我的项目还在使用Java 7
-
性能问题:处理高分辨率图片时(如4000x3000),水印添加过程耗时超过3秒
-
内存泄漏:多次调用后出现内存溢出,特别是在低端设备上
-
样式限制:无法实现我想要的多行水印+图标组合效果
4. 问题排查与解决方案尝试
4.1 兼容性问题解决尝试
对于Java 8兼容性问题,我尝试了以下方案:
- 在gradle.properties中添加:
code复制android.enableD8.desugaring=true
- 在模块级build.gradle中配置:
groovy复制compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
虽然解决了编译问题,但运行时仍然出现了一些兼容性异常。
4.2 性能优化尝试
针对性能问题,我尝试了:
- 使用子线程处理:
java复制new AsyncTask<Void, Void, Bitmap>() {
@Override
protected Bitmap doInBackground(Void... voids) {
return WatermarkBuilder.create(context, originalBitmap).build();
}
@Override
protected void onPostExecute(Bitmap result) {
imageView.setImageBitmap(result);
}
}.execute();
- 降低图片分辨率后再添加水印:
java复制Bitmap scaledBitmap = Bitmap.createScaledBitmap(
originalBitmap,
originalBitmap.getWidth()/2,
originalBitmap.getHeight()/2,
true
);
这些方案虽然有所改善,但用户体验仍然不理想。
5. 放弃第三方库的决定
经过一周的尝试和调试,我最终决定放弃使用第三方水印库,主要原因包括:
- 过度设计:我的需求其实很简单,而库提供了太多我用不到的功能
- 不可控因素:遇到问题时难以快速解决,依赖库的维护者响应
- 性能代价:引入额外依赖增加了APK大小和运行时内存占用
- 学习成本:需要花时间理解库的设计和API,不如自己实现直接
6. 原生实现方案
最终,我选择了使用Android原生Canvas API自己实现水印功能:
6.1 基础实现代码
java复制public static Bitmap addWatermark(Bitmap src, String watermark) {
Bitmap result = src.copy(src.getConfig(), true);
Canvas canvas = new Canvas(result);
Paint paint = new Paint();
paint.setColor(Color.RED);
paint.setTextSize(40);
paint.setAntiAlias(true);
paint.setAlpha(100);
canvas.drawText(watermark, 20, src.getHeight() - 20, paint);
return result;
}
6.2 优化后的版本
java复制public static Bitmap addCustomWatermark(Bitmap src, WatermarkConfig config) {
int width = src.getWidth();
int height = src.getHeight();
Bitmap result = Bitmap.createBitmap(width, height, src.getConfig());
Canvas canvas = new Canvas(result);
canvas.drawBitmap(src, 0, 0, null);
Paint paint = new Paint();
paint.setColor(config.getColor());
paint.setTextSize(config.getTextSize());
paint.setAntiAlias(true);
paint.setAlpha(config.getAlpha());
if(config.getTypeface() != null) {
paint.setTypeface(config.getTypeface());
}
// 计算水印位置
float x = width * config.getxRatio();
float y = height * config.getyRatio();
// 如果需要旋转
if(config.getRotation() != 0) {
canvas.save();
canvas.rotate(config.getRotation(), x, y);
canvas.drawText(config.getText(), x, y, paint);
canvas.restore();
} else {
canvas.drawText(config.getText(), x, y, paint);
}
return result;
}
6.3 配置类
java复制public class WatermarkConfig {
private String text;
private int color;
private float textSize;
private int alpha;
private Typeface typeface;
private float xRatio;
private float yRatio;
private float rotation;
// 构造方法和getter/setter省略
}
7. 性能对比与优化建议
7.1 性能测试数据
| 方案 | 处理时间(2000x1500) | 内存占用 | APK大小增加 |
|---|---|---|---|
| 第三方库 | 1200ms | 45MB | 150KB |
| 原生实现 | 350ms | 25MB | 0KB |
7.2 优化技巧
- 复用Bitmap对象:避免频繁创建和回收Bitmap
- 使用inSampleSize:先缩小图片再处理
- 异步处理:使用HandlerThread或RxJava避免阻塞UI线程
- 缓存结果:对相同图片+相同水印配置缓存处理结果
8. 扩展功能实现
虽然放弃了第三方库,但通过原生实现也能完成复杂需求:
8.1 多行水印
java复制String[] lines = watermark.split("\n");
float textHeight = paint.descent() - paint.ascent();
float startY = y;
for(String line : lines) {
canvas.drawText(line, x, startY, paint);
startY += textHeight + 10; // 10是行间距
}
8.2 图片+文字水印
java复制// 绘制文字
canvas.drawText(config.getText(), x, y, textPaint);
// 绘制图标
if(config.getIcon() != null) {
canvas.drawBitmap(
config.getIcon(),
x - config.getIcon().getWidth() - 10, // 文字左侧10像素
y - config.getIcon().getHeight()/2,
iconPaint
);
}
8.3 平铺水印
java复制float textWidth = paint.measureText(watermark);
float textHeight = paint.descent() - paint.ascent();
for(int i = 0; i < width; i += textWidth + 50) {
for(int j = 0; j < height; j += textHeight + 50) {
canvas.drawText(watermark, i, j, paint);
}
}
9. 实际项目中的经验总结
- 评估需求复杂度:简单需求可能不需要引入完整库
- 测试真实场景:在目标设备上测试性能,而不仅是开发机
- 考虑维护成本:第三方库可能增加长期维护难度
- 渐进式实现:先实现核心功能,再逐步添加特性
在Android开发中,有时候最简单的解决方案反而是最可靠的。虽然第三方库能快速实现功能,但也带来了额外的复杂性和不确定性。通过这次经历,我更加理解了"合适的就是最好的"这个道理。
