記一次安卓手機水印顯示問題的排查歷程

鵾鵬發表於2019-02-26

近期在風控部門的要求下,我們在APP的一些關鍵頁面上新增了水印,技術方案也比較簡單,上線一切正常,不過大概一週之後,陸陸續續開始收到有花屏的反饋,具體截圖如下類似:

頁面截圖.png

最開始考慮的可能是手機有自定義字型的緣故,後面偶然得知是因為安卓系統中輔助功能裡可以設定高對比字型導致的,高對比文字會在文字的周圍新增描邊,增強視覺效果。於是推薦使用者關掉了這個設定,隨著反饋開始增多,開始考慮如果徹底的解決這一問題了。

最開始想到的方案是能不能拿到是否開啟了這個高對比的設定,然後提示給使用者。查了一下還真的可以通過 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方法的,具體做法是:

  1. 在src Java資料夾下建立一個同包名的類,如:android.graphics.Canvas
  2. 定義Canvas中所有用到的方法和屬性,方法只需要定義,可以給空實現。

這樣AS在編譯的時候優先是引用原生程式碼,可以通過JAVA訪問許可權的校驗,執行時由於ClassLoader的雙親委派機制,載入到JVM中的只會是Android的Canvas類,這樣就不會影響到Canvas類的行為。

根據這個思路實現了之後,完美解決問題,一行程式碼解決困擾了幾天的問題,感覺倍爽。在準備提交這個commit回家的時候又開始猶豫了,真的要這麼黑科技嗎,對於這個另類的Canvas,以後會不會增加維護成本,還有這個hide掉的API呼叫會不會命中某個機型隱藏的坑?畢竟是Canvas類啊!

還是犯慫了,再想想,有沒有更優雅的方式。深入的瞭解了一下高對比度文字的實現過程,發現它只作用於文字,而且和背景有很大的關係。那自然就會想到如果把每個水印文字先繪製到一張Bitmap上呢,然後再把Bitmap平鋪到整個頁面,是不是就可以通過繪製前給定一個不會產生高對比的背景,繪製完成之後再對Bitmap做轉換呢?

按照這個思路,首先把水印繪製到透明的Bitmap上,然後再直接繪製到Drawable上,實驗了之後發現完美解決問題,而且不需要再對背景做特殊的處理。考慮到Bitmap的複用性,對Bitmap做了一定的快取,程式碼如下:

private static HashMap<String, Bitmap> 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!

相關文章