应用安装
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 73
| 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);
|