Detailed explanation of Android installation method of APK through code

  • 2021-12-04 19:47:10
  • OfStack

In APK development, it is not difficult to open the system installer to install APK through Java code, and all Android systems have this function.

However, with the iteration of Android system version, its control over permissions is becoming more and more strict, or it is becoming more and more concerned with security. This results in functionality that was previously possible with a few simple lines of code, but is now much more complex. The watershed for the limitation of opening system installer by code is on Android 7.0, that is, Android N. Generally, one approach is used for systems above Android and N, and the other approach is used below. The traditional way of installing APK through code

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);

This method is simple, crude and practical, as long as you know the location of APK to be installed and have access rights.
But now the mainstream version of Android mobile phone system in the market is higher than 7.0, and this method is almost useless

The way APK is installed by code on high version systems


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);

When it comes to authority, With the continuous improvement of Android version, The security of the system is getting higher and higher. Many permissions are not just registered in the manifest file. The read and write permissions of the memory card are dangerous permissions, which need to be added dynamically by using code. Here I use RxPermiision framework, and the methods of obtaining permissions may be different when encountering 9.0 or higher versions of the system.


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) {
     // Permission permission 
     // You can add your own actions here 
    } else {
     //  Permission denied 
    }
   }
  });
 }

Android for more permissions: https://www.jianshu.com/p/24f79a70025b

The above code installation code does not seem to be much different from the traditional way, does it?

Yes, but the real difference is not reflected in Java code.

In higher version systems, APK can no longer directly access the private data of other APK.

What is the private data of APK?

The exclusive directory created by APK under the data directory during installation is naturally its private data. In addition, as long as it is an File object encapsulated in the application, whether the file itself was created by the program or not, the file belongs to the "private data" of the program. For example, suppose we connect our mobile phone to our computer and push an APK file into the sdcard directory through adb push. Then we wrote a code by ourselves, and when the installation package in sdcard is transferred to PackageInstaller of the system for installation, security errors will be reported, because this file located in sdcard directory is "private data" for our code, and it is not allowed to be directly exposed to PackageInstaller.

Let's look at ways to expose "private data" to other programs in high-version systems.

In higher versions, Android 7.0 and above, the only way to open (expose) private data is through ContentProvider.

The specific steps are roughly as follows:

Configure ContentProvider information in AndroidManifest. xml; Configure paths information to be opened; Encapsulate file information through FileProvider in Java code.

1. AndroidManifest. xml configuration

As mentioned earlier, in the high-version system, the previous direct opening is actually changed into indirect opening through ContentProvider. So we need to add an provider tag to AndroidManifest. xml, as shown below:


<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: The name attribute is filled with the full name of the FileProvider class. This class can fill in two values, one under the support (android. support. v4.content. FileProvider) package and one under the androidx (androidx. core. content. FileProvider) package. Both of these can be filled in, and there is no difference in essence. But you have to decide which one to use based on the actual situation, that is, it depends on whether your project uses androidx support package or support support package. The relationship between support and androidx will not be repeated in this paper. android: The authorities attribute is an uri tag header for accessing file resources like the normal ContentProvider 1. Value content can be filled in according to actual needs. android: exported This attribute indicates whether other app can access this provider android: grantUriPermissions This attribute is used to authorize the data subset of the content provider If the grantUriPermissions property of the content provider is set to true, permissions can be granted to any data within the scope of the content provider. However, if the grantUriPermission attribute is set to false, permissions can only be granted to the subset of data specified by this element. 1 A content provider can contain any number of elements. Each can only specify one path (one of the three possible attributes). The content in the meta-data tag is concerned with the content in the android: resource attribute. The value of this attribute leads to a self-configured xml file. This xml file records the path information in the device. Simple understanding means that you want to open the file resources in which directories to the third party. Please refer to the description in step 2 for the configuration of this xml.

2. paths Configuration

Usually, a new xml directory is created under the res directory of the project, and a new xml file is created under the xml directory. The name of the file must match the 1 configured in the @ xml/attribute value in Step 1. Based on the sample code in Step 1, we need to create a new file_paths. xml file. Here my apk is added to the program's file file, which reads as follows:

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

Please refer to: https://editor. csdn. net/md? articleId=106670247

To put it simply, choose the type of path you want to open, and then fill in the relative path under that type.
Let's talk about it in detail with examples:


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

This means that we want to open the files directory in the program memory, and then the subpath under the files directory is/apk, and the combined absolute path is/data/con. xxx. xxx/files/apk. As for the name tag is used for ContentProvider logo, 1 generally speaking, it is OK to set different values as needed, here I have 1 subdirectory.

3. Java code configuration

The configuration of Java code is nothing special, just use the code at the beginning of the chapter. The key code is actually only one line:

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

The three parameters here are:

context: This means that 1 context needs to be passed over authority: The general code can be obtained in AndroidManifest. xml file: Is the file you need to install

String authority = new StringBuilder(packageName).append(".provider").toString();
// Here's strFile The path of the file + Name; For example: /data/file/apk/xxx.apk
File f=new File(strFile);
Uri uri = FileProvider.getUriForFile(context, authority, file) ; 

Usually, we will take into account the high and low versions of Android systems, so we will use the following "mixed" code:


public void install(){
	try{// There is a file stream read and write, need to deal with 1 Lower anomaly 
		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){
			// If SDK Version >=24 Namely: 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();
 }
}

Summarize


Related articles: