前言
以往安裝apk都是很簡單的方法, Intent 里添加apk的文件就可以調(diào)用系統(tǒng)安裝界面.后來隨著谷歌對安全的重視,從Android 7開始以往的方式都不能用,然而到Android 8 又有改動,相信隨著Android 的發(fā)展,以后的版本也會有改動,崇尚模塊化開發(fā)的我便希望每一個細(xì)小的功能,顆粒度最少的功能都可以由一個模塊來負(fù)責(zé),然后每個項(xiàng)目需要這個功能時調(diào)用這個模塊即可,而這個模塊只需要維護(hù)好自己的兼容性問題便解決所有項(xiàng)目的兼容性問題.
本文思路是提供一個筆者暫時覺得最優(yōu)的方案,然后原理分析提供每個Android 版本的安裝的原理思路
最優(yōu)解決方案 Android Install Apk 庫
最優(yōu)解決方案 : FitAndroid8
首先筆者在解決Android 7 的安裝問題時,遇到系統(tǒng)的私有目錄訪問限制問題,在解決同時感覺谷歌提供的解決方案特別麻煩,需要項(xiàng)目里因?yàn)獒槍ndroid 7 而增加一些文件和AndroidManifest 增加一些代碼,這非常不合理,到了之后的版本或許又不一樣,這些額外的代碼都會帶來維護(hù)的成本,后來搜索很久發(fā)現(xiàn) FitAndroid7 這個庫特別適合,在不用增加自己項(xiàng)目額外的代碼同時,解決Android 7 以下的安裝問題.然后到Android 8 系統(tǒng)時發(fā)現(xiàn)這個庫功能失效,所以筆者便在前者的基礎(chǔ)上稍微修改,讓FitAndroid8能兼容暫時所有版本的安裝,一行代碼完成一個功能,不引入其他額外與項(xiàng)目無關(guān)的代碼和文件.
使用方式:
public void installApk(View view) {
File file = new File(Environment.getExternalStorageDirectory(), "app-debug.apk");
Intent intent = new Intent(Intent.ACTION_VIEW);
// 僅需改變這一行
FileProvider8.setIntentDataAndType(this,
intent, "application/vnd.android.package-archive", file, true);
startActivity(intent);
}
原理分析
筆者的習(xí)慣是把問題用最簡單的方式解決,同時也需要知道其原理,以下內(nèi)容為原理解析.
Android 8 如何安裝apk
Android 8到時有了什么改變以致安裝apk的方法有很大改變呢?
在2017年8月29號的谷歌開發(fā)者博客中寫道 <<在 Android O 中更安全地獲取應(yīng)用>>新的安裝未知應(yīng)用的,Android O 禁用了總是安裝未知應(yīng)用的選擇,改為安裝未知應(yīng)用時提出設(shè)置的提示,減少惡意應(yīng)用通過虛假的安裝界面欺騙用戶行為.
所以開發(fā)者需要調(diào)整AndroidManifest文件里的權(quán)限,增加 REQUEST_INSTALL_PACKAGES權(quán)限.
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
谷歌建議是通過PackageManager canRequestPackageInstalls() 的API案站,查詢此權(quán)限的狀態(tài),然后使用使用 ACTION_MANAGE_UNKNOWN_APP_SOURCES Intent 操作
Intent intent = new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES);
startActivityForResult(intent, RESULT_CODE);
但是筆者不建議這樣使用,因?yàn)槭褂?ACTION_MANAGE_UNKNOWN_APP_SOURCES Intent 操作后會跳到所有應(yīng)用列表,然后從眾多的應(yīng)用里選擇對應(yīng)的APP的選擇進(jìn)入再打開權(quán)限,這樣的用戶體驗(yàn)不好.
所以筆者建議不使用判斷和Intent跳轉(zhuǎn).而是直接使用Intent里帶apk的安裝,會有提示,然后直接進(jìn)入權(quán)限開關(guān)的界面,這樣的體驗(yàn)相對好,而發(fā)現(xiàn)其他的主流的APP安裝時也是這樣.
流程如下:
Android 7 如何安裝apk
這里談?wù)凙ndroid 7 安裝apk時有什么改變.
參考谷歌文檔 FileProvider , Setup-sharing
從文檔里知道,Android 7 開始增加安全性,文件私有化,而需要共享文件給其他程序,例如APK安裝程序,需要通過FileProvider配置共享文件,配置表是基于XML文件實(shí)現(xiàn),然后通過Content URI攜帶配置文件xml來共享文件.
實(shí)現(xiàn)配置FileProvider 需要兩步:
第一步: 需要配置AndroidManifest.xml清單.
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<!-- 元數(shù)據(jù) -->
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
第二步:建立文件 res/xml/file_paths.xml.
<?xml version="1.0" encoding="utf-8"?>
<resources>
<paths>
<!--
files-path: 該方式提供在應(yīng)用的內(nèi)部存儲區(qū)的文件/子目錄的文件。
它對應(yīng)Context.getFilesDir返回的路徑:eg:”/data/data/com.***.***/files”。
cache-path: 該方式提供在應(yīng)用的內(nèi)部存儲區(qū)的緩存子目錄的文件伴嗡。
它對應(yīng)Context.getCacheDir返回的路:eg:“/data/data/com.***.***/cache”锁蠕;
external-path: 該方式提供在外部存儲區(qū)域根目錄下的文件窍株。
它對應(yīng)Environment.getExternalStorageDirectory返回的路徑
external-files-path: Context.getExternalFilesDir(null)
external-cache-path: Context.getExternalCacheDir(String)
-->
<external-path name="download" path="" />
</paths>
</resources>
而其中的 path=""是代表根目錄,也就是向共享的應(yīng)用程序共享根目錄以及其子目錄的任何一個文件.理論上說假如共享程序是惡意程序,那它便可以獲取你的應(yīng)用的所有共享文件信息.
最后準(zhǔn)備好上面兩步便可以安裝文件
/**
* android7.x
* @param path 文件路徑
*/
public void startInstallN(Context context, String path) {
//參數(shù)1 上下文, 參數(shù)2 在AndroidManifest中的android:authorities值, 參數(shù)3 共享的文件
Uri apkUri = FileProvider.getUriForFile(context, Constants.AUTHORITY, new File(path));
Intent install = new Intent(Intent.ACTION_VIEW);
//由于沒有在Activity環(huán)境下啟動Activity,設(shè)置下面的標(biāo)簽
install.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
//添加這一句表示對目標(biāo)應(yīng)用臨時授權(quán)該Uri所代表的文件
install.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
install.setDataAndType(apkUri, "application/vnd.android.package-archive");
startActivity(install);
}
Android 6及以下版本 如何安裝apk
最后Android 6的安裝是簡單
/**
*android1.x-6.x
*@param path 文件的路徑
*/
public void startInstall(Context context, String path) {
Intent install = new Intent(Intent.ACTION_VIEW);
install.setDataAndType(Uri.parse("file://" + path), "application/vnd.android.package-archive");
install.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(install);
}
最后
最后簡單總結(jié), 特別認(rèn)同 FitAndroid7 解決方法的理念.一行代碼解決一個基礎(chǔ)功能.