問題描述
我們在進行Android開發的時候,一般都會在應用裡檢測有沒有更新,並且從網上下載最新的版本包,覆蓋本地的舊版本。在我的專案中,出現了一個問題,就是當安裝包下載到本地的時候,產生了“解析包時出現問題”這樣的錯誤,導致無法安裝相應的新版本。同時,值得一提的是,這個問題只有在我們用程式碼呼叫去進行安裝的時候才會發生,我們手動去用安裝包覆蓋本地的舊包則沒有出現問題。經過反覆的測試,發現這個問題只會發生在Android 7.0版本的裝置上,在較低版本的裝置上則無這個問題。
這也是在網上比較通用的方案,就是在打包的時候我們使用了V2簽名導致無法進行安裝。
Android 7.0 引入一項新的應用簽名方案 APK Signature Scheme v2,它能提供更快的應用安裝時間和更多針對未授權 APK 檔案更改的保護。在預設情況下,Android Studio 2.2 和 Android Plugin for Gradle 2.2 會使用 APK Signature Scheme v2 和傳統簽名方案來簽署應用。
這項新方案並非強制性的,如果應用在使用 APK Signature Scheme v2 時不能正確開發,可以停用這項新方案。禁用過程會導致 Android Studio 2.2 和 Android Plugin for Gradle 2.2 僅使用傳統簽名方案來簽署應用。要僅用傳統方案簽署,開啟模組級 build.gradle 檔案,然後將行 v2SigningEnabled false 新增到版本簽名配置中:
android { … defaultConfig { … } signingConfigs { release { storeFile file(“myreleasekey.keystore”) storePassword “password” keyAlias “MyReleaseKey” keyPassword “password” v2SigningEnabled false } } }
根據官方文件,就是在我們的gradle檔案裡的相應位置新增這行程式碼
v2SigningEnabled false
解決方案2
但是,在我的情境中,即使使用了方案一,仍然在更新包下載完成後顯示了“解析包時出現問題”這樣的字樣,導致下載更新流程出錯。
這個時候,就有可能是另外一種情況:當我們在7.0上使用系統服務去下載apk並且試圖安裝時,有可能由於許可權問題導致安裝失敗。
每個Android版本的釋出,對於安全性問題的要求越來越高,也為Android程式設計師增加了額外的工作量。Android6.0引入動態許可權控制(Runtime Permissions),Android7.0引入私有目錄被限制訪問和StrictMode API 。私有目錄被限制訪問是指在Android7.0中為了提高應用的安全性,在7.0上應用私有目錄將被限制訪問,這與iOS的沙盒機制類似。StrictMode API是指禁止向你的應用外公開 file:// URI。 如果一項包含檔案 file:// URI型別 的 Intent 離開你的應用,則會報出異常。
以下是我原來在7.0上出問題的程式碼:
Intent install = new Intent(Intent.ACTION_VIEW); install.setDataAndType(Uri.fromFile(new File(fileName)), "application/vnd.android.package-archive"); install.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(install);
從程式碼中可以看出,Uri.fromFile導致我們在7.0上出現了問題,它其實就是生成一個file://URL。這就是為什麼在下載完成後,呼叫這段程式碼去安裝的時候出錯,因為一旦我們通過這種辦法開啟系統安裝器,就認為file:// URI型別的 Intent 離開我的應用,這樣程式就會發生異常;而我們手動去把安裝包覆蓋原來的舊包則沒有問題。
解決方案將使用FileProvider,它的步驟是:
第一步:
在AndroidManifest.xml中註冊provider,provider可以嚮應用外提供資料。
<provider android:name="android.support.v4.content.FileProvider" android:authorities="com.dafangya.app.pro.fileprovider" android:grantUriPermissions="true" android:exported="false"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths" /> </provider>
其中com.dafangya.app.pro是我的應用包名。
第二步:
- 在res/xml/file_paths.xml建立檔案。 內容為:
-
<?xml version="1.0" encoding="utf-8"?> <resources> <paths> <external-path path="" name="download"/> </paths> </resources>
- 第三步:
-
String fileName = cursor.getString(fileNameIdx);//承接我的程式碼,filename指獲取到了我的檔案相應路徑 if (fileName != null) { if (fileName.endsWith(".apk")) { if(Build.VERSION.SDK_INT>=24) {//判讀版本是否在7.0以上 File file= new File(fileName); Uri apkUri = FileProvider.getUriForFile(context, "com.dafangya.app.pro.fileprovider", file);//在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);//新增這一句表示對目標應用臨時授權該Uri所代表的檔案 install.setDataAndType(apkUri, "application/vnd.android.package-archive"); context.startActivity(install); } else{ Intent install = new Intent(Intent.ACTION_VIEW); install.setDataAndType(Uri.fromFile(new File(fileName)), "application/vnd.android.package-archive"); install.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(install); } } }
因為對於7.0以下的裝置,還是走以前的邏輯,所以上面的程式碼進行分情況討論,6.0及其以下的裝置還是走以前的邏輯。
- 第四步
按照大部分教程來說,第四步其實是不存在的,但是在我的專案中,執行到第三步中的程式碼時,還是報錯了,錯誤如下: -
java.lang.RuntimeException: Error receiving broadcast Intent { act=android.intent.action.DOWNLOAD_COMPLETE flg=0x10 pkg=com.dafangya.app.pro (has extras) } in com.b.b.a.a.e$1@457cfd2 at android.app.LoadedApk$ReceiverDispatcher$Args.run(LoadedApk.java:1229) at android.os.Handler.handleCallback(Handler.java:755) at android.os.Handler.dispatchMessage(Handler.java:95) at android.os.Looper.loop(Looper.java:156) at android.app.ActivityThread.main(ActivityThread.java:6524) at java.lang.reflect.Method.invoke(Method.java) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:941) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:831) Caused by: java.lang.IllegalArgumentException: Failed to find configured root that contains /data/data/com.android.providers.downloads/cache/product_other_V1.4.8ceshi.apk at android.support.v4.content.FileProvider$SimplePathStrategy.getUriForFile(FileProvider.java:678) at android.support.v4.content.FileProvider.getUriForFile(FileProvider.java:377) at com.example.xh.toolsdk.umeng.Downloads$1.onReceive(Downloads.java:95) at android.app.LoadedApk$ReceiverDispatcher$Args.run(LoadedApk.java:1219) ... 7 more
- 第四步