应用安装 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(); } 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); } private void startInstallN () { Uri apkUri = FileProvider.getUriForFile(mAct, mAct.getApplicationContext().getPackageName() + ".fileprovider" , new File(mPath)); Intent install = new Intent(Intent.ACTION_VIEW); install.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); install.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); install.setDataAndType(apkUri, "application/vnd.android.package-archive" ); mAct.startActivity(install); } @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-path、files-path、cache-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" > <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" ); Uri uri = FileProvider.getUriForFile(this , "cn.psvmc.myapp.fileprovider" , file);
观察我们生成的Uri示例,上边是我们普通的fileUri下边是我们生成的ContentUri,区别就在于ContentUri没有暴露具体的文件路径。
fileUri地址构成
file://+文件的全路径
ContentUri地址构成
content://+android:authorities的值/paths中匹配的名称/应用名称
例如:
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); Uri uri = FileProvider.getUriForFile(this , "cn.psvmc.myapp.fileprovider" , file); intent.setDataAndType(uri, "application/vnd.android.package-archive" ); intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); startActivity(intent);