Android应用安装、卸载、状态、打开及Android7以上文件权限设置

应用安装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.provider.Settings;

import androidx.annotation.RequiresApi;
import androidx.core.content.FileProvider;

import java.io.File;

public class AppInstallUtils {
private Activity mAct;
private String mPath;//下载下来后文件的路径
public static int UNKNOWN_CODE = 2018;

public AppInstallUtils(Activity mAct, String mPath) {
this.mAct = mAct;
this.mPath = mPath;
}

public void install() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) startInstallO();
else startInstallN();
}

/**
* android1.x-6.x
*/
private void startInstall() {
Intent install = new Intent(Intent.ACTION_VIEW);
install.setDataAndType(Uri.parse("file://" + mPath), "application/vnd.android.package-archive");
install.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mAct.startActivity(install);
}

/**
* android7.x
*/
private void startInstallN() {
//参数1 上下文, 参数2 在AndroidManifest中的android:authorities值, 参数3 共享的文件
Uri apkUri = FileProvider.getUriForFile(mAct, mAct.getApplicationContext().getPackageName() + ".fileprovider", new File(mPath));
Intent install = new Intent(Intent.ACTION_VIEW);
//由于没有在Activity环境下启动Activity,设置下面的标签
install.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
//添加这一句表示对目标应用临时授权该Uri所代表的文件
install.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
install.setDataAndType(apkUri, "application/vnd.android.package-archive");
mAct.startActivity(install);
}

/**
* android8.x
*/
@RequiresApi(api = Build.VERSION_CODES.O)
private void startInstallO() {
boolean isGranted = mAct.getPackageManager().canRequestPackageInstalls();
if (isGranted) startInstallN();//安装应用的逻辑(写自己的就可以)
else new AlertDialog.Builder(mAct)
.setCancelable(false)
.setTitle("安装应用需要打开未知来源权限,请去设置中开启权限")
.setPositiveButton("确定", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface d, int w) {
Intent intent = new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES);
mAct.startActivityForResult(intent, UNKNOWN_CODE);
}
})
.show();
}
}

打开APP

1
2
3
4
5
6
7
8
9
public static void openApp(Activity activity,String packageName){
Intent launchIntent = activity.getPackageManager().getLaunchIntentForPackage(packageName);
if (launchIntent != null) {
activity.startActivity(launchIntent);
} else {
// 应用未安装或包名无效
Toast.makeText(activity, "应用未安装或包名无效", Toast.LENGTH_SHORT).show();
}
}

应用安装卸载监听

静态注册

新建监听类:BootReceiver继承BroadcastReceiver

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;

public class BootReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
String packageName = intent.getDataString();
packageName = packageName.replaceFirst("package:", "");
//接收安装广播
if (intent.getAction().equals("android.intent.action.PACKAGE_ADDED")) {

Log.i("安装卸载监控", "安装了:" + packageName);
}
//接收卸载广播
if (intent.getAction().equals("android.intent.action.PACKAGE_REMOVED")) {
Log.i("安装卸载监控", "卸载了:" + packageName);

}
}
}

修改AndroidManifest.xml配置文件,添加广播介绍,添加监听的权限

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="cn.psvmc.myapp"
android:versionCode="1"
android:versionName="1.0" >
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name" >
<receiver
android:name=".receiver.BootReceiver"
android:exported="true"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.PACKAGE_ADDED" />
<action android:name="android.intent.action.PACKAGE_REMOVED" />

<data android:scheme="package" />
</intent-filter>
</receiver>
</application>
</manifest>

动态注册

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
private BootReceiver installedReceiver;

@Override
public void onStart(){
super.onStart();

IntentFilter filter = new IntentFilter();
filter.addAction("android.intent.action.PACKAGE_ADDED");
filter.addAction("android.intent.action.PACKAGE_REMOVED");
filter.addDataScheme("package");
installedReceiver = new BootReceiver();
this.registerReceiver(installedReceiver, filter);
}

@Override
public void onDestroy(){
if(installedReceiver != null) {
this.unregisterReceiver(installedReceiver);
}

super.onDestroy();
}

FileProvider

Android7及以上对文件权限的管控抓的很严格。

需要在AndroidManifest.xml里面对它进行声明一个ContentProvider

定义FileProvider

由于FileProvider提供了ContentURI的生成方法,所以我们无需在代码中定义写一个它的子类

name属性是固定的。

authorities可以自己定义,一般是包名字加上.fileprovider

exported设置为false,因为通常是拒绝外部直接访问的。

grantUriPermissions需要为true,需要授予临时的Uri权限。

1
2
3
4
5
6
7
8
9
10
11
12
13
<manifest>
<application>
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="cn.psvmc.myapp.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_path" />
</provider>
</application>
</manifest>

假如我们APP的包名是cn.psvmc.myapp,其中的android:authorities就在包名的基础上添加.fileprovider

1
2
3
4
5
6
7
8
9
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="cn.psvmc.myapp.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_path" />
</provider>

file_path.xml需要建立在res目录下名为xml的目录下,xml目录需要自己建立。

paths下可以包含一个或者多个子节点。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<files-path
name="files"
path="images/" />
<cache-path
name="cache"
path="." />
<external-path
name="external"
path="." />
<external-files-path
name="external-files"
path="." />
<external-cache-path
name="external-cache"
path="." />
<external-media-path
name="external-media"
path="." />
</paths>

这个xml的作用在于为文件生成URI,

其中root-pathfiles-pathcache-path这些标签代表父路径:

  • root-path : File("/")
  • files-path : Context.getFilesDir()
  • cache-path : context.getCacheDir()
  • external-path : Environment.getExternalStorageDirectory()
  • external-files-path : ContextCompat.getExternalFilesDirs(context, null)[0]
  • external-cache-path : ContextCompat.getExternalCacheDirs(context)[0]
  • external-media-path : context.getExternalMediaDirs()[0]

path属性代表子路径,name代表为”父路径/子路径”起的名字,

1
<files-path name="files" path="images/" />

路径对应关系

创建file_paths.xml文件

1
2
3
4
5
6
7
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<!--定义APP的存放目录-->
<external-path
name="AppInstaller"
path="/Download"></external-path>
</paths>

我们还可以在path中用.代替所有目录。

1
2
3
4
//文件路径
File file = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getCanonicalPath() + "/apps/MyApp.apk");
//获取文件对应的content类型Uri
Uri uri = FileProvider.getUriForFile(this, "cn.psvmc.myapp.fileprovider", file);

观察我们生成的Uri示例,上边是我们普通的fileUri下边是我们生成的ContentUri,区别就在于ContentUri没有暴露具体的文件路径。

fileUri地址构成

file://+文件的全路径

ContentUri地址构成

content://+android:authorities的值/paths中匹配的名称/应用名称

例如:

1
2
3
4
//普通的fileUri(通过Uri.fromFile(file)获取)
file:///storage/emulated/0/Download/apps/MyApp.apk
//contentUri
content://cn.psvmc.myapp.fileprovider/AppInstaller/apps/MyApp.apk

APP的安装

1
2
3
4
5
6
7
8
9
//文件路径
File file = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getCanonicalPath() + "/MyApp.apk");
Intent intent = new Intent(Intent.ACTION_VIEW);
//获取文件对应的content类型Uri
Uri uri = FileProvider.getUriForFile(this, "cn.psvmc.myapp.fileprovider", file);
intent.setDataAndType(uri, "application/vnd.android.package-archive");
//intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);//可以不加
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivity(intent);