新聞中心
最近在做新業(yè)務(wù)需求的同時,我們在 Android 上遇到了一些之前沒有碰到過的問題,截屏分享、 WebView 生成長圖以及長圖在各個分享渠道分享時圖片模糊甚至分享失敗等問題,在這過程中踩了很多坑,到目前為止絕大部分的問題都還算是有了比較滿意的解決方案。以下就從三個方面來總結(jié)一下過程中遇到的挑戰(zhàn)和***的解決方案。

一、概述
最近在做新業(yè)務(wù)需求的同時,我們在 Android 上遇到了一些之前沒有碰到過的問題,截屏分享、 WebView 生成長圖以及長圖在各個分享渠道分享時圖片模糊甚至分享失敗等問題,在這過程中踩了很多坑,到目前為止絕大部分的問題都還算是有了比較滿意的解決方案。以下就從三個方面來總結(jié)一下過程中遇到的挑戰(zhàn)和***的解決方案。
二、截圖分享
在 Android 原生系統(tǒng)中是沒有提供截圖的廣播或者監(jiān)聽事件的,也就是說代碼層面無法獲知用戶的截屏操作,這樣就無法滿足用戶截屏后跳出分享提示的需求。既然無法從根本上解決截屏監(jiān)聽的問題,那么就要考慮通過其他方式間接實現(xiàn),目前比較成熟穩(wěn)定的方案是監(jiān)聽系統(tǒng)媒體數(shù)據(jù)庫資源的變化,具體方案原理如下:
Android 系統(tǒng)有一個媒體數(shù)據(jù)庫,每拍一張照片,或使用系統(tǒng)截屏截取一張圖片,都會把這張圖片的詳細(xì)信息加入到這個媒體數(shù)據(jù)庫,并發(fā)出內(nèi)容改變通知,我們可以利用內(nèi)容觀察者(ContentObserver)監(jiān)聽媒體數(shù)據(jù)庫的變化,當(dāng)數(shù)據(jù)庫有變化時,獲取***插入的一條圖片數(shù)據(jù),如果該圖片符合特定的規(guī)則,則認(rèn)為被截屏了。
考慮到手機(jī)存儲包括內(nèi)部存儲器和外部存儲器,為了增強(qiáng)兼容性,***同時監(jiān)聽兩種儲存空間的變化,以下是需要 ContentObserver 監(jiān)聽的資源 URI :
- MediaStore.Images.Media.INTERNAL_CONTENT_URI
- MediaStore.Images.Media.EXTERNAL_CONTENT_URI
讀取外部存儲器資源,需要添加權(quán)限:
- android.permission.READ_EXTERNAL_STORAGE
注:在 Android 6.0 及以上版本需要動態(tài)申請權(quán)限
1. 截屏判斷規(guī)則
當(dāng) ContentObserver 監(jiān)聽到媒體數(shù)據(jù)庫的數(shù)據(jù)改變, 在有數(shù)據(jù)改變時獲取***插入數(shù)據(jù)庫的一條圖片數(shù)據(jù), 如果符合以下規(guī)則, 則認(rèn)為截屏了:
- 時間判斷:通常截屏生成后會立馬存入系統(tǒng)多媒體數(shù)據(jù)庫,也就是說監(jiān)聽到數(shù)據(jù)庫變化的時間與截圖生成的時間不會相差太多,這里推薦以10秒作為閾值,當(dāng)然這個也是經(jīng)驗值。
- 尺寸判斷:截屏顧名思義取得是當(dāng)前手機(jī)屏幕尺寸大小的圖片,所以圖片寬高大于屏幕寬高的肯定都不是截圖產(chǎn)生的。
- 路徑判斷:由于各手機(jī)廠家存放截圖的文件路徑都不太一樣,國內(nèi)情況可能會更嚴(yán)重,但是通常圖片保存路徑都會包含一些常見的關(guān)鍵詞,比如 “screenshot”、 “screencapture” 、 “screencap” 、 “截圖”、 “截屏”等,每次都檢查圖片路徑信息是否包含這些關(guān)鍵詞。
關(guān)于第3點需要補充說明一下,由于要判斷圖片文件路徑是否包含關(guān)鍵字,所以目前僅支持中英文環(huán)境,如果需要支持其他語言,需要手動添加一些該語言的關(guān)鍵詞,否則有可能獲取不到圖片。
以上3點基本上可以保證截圖的正常監(jiān)聽,當(dāng)然在實際測試過程中,還會發(fā)現(xiàn)有些機(jī)型存在多報的情況,所以還需要做一些去重等工作,關(guān)于去重下面還會再提及。
2. 關(guān)鍵代碼
原理都了解清楚了,那么接下來就是如何實現(xiàn)的問題了。這里最關(guān)鍵是媒體內(nèi)容觀察者的設(shè)置,從數(shù)據(jù)庫中取出***條數(shù)據(jù)并解析圖片信息,然后再檢驗圖片信息是否符合以上3條規(guī)則。
為了說清楚如何監(jiān)聽媒體數(shù)據(jù)庫改變,先要稍微講一下 ContentObserver 的原理。 ContentObserver ——內(nèi)容觀察者,目的是觀察(捕捉)特定 Uri 引起的數(shù)據(jù)庫的變化,繼而做一些相應(yīng)的處理,它類似于數(shù)據(jù)庫技術(shù)中的觸發(fā)器(Trigger),當(dāng) ContentObserver 所觀察的 Uri 發(fā)生變化時,便會觸發(fā)它。當(dāng)然想要觀察就必須先要注冊, Android 系統(tǒng)提供了 ContentResolver#registerContentObserver 方法用來注冊觀察器。此部分不熟悉的同學(xué)可以溫習(xí)一下 Android 的 ContentProvider 相關(guān)知識。
接下來直接用代碼說明整個注冊和觸發(fā)流程,代碼如下:
- private void initMediaContentObserver() {
- // 運行在 UI 線程的 Handler, 用于運行監(jiān)聽器回調(diào)
- private final Handler mUiHandler = new Handler(Looper.getMainLooper());
- // 創(chuàng)建內(nèi)容觀察者,包括內(nèi)部存儲和外部存儲
- mInternalObserver = new MediaContentObserver(MediaStore.Images.Media.INTERNAL_CONTENT_URI, mUiHandler);
- mExternalObserver = new MediaContentObserver(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, mUiHandler);
- // 注冊內(nèi)容觀察者
- mContext.getContentResolver().registerContentObserver(
- MediaStore.Images.Media.INTERNAL_CONTENT_URI, false, mInternalObserver);
- mContext.getContentResolver().registerContentObserver(
- MediaStore.Images.Media.EXTERNAL_CONTENT_URI, false, mExternalObserver);
- }
- /**
- * 自定義媒體內(nèi)容觀察者類(觀察媒體數(shù)據(jù)庫的改變)
- */
- private class MediaContentObserver extends ContentObserver {
- private Uri mediaContentUri; // 需要觀察的Uri
- public MediaContentObserver(Uri contentUri, Handler handler) {
- super(handler);
- mediaContentUri = contentUri;
- }
- @Override
- public void onChange(boolean selfChange) {
- super.onChange(selfChange);
- // 處理媒體數(shù)據(jù)庫反饋的數(shù)據(jù)變化
- handleMediaContentChange(mediaContentUri);
- }
- }
有注冊就需要在 Activity 銷毀時取消注冊,所以還需要封裝一個解除注冊的方法供外部調(diào)用, Android 系統(tǒng)提供 ContentResolver#unregisterContentObserver 方法來取消注冊,代碼比較簡單,這里就不再展示了。
監(jiān)聽器設(shè)置和注冊完成后,一旦用戶操作了截屏動作,系統(tǒng)就會執(zhí)行 ContentObserver#onChange 回調(diào)方法,在這個方法中我們可以根據(jù) Uri 獲取并解析數(shù)據(jù)。這里展示一下具體的數(shù)據(jù)解析過程,上述提到的規(guī)則判斷比較簡單,就不再展示了。
- private void handleMediaContentChange(Uri contentUri) {
- Cursor cursor = null;
- try {
- // 數(shù)據(jù)改變時查詢數(shù)據(jù)庫中***加入的一條數(shù)據(jù)
- cursor = mContext.getContentResolver().query(contentUri,
- Build.VERSION.SDK_INT < 16 ? MEDIA_PROJECTIONS : MEDIA_PROJECTIONS_API_16,
- null, null, MediaStore.Images.ImageColumns.DATE_ADDED + " desc limit 1");
- if (cursor == null) return;
- if (!cursor.moveToFirst()) return;
- // cursor.getColumnIndex獲取數(shù)據(jù)庫列索引
- int dataIndex = cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATA);
- String data = cursor.getString(dataIndex); // 圖片存儲地址
- int dateTakenIndex = cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATE_TAKEN);
- long dateTaken = cursor.getLong(dateTakenIndex); // 圖片生成時間
- int width = 0;
- int height = 0;
- if (Build.VERSION.SDK_INT >= 16) {
- int widthIndex = cursor.getColumnIndex(MediaStore.Images.ImageColumns.WIDTH);
- int heightIndex = cursor.getColumnIndex(MediaStore.Images.ImageColumns.HEIGHT);
- width = cursor.getInt(widthIndex); // 獲取圖片高度
- height = cursor.getInt(heightIndex); // 獲取圖片寬度
- } else {
- Point size = getImageSize(data); // 根據(jù)路徑獲取圖片寬和高
- width = size.x;
- height = size.y;
- }
- // 處理獲取到的***行數(shù)據(jù),分別判斷路徑是否包含關(guān)鍵詞、時間差以及圖片寬高和屏幕寬高的大小關(guān)系
- handleMediaRowData(data, dateTaken, width, height);
- } catch (Exception e) {
- e.printStackTrace();
- } finally {
- if (cursor != null && !cursor.isClosed())
當(dāng)前題目:Android截屏與WebView長圖分享經(jīng)驗總結(jié)
文章轉(zhuǎn)載:http://fisionsoft.com.cn/article/coiedpj.html


咨詢
建站咨詢
