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

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


咨詢
建站咨詢
