1.背景
在工作上遇到了一個關于在Android7.0下載更新安裝包無法安裝的兼容性問題,特記錄下來,作為典型兼容問題的積累和專業(yè)知識的積累,以及提醒自己在后續(xù)的測試工作中要遇到系統(tǒng)版本更新時,要做到系統(tǒng)性地分析版本特性,然后針對自身項目發(fā)起平臺/系統(tǒng)升級的補充測試,提高測試覆蓋率,保障產(chǎn)品質量。
2.問題描述
測試組里某個項目產(chǎn)品APP進行更新升級功能測試時,出現(xiàn)了一個問題,就是發(fā)現(xiàn)在自動更新功能的時候,下載好了apk的文件后不能自動跳到安裝界面,導致無法安裝相應的新版本,發(fā)現(xiàn)這個問題只會發(fā)生在Android 7.0版本的設備上,在較低版本的設備上則無這個問題。
3.問題引入原因分析
3.1 先來了解什么是APP更新升級功能
app在線更新是一個比較常見需求,新版本發(fā)布時,用戶進入我們的app,就會彈出更新提示框,第一時間更新新版本app。在線更新分為以下幾個步驟:
1, 通過接口獲取線上版本號,versionCode
2, 比較線上的versionCode 和本地的versionCode,彈出更新窗口
3, 下載APK文件(文件下載)
4,安裝APK
在線更新就上面幾個步驟,前2步比較簡單,重要的就是后2個步驟,而由于Android 各個版本對權限和隱私的收歸和保護,因此,可能會出現(xiàn)各種的適配問題
3.2 理解安裝APK的實現(xiàn)原理
上一節(jié)講到由于Android 各個版本對權限和隱私的收歸和保護,因此,下載和安裝apk時可能會出現(xiàn)各種的適配問題,由于此bug是安裝apk時出現(xiàn)的問題,所以我們就來重點分析一下安裝apk的實現(xiàn)原理。
安裝APK步驟
一般安裝apk之前是先下載apk,一般最簡單的方式是用 DownloadManager 來下載apk。
DownloadManager 是SDK 自帶的,大概流程如下:
(1)創(chuàng)建一個Request,進行簡單的配置(下載地址,和文件保存地址等)
(2)下載完成后,系統(tǒng)會發(fā)送一個下載完成的廣播,我們需要監(jiān)聽廣播。
(3)監(jiān)聽到下載完成的廣播后,根據(jù)id查找下載的apk文件
(4)在代碼中執(zhí)行apk安裝。
DownloadManager在下載完成之后,會發(fā)送一個下載完成的廣播DownloadManager.ACTION_DOWNLOAD_COMPLETE,我們只需要監(jiān)聽這個廣播,收到廣播后, 獲取apk文件安裝。
定義一個廣播DownloadReceiver:
class DownloadReceiver extends BroadcastReceiver {
@Override
public void onReceive(final Context context, final Intent intent) {
// 安裝APK
long completeDownLoadId = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1);
Logger.e(TAG, "收到廣播");
Uri uri;
Intent intentInstall = new Intent();
intentInstall.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intentInstall.setAction(Intent.ACTION_VIEW);
if (completeDownLoadId == mReqId) {
uri = mDownloadManager.getUriForDownloadedFile(completeDownLoadId);
}
intentInstall.setDataAndType(uri, "application/vnd.android.package-archive");
context.startActivity(intentInstall);
}
}
在下載之前注冊廣播
// 注冊廣播,監(jiān)聽APK是否下載完成
weakReference.get().registerReceiver(mDownloadReceiver, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
通過上面的幾個步驟,基本上就完成app在線更新功能,在Android 6.0以下可以正常運行。
3.3 Android7.0系統(tǒng) 文件訪問權限特性
每個Android版本的發(fā)布,對于安全性問題的要求越來越高,也為Android程序員增加了額外的工作量。
Android7.0引入私有目錄被限制訪問和StrictMode API 。
私有目錄被限制訪問是指在Android7.0中為了提高應用的安全性,在7.0上應用私有目錄將被限制訪問,這與iOS的沙盒機制類似。
StrictMode API是指禁止向你的應用外公開 file:// URI。 如果一項包含文件 file:// URI類型 的 Intent 離開你的應用,則會報出異常,也就是說不能訪問你應用私有的文件夾了
帶來的影響:這項權限的變更將意味著你無法通過File API訪問手機存儲上的數(shù)據(jù)了,基于File API的一些文件瀏覽器等也將受到很大的影響,例如文件下載安裝、上傳圖片功能、系統(tǒng)相機拍照,或裁切照片功能等。
3.4 得出BUG引入原因
綜上所述,我們理解了更新升級的功能和功能實現(xiàn)原理,以及通過對Android系統(tǒng)文件訪問權限的特性的詳細分析,得出在Android 7.0上,對文件的訪問權限作出了修改,從代碼中可以看出,Uri.fromFile導致我們在7.0上出現(xiàn)了問題,它其實就是生成一個file://URL。這就是為什么在下載完成后,無法進行自動安裝,因為一旦我們通過這種辦法打開系統(tǒng)安裝器,就認為file:// URI類型的 Intent 離開我的應用,這樣程序就會發(fā)生異常;
所以在Android7.0上,不能在使用file://格式的Uri 訪問文件 ,Android 7.0提供 FileProvider,應該使用這個來獲取apk地址,然后安裝apk。
4.解決方案
解決方案那就是允許共享你私有目錄下的一個文件夾,共享出去讓大家訪問,這樣就可以訪問你下載的apk來安裝了,將使用FileProvider,它的步驟是:
- 第一步:
在AndroidManifest.xml中注冊provider,provider可以向應用外提供數(shù)據(jù)。
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="你的包名.fileprovider"
android:grantUriPermissions="true"
android:exported="false">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
- 第二步:
在res/xml/file_paths.xml創(chuàng)建文件。 內(nèi)容為:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<paths>
<external-path path="" name="download"/>
</paths>
</resources>
這個要說明一下
<files-path/>代表的根目錄: [Context.getFilesDir()](https://developer.android.com/reference/android/content/Context.html?hl=zh-tw#getFilesDir())
<external-path/>代表的根目錄: [Environment.getExternalStorageDirectory()](https://developer.android.com/reference/android/os/Environment.html?hl=zh-tw#getExternalStorageDirectory())
<cache-path/>代表的根目錄: [getCacheDir()](https://developer.android.com/reference/android/content/Context.html?hl=zh-tw#getCacheDir())
這樣就把這個目錄給共享出去了
- 第三步:通過FileProvider獲取URI進行安裝成功
if(Build.VERSION.SDK_INT>=24) {//判讀版本是否在7.0以上
Uri apkUri = FileProvider.getUriForFile(this, "你的包名.fileprovider", apkFile);//在AndroidManifest中的android:authorities值
Intent install = new Intent(Intent.ACTION_VIEW);
install.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
install.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
install.setDataAndType(apkUri, "application/vnd.android.package-archive");
startActivity(install);
} else{
Intent install = new Intent(Intent.ACTION_VIEW);
install.setDataAndType(Uri.fromFile(apkFile), "application/vnd.android.package-archive");
install.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(install);
}
5.總結
將功能代碼實現(xiàn)和系統(tǒng)平臺特征分析結合起來,運用到測試實踐中,還是蠻重要的,通過看別人的代碼了解功能的實現(xiàn)方式,進而思考這種實現(xiàn)方式在各系統(tǒng)版本中可能會存在的適配問題,避免掉一些由于對系統(tǒng)對功能實現(xiàn)不了解而忽略的場景,使得測試覆蓋率更高,更精準。
另外,一個困擾自己超過2個小時的問題有必要整理下來,這篇小文章不過用了30分鐘整理,但是積累多了也是一份寶貴的財富。