Android 9.0 静默安装实现

2,445 阅读2分钟

一、需求

最近需要对一个旧项目的SDK做版本Android 9.0的版本适配。其中有个静默安装的API在Android 5.1上可以正常运行但是在Android 9.0上出现了很多问题。

  • 问题1: 旧版本上引用了IPackageInstallObserver作为应用回调接口,然后在Android9.0上该回调接口已经被IPackageInstallObserver2替代。事实上我们可以自定义回调接口并不需要使用系统定义的IPackageInstallObserver。

  • 问题2: 在Android 5.1中安装应用直接调用PackageManager&installPackage即可实现,然而从Android7.0(api24)开始installPackage方法已经被PackageManager&installPacakageAsUser替换,而到了Android 9.0(api28)整改安装机制都发生变化。Android 9.0之后并没有提供一个单一的api来实现应用的安装installPacakageAsUser也已经不存在了。

二、原理

  • sdk_api<api24:直接调用PackageManager$installPackage实现应用安装。
public static void installPackage(Context ctx, String filePath, IPackageInstallObserver observer)
            throws RemoteException {
        PackageManager mPm = ctx.getPackageManager();
        Uri packageURI = Uri.fromFile(new File(filePath));
        mPm.installPackage(packageURI, observer, PackageManager.INSTALL_REPLACE_EXISTING, null);
    }
  • sdk_api>=api24:直接调用PackageManager&installPacakageAsUser实现应用安装
public static void installPackage(Context mContext, String filePath, IPackageInstallObserver2 observer)
            throws RemoteException {
        PackageManager mPm = ctx.getPackageManager();
        File apkFile = new File(filePath)
        mPm.installPackageAsUser(Uri.fromFile(apkFile).getPath(), observer, 2, apkFile.getName(), mContext.getUserId());
    }
  • sdk_api>=28:Android 9.0以后使用PackageInstaller进行应用安装。 通过PackageInstaller.Session将APK数据传递给PackageManagerService(PMS),PMS接收数据进行安装,安装完后将安装结果通过广播发送到上层应用。

三、实现

第一步:注册安装结果的广播:

public class SilencePackageHandleService extends ILakalaPackageHandler.Stub{
  private static final String BROADCAST_SENDER_PERMISSION =   "android.permission.INSTALL_PACKAGES";
  // 声明广播接收器
  private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            final int statusCode = intent.getIntExtra(
                    PackageInstaller.EXTRA_STATUS, PackageInstaller.STATUS_FAILURE);
            Log.d(TAG, "install status code:" + statusCode);
            try {
                String packageName = null;
                if (pkg != null) {
                    packageName = pkg.packageName;
                }
                // 回调安装结果,statusCode == 0:安装成功,否正安装失败
                observer2.onPackageInstalled(packageName, statusCode, null, null);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
    };
  public SilencePackageHandleService(Context context) {
        ServiceManager.addService("lakala_packageHandler", this.asBinder());
        ...
        // 注册广播
        IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction("com.install");
        mContext.registerReceiver(mBroadcastReceiver, intentFilter, BROADCAST_SENDER_PERMISSION, null);
    }
}

第二步:创建PackageInstaller.SessionParams

private void startInstall(String filePath) throws RemoteException {
        ...
        final PackageManager pm = mContext.getPackageManager();
        mPackageURI = Uri.fromFile(new File(filePath));
        final File sourceFile = new File(filePath);
        Log.d(TAG, "zxc startInstall: sourceFile:" + filePath);
        final PackageInstaller.SessionParams params = new PackageInstaller
                .SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL);
        params.originatingUid = UID_UNKNOWN;
        try {
            params.setInstallLocation(PackageParser.parsePackageLite(sourceFile, 0).installLocation);
            pkg = PackageParser.parsePackageLite(sourceFile, 0);
        } catch (PackageParser.PackageParserException e) {
            Log.e(TAG, "zxc startInstall: error...");
            e.printStackTrace();
            observer2.onPackageInstalled(null, PackageInstaller.STATUS_FAILURE, null, null);
            return;
        }
        // 开启线程传递apk
        mInstallHandler.post(new Runnable() {
            @Override
            public void run() {
                try {
                    doInstall(pm, params);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        });
    }

第三步:传递apk数据

private void doInstall(PackageManager pm, PackageInstaller.SessionParams params) throws RemoteException {
        PackageInstaller packageInstaller = pm.getPackageInstaller();
        int sessionId = 0;
        String packageLocation = mPackageURI.getPath();
        File file = new File(packageLocation);
        byte[] buffer = new byte[65536];
        InputStream in = null;
        OutputStream out = null;
        try {
            sessionId = packageInstaller.createSession(params);
            session = packageInstaller.openSession(sessionId);
            in = new FileInputStream(file);
            long sizeBytes = file.length();
            out = session.openWrite("PackageInstaller", 0, sizeBytes);
            session.setStagingProgress(0);
            int c;
            while ((c = in.read(buffer)) != -1) {
                out.write(buffer, 0, c);
                if (sizeBytes > 0) {
                    final float fraction = ((float) c / (float) sizeBytes);
                    session.addProgress(fraction);
                }
            }
            session.fsync(out);
            out.close(); // session.commit之前一定要关闭数据流否则会报错
            in.close();
            out = null;
            in = null;
            Intent broadcastIntent = new Intent("com.install"); // 新建intent用于安装结束发送广播
            PendingIntent pendingIntent = PendingIntent.getBroadcast(
                    mContext,
                    sessionId,
                    broadcastIntent,
                    PendingIntent.FLAG_UPDATE_CURRENT);
            session.commit(pendingIntent.getIntentSender()); // 提交数据 完成安装
        } catch (IOException e) {
            e.printStackTrace();
            observer2.onPackageInstalled(pkg.packageName, PackageInstaller.STATUS_FAILURE, null, null);
        } catch (Exception e) {
            e.printStackTrace();
            observer2.onPackageInstalled(pkg.packageName, PackageInstaller.STATUS_FAILURE, null, null);
        } finally {
            try {
                if (in != null) in.close();
                if (out != null) out.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            session.close();
        }
    }


四、参考链接

  1. www.jianshu.com/p/ec642ff5f…
  2. www.jianshu.com/p/27766c5a9…