Android 通過(guò)代碼安裝 APK的方法詳解
在 APK 開(kāi)發(fā)中,通過(guò) Java 代碼來(lái)打開(kāi)系統(tǒng)的安裝程序以安裝 APK 并不是什么難事,一般的 Android 系統(tǒng)都有開(kāi)放這一功能。
- 但隨著 Android系統(tǒng)版本的迭代,其對(duì)于權(quán)限的把控越來(lái)越嚴(yán)格,或者說(shuō)是變得越來(lái)越注重安全性。這就導(dǎo)致了以前可以通過(guò)很簡(jiǎn)單的幾行代碼就能實(shí)現(xiàn)的功能,現(xiàn)在要復(fù)雜很多。
- 對(duì)于通過(guò)代碼打開(kāi)系統(tǒng)安裝程序這一功能的限制,其分水嶺在 Android7.0,即 Android N 上。通常在 Android N以上的系統(tǒng)使用一種做法,以下則使用另一種做法。
- 傳統(tǒng)的通過(guò)代碼安裝APK的方式
File apk = new File(...); Uri uri = Uri.fromFile(apk); Intent intent = new Intent(); intent.setClassName("com.android.packageinstaller", "com.android.packageinstaller.PackageInstallerActivity"); intent.setData(uri); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); startActivity(intent);
這種方法簡(jiǎn)單粗暴且實(shí)用,只要知曉要安裝的 APK 的位置,并擁有訪問(wèn)權(quán)限即可。
但現(xiàn)在市面上主流的 Android 手機(jī)系統(tǒng)版本都已經(jīng)要高于 7.0 了,這一方法幾乎已經(jīng)沒(méi)有用了
高版本系統(tǒng)上的通過(guò)代碼安裝APK的方式
File apk = new File(...); Intent intent = new Intent(Intent.ACTION_VIEW); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); Uri uri = FileProvider.getUriForFile(this, "com.apk.demo.fileprovider", apk); intent.setDataAndType(uri, "application/vnd.android.package-archive"); startActivity(intent);
說(shuō)到權(quán)限問(wèn)題,在Android版本不斷提高的趨勢(shì)下,系統(tǒng)得安全性也越來(lái)越高,很多權(quán)限不只是在清單文件里面注冊(cè)那么簡(jiǎn)單,內(nèi)存卡得讀寫權(quán)限屬于危險(xiǎn)權(quán)限,需要我們使用代碼動(dòng)態(tài)添加,這里我使用了RxPermiision框架,遇到9.0或者更高版本的系統(tǒng)時(shí)獲取權(quán)限的方法可能會(huì)不同。
private void rxPermission() { RxPermissions rxPermissions = new RxPermissions(this); rxPermissions.request(Manifest.permission.WRITE_EXTERNAL_STORAGE).subscribe(new Consumer<Boolean>() { @Override public void accept(Boolean granted) throws Exception { if (granted) { //權(quán)限允許 //在這里可以添加自己的操作 } else { // 權(quán)限被拒絕 } } }); }
Android更多權(quán)限得查詢:https://www.jianshu.com/p/24f79a70025b
上面這段代碼安裝代碼看起來(lái)似乎和傳統(tǒng)的方式并沒(méi)有太大的區(qū)別是嗎?
確實(shí)是,但它真正的區(qū)別并沒(méi)有在 Java 代碼上體現(xiàn)出來(lái)。
在高版本系統(tǒng)中,APK 已經(jīng)不能直接訪問(wèn)其它 APK 的私有數(shù)據(jù)了。
什么是APK的私有數(shù)據(jù)?
APK在安裝過(guò)程中于 data 目錄下創(chuàng)建的專屬目錄自然是其私有數(shù)據(jù)無(wú)疑。另外,只要是在應(yīng)用程序中封裝的 File 對(duì)象,不管這個(gè)文件本身是不是由該程序創(chuàng)建的,那這個(gè)文件都屬于該程序的“私有數(shù)據(jù)”。舉個(gè)例子來(lái)說(shuō),假設(shè)我們將手機(jī)連接到電腦,通過(guò) adb push 的方式往 sdcard 目錄下推了一個(gè) APK 文件進(jìn)去。然后我們自行編寫了一段代碼,將這個(gè) sdcard 中的安裝包傳到系統(tǒng)的 PackageInstaller 中去安裝,都會(huì)報(bào)安全錯(cuò)誤,因?yàn)檫@個(gè)位于 sdcard 目錄下文件對(duì)我們這段代碼來(lái)說(shuō)是“私有數(shù)據(jù)”,不允許直接暴露給 PackageInstaller。
下面就來(lái)看看在高版本系統(tǒng)中暴露“私有數(shù)據(jù)”給其它程序的方法。
在高版本中,Android7.0 及以上,開(kāi)放(暴露)私有數(shù)據(jù)的唯一方式是通過(guò) ContentProvider 來(lái)實(shí)現(xiàn)。
具體的步驟大致如下:
- 配置 AndroidManifest.xml 中的 ContentProvider 信息;
- 配置要開(kāi)放的 paths 信息;
- 在 Java 代碼中通過(guò) FileProvider 封裝文件信息。
1、AndroidManifest.xml 配置
前面說(shuō)過(guò),高版本系統(tǒng)中其實(shí)就是將以前的直接開(kāi)放變成通過(guò) ContentProvider 來(lái)間接開(kāi)放。因此我們需要在 AndroidManifest.xml 中添加一個(gè) provider 標(biāo)簽,示例如下:
<provider android:name="androidx.core.content.FileProvider" android:authorities="com.your.app.fileprovider" android:exported="false" android:grantUriPermissions="true"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths" /> </provider>
- android:name 屬性填寫的是 FileProvider 類的完整名稱。這個(gè)類可以填寫兩個(gè)值,一個(gè)是位于 support(android.support.v4.content.FileProvider) 包下的,另一個(gè)是位于 androidx(androidx.core.content.FileProvider) 包下的。這兩種都可以填寫,本質(zhì)上沒(méi)有區(qū)別。但是要根據(jù)實(shí)際情況來(lái)決定用哪個(gè),即要看你的工程引的是 androidx 支援包還是 support 支援包。關(guān)于 support 與 androidx 的關(guān)系本文就不再贅述了。
- android:authorities 屬性就是和普通的 ContentProvider 一樣的用于訪問(wèn)文件資源的 uri 標(biāo)簽頭。值內(nèi)容根據(jù)實(shí)際需要來(lái)填寫即可。
- android:exported這個(gè)屬性表示的是:其他app能否訪問(wèn)這個(gè)provider
- android:grantUriPermissions 這個(gè)屬性用于給內(nèi)容提供器的數(shù)據(jù)子集授權(quán)
- 如果內(nèi)容提供器的grantUriPermissions屬性被設(shè)置為true,那么權(quán)限能夠被授予內(nèi)容提供器范圍內(nèi)的任何數(shù)據(jù)。但是,如果grantUriPermission屬性被設(shè)置為false,那么權(quán)限就只能授予這個(gè)元素所指定的數(shù)據(jù)子集。一個(gè)內(nèi)容提供器能夠包含任意多個(gè)元素。每個(gè)都只能指定一個(gè)路徑(三個(gè)可能屬性中的一個(gè))。
- meta-data 標(biāo)簽中的內(nèi)容需要關(guān)注的是 android:resource 屬性中的內(nèi)容。這個(gè)屬性的值引向一個(gè)自行配置的 xml 文件,這份 xml 文件記載的是設(shè)備中的路徑信息,簡(jiǎn)單理解就是你想開(kāi)放哪些目錄中的文件資源給第三方使用的意思。關(guān)于這個(gè) xml 的配置請(qǐng)看第 2 步的記載。
2、paths 配置
- 通常的做法是在工程 res 目錄下新建一個(gè) xml 目錄,并在該 xml 目錄下新建一個(gè) xml 文件。文件的名稱必須與第 1 步中 @xml/ 屬性值中配置的一致。
- 根據(jù)第 1 步中的示例代碼,我們需要新建一個(gè) file_paths.xml 文件。這里我的apk是保留在程序的file文件加下得,該文件的內(nèi)容如下所示:
<?xml version="1.0" encoding="utf-8"?> <paths> <files-path path="apk/" name="apk" /> </paths>
其他路徑的的配置方式請(qǐng)參考:https://editor.csdn.net/md?articleId=106670247
簡(jiǎn)單來(lái)說(shuō),就是將你要開(kāi)放出去的路徑的類型選好,然后填上該類型下的相對(duì)路徑即可。
我們以示例詳細(xì)說(shuō)說(shuō):
<?xml version="1.0" encoding="utf-8"?> <paths> <files-path path="apk/" name="apk" /> </paths>
這表示我們想開(kāi)放 程序內(nèi)存里面的files目錄,然后在 files 目錄下的子路徑是 /apk,組合成絕對(duì)路徑就是 /data/con.xxx.xxx/files/apk 。至于 name 標(biāo)簽則是用于 ContentProvider 標(biāo)識(shí)使用的,一般來(lái)講按需要設(shè)置成不同的值就可以了,這里我有一個(gè)子目錄。
3、Java 代碼配置
Java 代碼的配置就沒(méi)什么特別的了,直接以章節(jié)首部的代碼來(lái)用就可以了。關(guān)鍵的代碼其實(shí)只有一行:
Uri uri = FileProvider.getUriForFile(context, authority, file);
這里的三個(gè)參數(shù)分別為:
- context:這里表示需要傳一個(gè)上下文過(guò)來(lái)
- authority:可以通代碼在AndroidManifest.xml里面獲得
- file:是你需要的安裝的文件
String authority = new StringBuilder(packageName).append(".provider").toString(); //這里的strFile文件的路徑+名稱;例如:/data/file/apk/xxx.apk File f=new File(strFile); Uri uri = FileProvider.getUriForFile(context, authority, file);
通常我們都會(huì)兼顧 Android 高低版本的系統(tǒng),因此會(huì)使用如下所示的“混合型”代碼:
public void install(){ try{//這里有文件流的讀寫,需要處理一下異常 Intent intent = new Intent(Intent.ACTION_VIEW); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N){ //如果SDK版本>=24,即:Build.VERSION.SDK_INT >= 24 String packageName = context.getApplicationContext().getPackageName(); String authority = new StringBuilder(packageName).append(".provider").toString(); uri = FileProvider.getUriForFile(context, authority, file); intent.setDataAndType(uri, "application/vnd.android.package-archive"); } else{ uri = Uri.fromFile(file); intent.setDataAndType(uri, "application/vnd.android.package-archive"); } context.startActivity(intent); }catch (Exception e) { e.printStackTrace(); } }
總結(jié)
到此這篇關(guān)于Android 通過(guò)代碼安裝 APK的方法詳解的文章就介紹到這了,更多相關(guān)android 代碼安裝apk內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Android中實(shí)現(xiàn)ping功能的多種方法詳解
這篇文章主要介紹了Android中實(shí)現(xiàn)ping功能的多種方法詳解,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-03-03Android 集成 google 登錄并獲取性別等隱私信息的實(shí)現(xiàn)代碼
這篇文章主要介紹了Android 集成 google 登錄并獲取 性別等隱私信息,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-06-06Android RecyclerView滾動(dòng)定位
這篇文章主要為大家詳細(xì)介紹了Android RecyclerView滾動(dòng)定位的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-01-01Android開(kāi)發(fā)實(shí)現(xiàn)的圖片點(diǎn)擊切換功能示例
這篇文章主要介紹了Android開(kāi)發(fā)實(shí)現(xiàn)的圖片點(diǎn)擊切換功能,涉及Android ImageView組件創(chuàng)建、布局及實(shí)現(xiàn)圖形切換相關(guān)操作技巧,需要的朋友可以參考下2019-04-04Android 自動(dòng)化測(cè)試經(jīng)驗(yàn)分享 深入U(xiǎn)iScrollable
UiScrollable是一個(gè)UiCollection(這東西還沒(méi)搞懂),我們可以使用它,在可滑動(dòng)的頁(yè)面(水平滑動(dòng)或上下滑動(dòng)都可以)上查找我們想要的控件(item)2013-05-05Android 使用ViewPager實(shí)現(xiàn)輪播圖效果
這篇文章主要介紹了Android 使用ViewPager實(shí)現(xiàn)輪播圖效果,通過(guò)實(shí)例代碼給大家講解了適配器和各個(gè)方法的作用介紹,需要的朋友可以參考下2017-05-05快速解決Android7.0下沉浸式狀態(tài)欄變灰的問(wèn)題
下面小編就為大家分享一篇快速解決Android7.0下沉浸式狀態(tài)欄變灰的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-01-01解決RecyclerView無(wú)法onItemClick問(wèn)題的兩種方法
這篇文章主要介紹了解決RecyclerView無(wú)法onItemClick問(wèn)題的相關(guān)知識(shí),非常不錯(cuò),具有參考借鑒價(jià)值,感興趣的朋友一起看下吧2016-07-07Android簡(jiǎn)單實(shí)現(xiàn)自定義流式布局的方法
這篇文章主要介紹了Android簡(jiǎn)單實(shí)現(xiàn)自定義流式布局的方法,結(jié)合實(shí)例形式分析了Android流式布局的原理與實(shí)現(xiàn)技巧,需要的朋友可以參考下2016-07-07