Android静默安装的两种方案

程序员 2024-9-19 01:15:14 117 0 来自 中国
一些产物要求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
您需要登录后才可以回帖 登录 | 立即注册

Powered by CangBaoKu v1.0 小黑屋藏宝库It社区( 冀ICP备14008649号 )

GMT+8, 2024-11-25 02:59, Processed in 0.189436 second(s), 32 queries.© 2003-2025 cbk Team.

快速回复 返回顶部 返回列表