一些产物要求APP在升级时可以大概实现静默安装,而无需弹出安装界面让用户确认。这里提出两种实现方案:
方案一:通过pm下令安装
APP调用『pm』下令实现静默安装,此方案无须修改Android源码,但必要root权限。实现如下:
/** * Silent install * * @param path Package * @return true: success false: failed */public static boolean installSilent(String path) { boolean result = false; BufferedReader es = null; DataOutputStream os = null; try { Process process = Runtime.getRuntime().exec("su"); os = new DataOutputStream(process.getOutputStream()); String command = "pm install -r " + path + "\n"; os.write(command.getBytes(Charset.forName("utf-8"))); os.flush(); os.writeBytes("exit\n"); os.flush(); process.waitFor(); es = new BufferedReader(new InputStreamReader(process.getErrorStream())); String line; StringBuilder builder = new StringBuilder(); while ((line = es.readLine()) != null) { builder.append(line); } Log.d(TAG, "install msg is " + builder.toString()); /* Installation is considered a Failure if the result contains the Failure character, or a success if it is not. */ if (!builder.toString().contains("Failure")) { result = true; } } catch (Exception e) { Log.e(TAG, e.getMessage(), e); } finally { try { if (os != null) { os.close(); } if (es != null) { es.close(); } } catch (IOException e) { Log.e(TAG, e.getMessage(), e); } } return result;}方案二 修改PackageInstaller源码
如果没有root权限,方案一将无法实现,因此我们通过定制 PackageInstaller 来实现指定包名可以静默安装,并增长Intent参数来指定静默安装还是默认安装。具体修改如下:
diff --git a/packages/apps/PackageInstaller/src/com/android/packageinstaller/InstallStart.java b/packages/apps/Pacold mode 100644new mode 100755index 12441b5..cbf8c41--- a/packages/apps/PackageInstaller/src/com/android/packageinstaller/InstallStart.java+++ b/packages/apps/PackageInstaller/src/com/android/packageinstaller/InstallStart.java@@ -22,17 +22,30 @@ import android.app.ActivityManager; import android.app.AppGlobals; import android.app.IActivityManager; import android.content.ContentResolver;+import android.content.Context; import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.IPackageManager; import android.content.pm.PackageInstaller; import android.content.pm.PackageManager;+import android.content.pm.PackageInfo;+import android.content.pm.PackageParser;+import android.content.pm.PackageUserState;+import android.content.pm.ProviderInfo; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.RemoteException;+import android.os.SystemProperties; import android.support.annotation.Nullable;+import android.text.TextUtils; import android.util.Log;+import android.content.pm.IPackageInstallObserver;+import android.support.v4.content.FileProvider;++import java.io.File;+import java.lang.reflect.Method;+import java.util.List; import com.android.internal.annotations.VisibleForTesting; @@ -43,6 +56,8 @@ import com.android.internal.annotations.VisibleForTesting; public class InstallStart extends Activity { private static final String LOG_TAG = InstallStart.class.getSimpleName(); + private static final String EXTRA_SILENT_INSTALL = "silent_install";+ private static final String DOWNLOADS_AUTHORITY = "downloads"; private IActivityManager mIActivityManager; private IPackageManager mIPackageManager;@@ -91,40 +106,57 @@ public class InstallStart extends Activity { return; } - Intent nextActivity = new Intent(intent);- nextActivity.setFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);-- // The the installation source as the nextActivity thinks this activity is the source, hence- // set the originating UID and sourceInfo explicitly- nextActivity.putExtra(PackageInstallerActivity.EXTRA_CALLING_PACKAGE, callingPackage);- nextActivity.putExtra(PackageInstallerActivity.EXTRA_ORIGINAL_SOURCE_INFO, sourceInfo);- nextActivity.putExtra(Intent.EXTRA_ORIGINATING_UID, originatingUid);+ Uri pkgUri = intent.getData();+ String path = "";+ if (pkgUri != null) {+ if (pkgUri.getScheme().equals(ContentResolver.SCHEME_FILE)) {+ path = pkgUri.getPath();+ } else if (pkgUri.getScheme().equals(ContentResolver.SCHEME_CONTENT)) {+ path = providerUri2Path(this, pkgUri);+ }+ } - if (PackageInstaller.ACTION_CONFIRM_PERMISSIONS.equals(intent.getAction())) {- nextActivity.setClass(this, PackageInstallerActivity.class);+ if (isSilentInstall(intent, path)) {+ Log.i(LOG_TAG, "silent install path: " + path);+ getPackageManager().installPackage(Uri.fromFile(new File(path)),+ new PackageInstallObserver(), 2, null); } else {- Uri packageUri = intent.getData();-- if (packageUri != null && (packageUri.getScheme().equals(ContentResolver.SCHEME_FILE)- || packageUri.getScheme().equals(ContentResolver.SCHEME_CONTENT))) {- // Copy file to prevent it from being changed underneath this process- nextActivity.setClass(this, InstallStaging.class);- } else if (packageUri != null && packageUri.getScheme().equals(- PackageInstallerActivity.SCHEME_PACKAGE)) {+ Intent nextActivity = new Intent(intent);+ nextActivity.setFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);++ // The the installation source as the nextActivity thinks this activity is the source, hence+ // set the originating UID and sourceInfo explicitly+ nextActivity.putExtra(PackageInstallerActivity.EXTRA_CALLING_PACKAGE, callingPackage);+ nextActivity.putExtra(PackageInstallerActivity.EXTRA_ORIGINAL_SOURCE_INFO, sourceInfo);+ nextActivity.putExtra(Intent.EXTRA_ORIGINATING_UID, originatingUid);++ if (PackageInstaller.ACTION_CONFIRM_PERMISSIONS.equals(intent.getAction())) { nextActivity.setClass(this, PackageInstallerActivity.class); } else {- Intent result = new Intent();- result.putExtra(Intent.EXTRA_INSTALL_RESULT,- PackageManager.INSTALL_FAILED_INVALID_URI);- setResult(RESULT_FIRST_USER, result);+ Uri packageUri = intent.getData();++ if (packageUri != null && (packageUri.getScheme().equals(ContentResolver.SCHEME_FILE)+ || packageUri.getScheme().equals(ContentResolver.SCHEME_CONTENT))) {+ // Copy file to prevent it from being changed underneath this process+ nextActivity.setClass(this, InstallStaging.class);+ } else if (packageUri != null && packageUri.getScheme().equals(+ PackageInstallerActivity.SCHEME_PACKAGE)) {+ nextActivity.setClass(this, PackageInstallerActivity.class);+ } else {+ Intent result = new Intent();+ result.putExtra(Intent.EXTRA_INSTALL_RESULT,+ PackageManager.INSTALL_FAILED_INVALID_URI);+ setResult(RESULT_FIRST_USER, result); - nextActivity = null;+ nextActivity = null;+ } }- } - if (nextActivity != null) {- startActivity(nextActivity);+ if (nextActivity != null) {+ startActivity(nextActivity);+ } }+ finish(); } @@ -247,4 +279,94 @@ public class InstallStart extends Activity { void injectIActivityManager(IActivityManager iActivityManager) { mIActivityManager = iActivityManager; }++ private static String providerUri2Path(Context context, Uri uri) {+ Log.i(LOG_TAG, "providerUri2Path, uri: " + uri.toString());++ try {+ List<ackageInfo> packs = context.getPackageManager()+ .getInstalledPackages(PackageManager.GET_PROVIDERS);+ if (packs != null) {+ for (PackageInfo pack : packs) {+ ProviderInfo[] providers = pack.providers;+ if (providers != null) {+ for (ProviderInfo provider : providers) {+ if (provider.authority.equals(uri.getAuthority())) {+ Class<FileProvider> fileProviderClass = FileProvider.class;+ try {+ Method getPathStrategy = fileProviderClass.getDeclaredMethod(+ "getPathStrategy", Context.class, String.class);+ getPathStrategy.setAccessible(true);+ Object invoke = getPathStrategy.invoke(null, context, uri.getAuthority());+ if (invoke != null) {+ String PathStrategyStringClass = FileProvider.class.getName() + "$PathStr+ Class<?> PathStrategy = Class.forName(PathStrategyStringClass);+ Method getFileForUri = PathStrategy.getDeclaredMethod("getFileForUri", Ur+ getFileForUri.setAccessible(true);+ Object invoke1 = getFileForUri.invoke(invoke, uri);+ if (invoke1 instanceof File) {+ return ((File) invoke1).getAbsolutePath();+ }+ } else {+ Log.e(LOG_TAG, "providerUri2Path, invoke is null.");+ }+ } catch (Exception e) {+ Log.e(LOG_TAG, e.getMessage());+ }+ break;+ }+ }+ }+ }+ } else {+ Log.w(LOG_TAG, "providerUri2Path, packs is null.");+ }+ } catch (Exception e) {+ Log.e(LOG_TAG, e.getMessage());+ }++ return "";+ }++ private boolean isSilentInstall(Intent intent, String path) {+ if (!TextUtils.isEmpty(path)) {+ if (intent.getBooleanExtra(EXTRA_SILENT_INSTALL, false)) {+ Log.i(LOG_TAG, "isSilentInstall, Intent include EXTRA_SILENT_INSTALL.");+ return true;++ } else {+ String value = SystemProperties.get("ro.silentinstallapps", "");+ if (!TextUtils.isEmpty(value)) {+ if (TextUtils.equals(value, "all")) {+ Log.i(LOG_TAG, "isSilentInstall, All.");+ return true;++ } else {+ File sourceFile = new File(path);+ PackageParser.Package parsed = PackageUtil.getPackageInfo(this, sourceFile);+ if (parsed != null) {+ PackageInfo pkgInfo = PackageParser.generatePackageInfo(parsed, null,+ PackageManager.GET_PERMISSIONS, 0, 0, null,+ new PackageUserState());+ if (pkgInfo != null) {+ if (TextUtils.equals(value, "system")) {+ if (TextUtils.equals(pkgInfo.sharedUserId, "android.uid.system")) {+ Log.i(LOG_TAG, "isSilentInstall, System.");+ return true;+ }++ } else {+ String[] pkgNames = value.split(",");+ if (pkgNames != null && pkgNames.length > 0) {+ for (String pkgName : pkgNames) {+ if (TextUtils.equals(pkgName, pkgInfo.packageName)) {+ Log.i(LOG_TAG, "isSilentInstall, Included in the whitelist.");+ return true;+ }+ }+ }+ }+ }+ }+ }+ }+ }+ } else {+ Log.w(LOG_TAG, "isSilentInstall, path is null.");+ }++ return false;+ }++ class PackageInstallObserver extends IPackageInstallObserver.Stub {++ @Override+ public void packageInstalled(String packageName, int returnCode) throws RemoteException {+ Log.i(LOG_TAG, packageName + " silent installed.");+ }+ } }设置指定包名走静默安装
支持通过属性设置必要静默安装的APP包名,只要是属性设置的包名就走静默安装,别的APP走默认安装。这个操纵由系统端设置,APP端按Android标准API调应用安装即可。设置参考:
ro.silentinstallapps=com.ayst.sample1,com.ayst.sample1留意 :支持同时设置多个包名,包名之间用逗号隔开。
设置全部APP走静默安装
全部APP都走静默安装。
ro.silentinstallapps=all设置系统APP走静默安装
仅系统uid的APP走静默安装,别的APP走默认安装。
ro.silentinstallapps=system指定Intent参数走静默安装
通过Intent参数指定是否要静默安装。使用方法如下:
intent.putExtra("silent_install", true); // 静默安装完备参考:
private static final String EXTRA_SILENT_INSTALL = "silent_install";public static void install(Context context, String path) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { installO(context, path); } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { installN(context, path); } else { installOther(context, path); }}/** * android1.x-6.x * * @param context Context * @param path Package */private static void installOther(Context context, String path) { Intent install = new Intent(Intent.ACTION_VIEW); install.setDataAndType(Uri.parse("file://" + path), "application/vnd.android.package-archive"); install.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); install.putExtra(EXTRA_SILENT_INSTALL, true); // 静默安装 context.startActivity(install);}/** * android7.x * * @param context Context * @param path Package */private static void installN(Context context, String path) { Uri apkUri = FileProvider.getUriForFile(context, AUTHORITY, new File(path)); 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"); install.putExtra(EXTRA_SILENT_INSTALL, true); // 静默安装 context.startActivity(install);}/** * android8.x * * @param context Context * @param path Package */@RequiresApi(api = Build.VERSION_CODES.O)private static void installO(Context context, String path) { boolean isGranted = context.getPackageManager().canRequestPackageInstalls(); if (isGranted) { installN(context, path); } else { Dialog dialog = new AlertDialog.Builder(context.getApplicationContext()) .setTitle("Unknown sources") .setPositiveButton("OK", new DialogInterface.OnClickListener() { public void onClick(DialogInterface d, int w) { Intent intent = new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES); context.startActivity(intent); } }).create(); dialog.setCancelable(false); dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT); dialog.show(); }}https://www.yuque.com/aiyinsitan-dhjkq/android-system/fngm5h
|