1. 程式人生 > 程式設計 >Android 通過程式碼安裝 APK的方法詳解

Android 通過程式碼安裝 APK的方法詳解

在 APK 開發中,通過 Java 程式碼來開啟系統的安裝程式以安裝 APK 並不是什麼難事,一般的 Android 系統都有開放這一功能。

  • 但隨著 Android系統版本的迭代,其對於許可權的把控越來越嚴格,或者說是變得越來越注重安全性。這就導致了以前可以通過很簡單的幾行程式碼就能實現的功能,現在要複雜很多。
  • 對於通過程式碼開啟系統安裝程式這一功能的限制,其分水嶺在 Android7.0,即 Android N 上。通常在 Android N以上的系統使用一種做法,以下則使用另一種做法。
  • 傳統的通過程式碼安裝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);

這種方法簡單粗暴且實用,只要知曉要安裝的 APK 的位置,並擁有訪問許可權即可。
但現在市面上主流的 Android 手機系統版本都已經要高於 7.0 了,這一方法幾乎已經沒有用了

高版本系統上的通過程式碼安裝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);

說到許可權問題,在Android版本不斷提高的趨勢下,系統得安全性也越來越高,很多許可權不只是在清單檔案裡面註冊那麼簡單,記憶體卡得讀寫許可權屬於危險許可權,需要我們使用程式碼動態新增,這裡我使用了RxPermiision框架,遇到9.0或者更高版本的系統時獲取許可權的方法可能會不同。

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) {
          //許可權允許
          //在這裡可以新增自己的操作
        } else {
          // 許可權被拒絕
        }
      }
    });
  }

Android更多許可權得查詢:https://www.jianshu.com/p/24f79a70025b

上面這段程式碼安裝程式碼看起來似乎和傳統的方式並沒有太大的區別是嗎?

確實是,但它真正的區別並沒有在 Java 程式碼上體現出來。

在高版本系統中,APK 已經不能直接訪問其它 APK 的私有資料了。

什麼是APK的私有資料?

APK在安裝過程中於 data 目錄下建立的專屬目錄自然是其私有資料無疑。另外,只要是在應用程式中封裝的 File 物件,不管這個檔案本身是不是由該程式建立的,那這個檔案都屬於該程式的“私有資料”。舉個例子來說,假設我們將手機連線到電腦,通過 adb push 的方式往 sdcard 目錄下推了一個 APK 檔案進去。然後我們自行編寫了一段程式碼,將這個 sdcard 中的安裝包傳到系統的 PackageInstaller 中去安裝,都會報安全錯誤,因為這個位於 sdcard 目錄下檔案對我們這段程式碼來說是“私有資料”,不允許直接暴露給 PackageInstaller。

下面就來看看在高版本系統中暴露“私有資料”給其它程式的方法。

在高版本中,Android7.0 及以上,開放(暴露)私有資料的唯一方式是通過 ContentProvider 來實現。

具體的步驟大致如下:

  • 配置 AndroidManifest.xml 中的 ContentProvider 資訊;
  • 配置要開放的 paths 資訊;
  • 在 Java 程式碼中通過 FileProvider 封裝檔案資訊。

1、AndroidManifest.xml 配置

前面說過,高版本系統中其實就是將以前的直接開放變成通過 ContentProvider 來間接開放。因此我們需要在 AndroidManifest.xml 中新增一個 provider 標籤,示例如下:

<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 類的完整名稱。這個類可以填寫兩個值,一個是位於 support(android.support.v4.content.FileProvider) 包下的,另一個是位於 androidx(androidx.core.content.FileProvider) 包下的。這兩種都可以填寫,本質上沒有區別。但是要根據實際情況來決定用哪個,即要看你的工程引的是 androidx 支援包還是 support 支援包。關於 support 與 androidx 的關係本文就不再贅述了。
  • android:authorities 屬性就是和普通的 ContentProvider 一樣的用於訪問檔案資源的 uri 標籤頭。值內容根據實際需要來填寫即可。
  • android:exported這個屬性表示的是:其他app能否訪問這個provider
  • android:grantUriPermissions 這個屬性用於給內容提供器的資料子集授權
  • 如果內容提供器的grantUriPermissions屬性被設定為true,那麼許可權能夠被授予內容提供器範圍內的任何資料。但是,如果grantUriPermission屬性被設定為false,那麼許可權就只能授予這個元素所指定的資料子集。一個內容提供器能夠包含任意多個元素。每個都只能指定一個路徑(三個可能屬性中的一個)。
  • meta-data 標籤中的內容需要關注的是 android:resource 屬性中的內容。這個屬性的值引向一個自行配置的 xml 檔案,這份 xml 檔案記載的是裝置中的路徑資訊,簡單理解就是你想開放哪些目錄中的檔案資源給第三方使用的意思。關於這個 xml 的配置請看第 2 步的記載。

2、paths 配置

  • 通常的做法是在工程 res 目錄下新建一個 xml 目錄,並在該 xml 目錄下新建一個 xml 檔案。檔案的名稱必須與第 1 步中 @xml/ 屬性值中配置的一致。
  • 根據第 1 步中的示例程式碼,我們需要新建一個 file_paths.xml 檔案。這裡我的apk是保留在程式的file檔案加下得,該檔案的內容如下所示:
<?xml version="1.0" encoding="utf-8"?>
<paths>
  <files-path path="apk/" name="apk" />
</paths>

其他路徑的的配置方式請參考:https://editor.csdn.net/md?articleId=106670247

簡單來說,就是將你要開放出去的路徑的型別選好,然後填上該型別下的相對路徑即可。
我們以示例詳細說說:

<?xml version="1.0" encoding="utf-8"?>
<paths>
  <files-path path="apk/" name="apk" />
</paths>

這表示我們想開放 程式記憶體裡面的files目錄,然後在 files 目錄下的子路徑是 /apk,組合成絕對路徑就是 /data/con.xxx.xxx/files/apk 。至於 name 標籤則是用於 ContentProvider 標識使用的,一般來講按需要設定成不同的值就可以了,這裡我有一個子目錄。

3、Java 程式碼配置

Java 程式碼的配置就沒什麼特別的了,直接以章節首部的程式碼來用就可以了。關鍵的程式碼其實只有一行:

Uri uri = FileProvider.getUriForFile(context,authority,file);

這裡的三個引數分別為:

  • context:這裡表示需要傳一個上下文過來
  • 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,file);

通常我們都會兼顧 Android 高低版本的系統,因此會使用如下所示的“混合型”程式碼:

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,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();
  }
}

總結

到此這篇關於Android 通過程式碼安裝 APK的方法詳解的文章就介紹到這了,更多相關android 程式碼安裝apk內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!