近期在风控部门的要求下,我们在APP的一些关键页面上添加了水印,技术方案也比较简单,上线一切正常,不过大概一周之后,陆陆续续开始收到有花屏的反馈,具体截图如下类似:
最开始考虑的可能是手机有自定义字体的缘故,后面偶然得知是因为安卓系统中辅助功能里可以设置高对比字体导致的,高对比文字会在文字的周围添加描边,增强视觉效果。于是推荐用户关掉了这个设置,随着反馈开始增多,开始考虑如果彻底的解决这一问题了。
最开始想到的方案是能不能拿到是否开启了这个高对比的设置,然后提示给用户。查了一下还真的可以通过 AccessibilityManager 的一个属性拿到,代码如下:
AccessibilityManager am = (AccessibilityManager) this.getSystemService(Context.ACCESSIBILITY_SERVICE);
boolean isHighTextContrastEnabled = am.isHighTextContrastEnabled();
复制代码
不过拿到是否开关了这个选项之后提示用户关闭的体验总归还是不太好,部分用户确实有开启这个高对比的需求,那只能再想办法能在开启了高对比度文字的情况下如何去掉水印的这个高对比的描边了。
Google搜索HighTextContrast这个关键字的时候意外发现Canvas有一个setHighContrastText方法,简直看到了曙光,大概也了解了这个是否高亮是跟每个Canvas绘制的时候绑定的。那就在绘制这个水印的地方调用这个API不就万事ok了吗?
不幸的是Android很任性的把这个方法定义成了一个hide方法:
/** @hide */
public void setHighContrastText(boolean highContrastText) {
nSetHighContrastText(mNativeCanvasWrapper, highContrastText);
}
复制代码
不过没关系,根据之前收藏已久的偏方,大概是可以通过ClassLoader的双亲委派机制完美的绕过hide方法的,具体做法是:
- 在src Java文件夹下创建一个同包名的类,如:android.graphics.Canvas
- 定义Canvas中所有用到的方法和属性,方法只需要定义,可以给空实现。
这样AS在编译的时候优先是引用本地代码,可以通过JAVA访问权限的校验,运行时由于ClassLoader的双亲委派机制,加载到JVM中的只会是Android的Canvas类,这样就不会影响到Canvas类的行为。
根据这个思路实现了之后,完美解决问题,一行代码解决困扰了几天的问题,感觉倍爽。在准备提交这个commit回家的时候又开始犹豫了,真的要这么黑科技吗,对于这个另类的Canvas,以后会不会增加维护成本,还有这个hide掉的API调用会不会命中某个机型隐藏的坑?毕竟是Canvas类啊!
还是犯怂了,再想想,有没有更优雅的方式。深入的了解了一下高对比度文字的实现过程,发现它只作用于文字,而且和背景有很大的关系。那自然就会想到如果把每个水印文字先绘制到一张Bitmap上呢,然后再把Bitmap平铺到整个页面,是不是就可以通过绘制前给定一个不会产生高对比的背景,绘制完成之后再对Bitmap做转换呢?
按照这个思路,首先把水印绘制到透明的Bitmap上,然后再直接绘制到Drawable上,实验了之后发现完美解决问题,而且不需要再对背景做特殊的处理。考虑到Bitmap的复用性,对Bitmap做了一定的缓存,代码如下:
private static HashMap waterBitmaps = new HashMap<>();
private static Bitmap getWaterBitmap(String label,int color, int fontSize) {
String key = generateKey(label,color, fontSize);
if (!waterBitmaps.containsKey(key)) {
Paint paint = new Paint();
paint.setColor(color);
paint.setAntiAlias(true);
paint.setTextSize(fontSize);
Rect rect = new Rect();
paint.getTextBounds(label, 0, label.length(), rect);
int width = rect.width();
int height = rect.height();
Bitmap waterBitmap = Bitmap.createBitmap(width + 10, height + 10, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(waterBitmap);
canvas.drawText(label, 10, height, paint);
waterBitmaps.put(key, waterBitmap);
return waterBitmap;
} else {
return waterBitmaps.get(key);
}
}
private static String generateKey(String label, int color, int fontSize) {
return label + "_" + color+ "_" + fontSize;
}
复制代码
然后把这个Bitmap平铺到需要的页面,经过多个页面的测试,完美解决问题。通过绘制到Bitmap的方式,不仅解决了花屏的问题,也通过直接drawBitmap提高了绘制的效率,一举两得,perfect!