Android进阶之路 - apk静默安装、卸载

关于apk静默安装、卸载的方式,网上有很多类似的文章,包含内容都基本一致,我之所以还会写这样的Blog,只是想减少你不必要浪费的时间 ~

apk相关

你是不是尝试了N种方法,打了N个debug,然后得到的却是各种各样的安装失败 ~

Look Here : 首先类似静默功能一般是被系统所禁止的,只有厂商在自已平台才会开发权限(好比小米的系统应用,默认实现了静默功能,但是如果小米应用移植到vivo就无效了)!
Look Here : 如果你的应用不是系统应用!
Look Here : 如果使用的设备也没有root过!
那么:就不必要看下面的实现方式了,因为写了也白写,系统压根没有为你开放权限!

基础配置

关于静默安装的方式

  • shell 调用 pm 命令
  • 反射调用 PackageManager 的 install 方法(9.0后方式变更)

AndroidMainfest权限配置

    <!--读写外部存储权限-->
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <!--允许装载和卸载文件系统权限-->
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <!-- 安装应用的权限(8.0及以上会需要用户手动打开允许安装未知应用的权限,但在provider_paths的配置路径里,可以配置不需要用户手动打开权限,也可跳转自动安装)-->
    <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"
        tools:ignore="ProtectedPermissions" />
    <!--应用卸载权限-->
    <uses-permission android:name="permission.REQUEST_DELETE_PACKAGES" />
    <uses-permission
        android:name="android.permission.DELETE_PACKAGES"
        tools:ignore="ProtectedPermissions" />
                
    <!--以下为应用监听静默安装成功/失败的广播,看需求实现-->
    <receiver
        android:name=".InstallResultReceiver"
        android:enabled="true"
        android:exported="true">
        <intent-filter>
            <action android:name="android.content.pm.extra.STATUS"/>
        </intent-filter>
    </receiver>
    <receiver
        android:name=".UnInstallResultReceiver"
        android:enabled="true"
        android:exported="true">
        <intent-filter>
            <action android:name="android.content.pm.extra.STATUS"/>
        </intent-filter>
    </receiver>

9.0前 静默安装

新手注意,要玩好最好要适配6.0、7.0

采用pm命令,目前支持5.0 - 7.0 (8.0不确定,9.0肯定不行)

  • 5.0 的指令:pm install -r [路径/sdcard/demo.apk]
  • 7.0 的指令:pm install -i [包名:com.demo.insatll] [路径/sdcard/demo.apk]

封装方法 installApk(方法实现相同,选其一即可)

    public static boolean installApk(String apkPath) {
        //7.0String [ ] args = { "pm" , "install" , "-i" , "com.example", apkPath} ;
        //7.0用这个,参考的博客说要加 --user,但是我发现使用了反而不成功。
        String[] args = {"pm", "install", "-r", apkPath};
        ProcessBuilder processBuilder = new ProcessBuilder(args);

        Process process = null;
        BufferedReader successResult = null;
        BufferedReader errorResult = null;
        StringBuilder successMsg = new StringBuilder();
        StringBuilder errorMsg = new StringBuilder();
        try {
            process = processBuilder.start();
            successResult = new BufferedReader(new InputStreamReader(process.getInputStream()));
            errorResult = new BufferedReader(new InputStreamReader(process.getErrorStream()));
            String s;
            while ((s = successResult.readLine()) != null) {
                successMsg.append(s);
            }
            while ((s = errorResult.readLine()) != null) {
                errorMsg.append(s);
            }
            return process.waitFor() == 0 || successMsg.toString().contains("Success");
        } catch (IOException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            try {
                if (successResult != null) {
                    successResult.close();
                }
                if (errorResult != null) {
                    errorResult.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            if (process != null) {
                process.destroy();
            }
        }
        return false;
    }

封装方法 startInstall(方法实现相同,选其一即可)

    /** 
    *适配android9.0之前的安装方法
    */
    private void startInstall(String path) {
        Log.e("install apk",path);
        Process process = null;
        BufferedReader successResult = null;
        BufferedReader errorResult = null;
        StringBuilder successMsg = new StringBuilder();
        StringBuilder errorMsg = new StringBuilder();
        try {
            /**
             * 7.0以后版本需要额外添加
             * 包名需自行填入,path一般为apk路径
             * process = new ProcessBuilder("pm", "install", "-i", "项目包名", "-r",path).start();
            * */
            process = new ProcessBuilder("pm", "install", "-i", "当前应用包名", "-r", "sdcard/xxx.apk").start();
            successResult = new BufferedReader(new InputStreamReader(process.getInputStream()));
            errorResult = new BufferedReader(new InputStreamReader(process.getErrorStream()));
            String s;
            while ((s = successResult.readLine()) != null) {
                successMsg.append(s);
            }
            while ((s = errorResult.readLine()) != null) {
                errorMsg.append(s);
            }
        } catch (Exception e) {
            Log.e("install", "Exception " + e.toString());
        } finally {
            try {
                if (successResult != null) {
                    successResult.close();
                }
                if (errorResult != null) {
                    errorResult.close();
                }
            } catch (Exception e) {
                Log.e("install", "Exception " + e.getMessage());
            }
            if (process != null) {
                process.destroy();
            }
        }
        Log.e("install", "errorMsg " + errorMsg.toString());
        Log.d("install", "successMsg " + successMsg.toString());
    }

9.0适配 - 正常实现

Android 9.0之后采用PackageManager的getPackageInstaller接口,返回PackageInstaller;

以前有前辈查看源码时发现普通应用发起安装时都会通过Intent,这个时候就会被系统的一个应用拦截,这个就是安装界面系统源码路径的packages/apps/PackageInstaller类,它提供了安装、更新以及卸载等功能,其中包括单 APK 和多 APK 安装 ~

其中InstallInstalling.java这个类里面主要实现安装逻辑代码,关于Session主要是监听应用安装状态(个人感觉非必要实现),还有通过广播监听系统是否安装的方式(看个人需求,考虑是否实现) ~

安装流程:通过 IO 流的方式向 Session 内输送 apk 数据;Andorid9.0中的PackageInsatller 对于安装结果回调没有采用普通的函数回调(InstallSessionCallback),而是采用 Intent 的方式完成回调,比较常见的就是广播了

  • 通过广播接收Intent安装状态,主要获取PackageInstaller.EXTRA_STATUS的Value值,常见的有STATUS_PENDING_USER_ACTIONSTATUS_SUCCESSSTATUS_FAILURESTATUS_FAILURE_ABORTEDSTATUS_FAILURE_BLOCKEDSTATUS_FAILURE_CONFLICTSTATUS_FAILURE_INCOMPATIBLESTATUS_FAILURE_INVALID,和 STATUS_FAILURE_STORAGE
  • 关于安装、卸载的Recriver,其实接收的是同一个android.content.pm.extra.STATUS广播,在下文实现中创建了俩个Receiver,内部实现也一致,开发者如嫌麻烦可使用同一广播进行接收 ~

InstallSessionCallback 回调相关

初始化 - 注册

  InstallSessionCallback mSessionCallback = new InstallSessionCallback();
  getPackageManager().getPackageInstaller().registerSessionCallback(mSessionCallback);

InstallSessionCallback

    private PackageInstaller.SessionCallback mSessionCallback;
    private class InstallSessionCallback extends PackageInstaller.SessionCallback {
        @Override
        public void onCreated(int sessionId) {
            Log.e("apk install", "onCreated()");
        }

        @Override
        public void onBadgingChanged(int sessionId) {
            Log.e("apk install", "onBadgingChanged()");
        }

        @Override
        public void onActiveChanged(int sessionId, boolean active) {
            Log.e("apk install", "onActiveChanged()");
        }

        @Override
        public void onProgressChanged(int sessionId, float progress) {
            if (sessionId == mSessionId) {
                int progres = (int) (Integer.MAX_VALUE * progress);
            }
        }

        @Override
        public void onFinished(int sessionId, boolean success) {
            // empty, finish is handled by InstallResultReceiver
            if (mSessionId == sessionId) {
                if (success) {
                    Log.e("apk install", "onFinished() 安装成功");
                } else {
                    Log.e("apk install", "onFinished() 安装失败");
                }

            }
        }
    }
静默卸载

动态发送广播,监听安装状态,非必要实现,看个人需求

    /**
     * 根据包名卸载应用
     * @param packageName
     */
    public void uninstall(String packageName) {
        Intent broadcastIntent = new Intent(this, UnInstallResultReceiver.class);
        PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 1,
                broadcastIntent, PendingIntent.FLAG_UPDATE_CURRENT);
        PackageInstaller packageInstaller = getPackageManager().getPackageInstaller();
        packageInstaller.uninstall(packageName, pendingIntent.getIntentSender());
    }

UnInstallResultReceiver

    public class UnInstallResultReceiver extends BroadcastReceiver {
        private static final String TAG = "MainActivity";

        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            Log.e("unInstall", "已收到卸载反馈广播");
            //安装广播
            if (intent != null) { 
                final int status = intent.getIntExtra(PackageInstaller.EXTRA_STATUS,
                        PackageInstaller.STATUS_FAILURE);
                if (status == PackageInstaller.STATUS_SUCCESS) {
                    Log.d(TAG, "APP UnInstall Success!");
                    // InstallAPP.getInstance().sendInstallSucces();
                } else {
                    String msg = intent.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE);
                    Log.e(TAG, "UnInstall FAILURE status_massage" + msg);
                    //InstallAPP.getInstance().sendFailure(msg);
                }
            }
        }
    }
静默安装
    /**
     * 适配android9的安装方法。
     * 全部替换安装
     * @return
     */
    private int mSessionId = -1;
    public Boolean installApk(String apkFilePath) {
        File apkFile = new File(apkFilePath);
        if (!apkFile.exists()) {
            return null;
        }
        PackageInfo packageInfo = context.getPackageManager().getPackageArchiveInfo(apkFilePath, PackageManager.GET_ACTIVITIES | PackageManager.GET_SERVICES);
        if (packageInfo != null) {
            String packageName = packageInfo.packageName;
            int versionCode = packageInfo.versionCode;
            String versionName = packageInfo.versionName;
            Log.e("ApkActivity", "packageName=" + packageName + ", versionCode=" + versionCode + ", versionName=" + versionName);
        }

        PackageInstaller packageInstaller = context.getPackageManager().getPackageInstaller();
        PackageInstaller.SessionParams sessionParams
                = new PackageInstaller.SessionParams(PackageInstaller
                .SessionParams.MODE_FULL_INSTALL);
        sessionParams.setSize(apkFile.length());
        try {
            mSessionId = packageInstaller.createSession(sessionParams);
        } catch (IOException e) {
            e.printStackTrace();
        }
        if (mSessionId != -1) {
            boolean copySuccess = onTransfersApkFile(apkFilePath);
            if (copySuccess) {
                execInstallAPP();
            }
        }
        return null;
    }

    /**
     * 通过文件流传输apk
     * @param apkFilePath
     * @return
     */
    private boolean onTransfersApkFile(String apkFilePath) {
        InputStream in = null;
        OutputStream out = null;
        PackageInstaller.Session session = null;
        boolean success = false;
        try {
            File apkFile = new File(apkFilePath);
            session = context.getPackageManager().getPackageInstaller().openSession(mSessionId);
            Log.e("ApkActivity","apkFile:"+apkFilePath);
            Log.e("ApkActivity","apkFile:"+apkFile.getName());
            //看别人传的是base.apk;个人建议如果你应用被授权了,base.apk无效后,建议更换为传入的apk路径
            out = session.openWrite("base.apk", 0, apkFile.length());
            in = new FileInputStream(apkFile);
            int total = 0, c;
            byte[] buffer = new byte[1024 * 1024];
            while ((c = in.read(buffer)) != -1) {
                total += c;
                out.write(buffer, 0, c);
            }
            session.fsync(out);
            success = true;
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (null != session) {
                session.close();
            }
            try {
                if (null != out) {
                    out.close();
                }
                if (null != in) {
                    in.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return success;
    }
    
    /**
     * 执行安装并通知安装结果
     */
    private void execInstallAPP() {
        PackageInstaller.Session session = null;
        try {
            Log.e("ApkActivity","mSessionId:"+mSessionId);
            session = context.getPackageManager().getPackageInstaller().openSession(mSessionId);
            Intent intent = new Intent(context, InstallResultReceiver.class);
            PendingIntent pendingIntent = PendingIntent.getBroadcast(context,1, intent,PendingIntent.FLAG_UPDATE_CURRENT);
            session.commit(pendingIntent.getIntentSender());
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (null != session) {
                session.close();
            }
        }
    }

监听安装状态,非必要实现,看个人需求

public class InstallResultReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        Log.d("install", "已收到安装反馈广播 action:" + action);
        if (intent != null) { //安装的广播
            final int status = intent.getIntExtra(PackageInstaller.EXTRA_STATUS,
                    PackageInstaller.STATUS_FAILURE);
            if (status == PackageInstaller.STATUS_SUCCESS) {
                // success
                Log.d(TAG, "APP Install Success!");
                // InstallAPP.getInstance().sendInstallSucces();
            } else {
                String msg = intent.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE);
                Log.e(TAG, "Install FAILURE status_massage" + msg);
                //InstallAPP.getInstance().sendFailure(msg);
            }
        }
    }
}

9.0适配 - 工具封装

此方式来源于一位简书的作者

AndroidP

/**
 * Copyright (C), 2018-2019
 * Author: ziqimo
 * Date: 2019-12-31 14:55
 * Description:
 * History:
 * <author> <time> <version> <desc>
 * 作者姓名 修改时间 版本号 描述
 */
public class AndroidP {


    private static final String TAG = AndroidP.class.getSimpleName();

    // 适配android9的安装方法。
    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    public void install28(Context context, String apkFilePath, PackageManager packageManager) {
        File apkFile = new File(apkFilePath);
        PackageInstaller packageInstaller = packageManager.getPackageInstaller();
        PackageInstaller.SessionParams sessionParams = new PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL);
        sessionParams.setSize(apkFile.length());

        int sessionId = createSession(packageInstaller, sessionParams);
        if (sessionId != -1) {
            boolean copySuccess = copyInstallFile(packageInstaller, sessionId, apkFilePath);
            if (copySuccess) {
                execInstallCommand(context, packageInstaller, sessionId);
            }
        }
    }

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    private int createSession(PackageInstaller packageInstaller,
                              PackageInstaller.SessionParams sessionParams) {
        int sessionId = -1;
        try {
            sessionId = packageInstaller.createSession(sessionParams);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return sessionId;
    }

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    private boolean copyInstallFile(PackageInstaller packageInstaller,
                                    int sessionId, String apkFilePath) {
        InputStream in = null;
        OutputStream out = null;
        PackageInstaller.Session session = null;
        boolean success = false;
        try {
            File apkFile = new File(apkFilePath);
            session = packageInstaller.openSession(sessionId);
            out = session.openWrite("base.apk", 0, apkFile.length());
            in = new FileInputStream(apkFile);
            int total = 0, c;
            byte[] buffer = new byte[65536];
            while ((c = in.read(buffer)) != -1) {
                total += c;
                out.write(buffer, 0, c);
            }
            session.fsync(out);
            Log.i(TAG, "streamed " + total + " bytes");
            success = true;
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            closeQuietly(out);
            closeQuietly(in);
            closeQuietly(session);
        }
        return success;
    }

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    private void execInstallCommand(Context context, PackageInstaller packageInstaller, int sessionId) {
        PackageInstaller.Session session = null;
        try {
            session = packageInstaller.openSession(sessionId);
            Intent intent = new Intent(context, InstallResultReceiver.class);
            PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 1, intent, PendingIntent.FLAG_UPDATE_CURRENT);
            session.commit(pendingIntent.getIntentSender());
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            closeQuietly(session);
        }
    }
}
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInstaller;

public class InstallResultReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        if (intent != null) {
            final int status = intent.getIntExtra(PackageInstaller.EXTRA_STATUS,
                    PackageInstaller.STATUS_FAILURE);
            if (status == PackageInstaller.STATUS_SUCCESS) {
                // success
            } else {
                //Log.e(TAG, intent.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE));
            }
        }
    }
}

IoUtils

import java.io.Closeable;
import java.io.IOException;
import java.net.Socket;

public final class IoUtils {
    public static void closeQuietly(Closeable c) {
        if (c != null) {
            try {
                c.close();
            } catch (IOException ignored) {
                ignored.printStackTrace();
            }
        }
    }

    public static void closeQuietly(Socket c) {
        if (c != null) {
            try {
                c.close();
            } catch (IOException ignored) {
                ignored.printStackTrace();
            }
        }
    }
}

兴趣爱好 - 别人研究9.0的framework真实源码整理出来的

public class PackageManagerCompatP {

    private final static String TAG = PackageManagerCompatP.class.getSimpleName();


    public static final long MAX_WAIT_TIME = 25 * 1000;

    public static final long WAIT_TIME_INCR = 5 * 1000;

    private static final String SECURE_CONTAINERS_PREFIX = "/mnt/asec";

    private Context mContext;

    public PackageManagerCompatQ(Context context) {
        this.mContext = context;
    }


    private static class LocalIntentReceiver {
        private final SynchronousQueue<Intent> mResult = new SynchronousQueue<>();

        private IIntentSender.Stub mLocalSender = new IIntentSender.Stub() {
            @Override
            public void send(int code, Intent intent, String resolvedType, IBinder whitelistToken,
                             IIntentReceiver finishedReceiver, String requiredPermission, Bundle options) {
                try {
                    mResult.offer(intent, 5, TimeUnit.SECONDS);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        };

        public IntentSender getIntentSender() {
            Class<?> aClass = null;
            try {
                aClass = Class.forName("android.content.IntentSender");
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
            if (aClass == null) {
                return null;
            }
            try {
                Constructor<?>[] declaredConstructors = aClass.getDeclaredConstructors();
                for (Constructor<?> declaredConstructor : declaredConstructors) {
                    Log.i(TAG, "declaredConstructor.toString():" + declaredConstructor.toString());
                    Log.i(TAG, "declaredConstructor.getName():" + declaredConstructor.getName());
                    Class<?>[] parameterTypes = declaredConstructor.getParameterTypes();
                    for (Class<?> parameterType : parameterTypes) {
                        Class aClass1 = parameterType.getClass();
                        Log.i(TAG, "parameterTypes...aClass1:" + aClass1.getName());
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            Constructor constructor = null;
            try {
                constructor = aClass.getDeclaredConstructor(IIntentSender.class);
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            }
            if (constructor == null) {
                return null;
            }
            Object o = null;
            try {
                o = constructor.newInstance((IIntentSender) mLocalSender);
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
            return (IntentSender) o;
//                 new IntentSender((IIntentSender) mLocalSender)
        }

        public Intent getResult() {
            try {
                return mResult.take();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }

    private PackageManager getPm() {
        return mContext.getPackageManager();
    }

    private PackageInstaller getPi() {
        return getPm().getPackageInstaller();
    }

    private void writeSplitToInstallSession(PackageInstaller.Session session, String inPath,
                                            String splitName) throws RemoteException {
        long sizeBytes = 0;
        final File file = new File(inPath);
        if (file.isFile()) {
            sizeBytes = file.length();
        } else {
            return;
        }

        InputStream in = null;
        OutputStream out = null;
        try {
            in = new FileInputStream(inPath);
            out = session.openWrite(splitName, 0, sizeBytes);

            int total = 0;
            byte[] buffer = new byte[65536];
            int c;
            while ((c = in.read(buffer)) != -1) {
                total += c;
                out.write(buffer, 0, c);
            }
            session.fsync(out);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            IoUtils.closeQuietly(out);
            IoUtils.closeQuietly(in);
            IoUtils.closeQuietly(session);
        }
    }

    /**
     * 入口方法
     * String apkPackageName = ""; //填写安装的包名
     * String apkPath = "";//填写安装的路径
     **/
    public void testReplaceFlagSdcardInternal(String apkPackageName, String apkPath) throws Exception {
        // Do not run on devices with emulated external storage.
        if (Environment.isExternalStorageEmulated()) {
            return;
        }

        int iFlags = 0x00000008;// PackageManager.INSTALL_EXTERNAL 0x00000008
        int rFlags = 0;
        //这个暂时用不上
        //InstallParams ip = sampleInstallFromRawResource(iFlags, false);
        Uri uri = Uri.fromFile(new File(apkPath));
        GenericReceiver receiver = new ReplaceReceiver(apkPackageName);
        int replaceFlags = rFlags | 0x00000002;//PackageManager.INSTALL_REPLACE_EXISTING 0x00000002
        try {
            invokeInstallPackage(uri, replaceFlags, receiver, true);
            //assertInstall(ip.pkg, iFlags, ip.pkg.installLocation);
        } catch (Exception e) {
            Log.e(TAG, "Failed with exception : " + e);
        } finally {
//            cleanUpInstall(ip);
        }
    }

//    class InstallParams {
//        Uri packageURI;
//
//        PackageParser.Package pkg;
//
//        InstallParams(String outFileName, int rawResId) throws PackageParserException {
//            this.pkg = getParsedPackage(outFileName, rawResId);
//            this.packageURI = Uri.fromFile(new File(pkg.codePath));
//        }
//
//        InstallParams(PackageParser.Package pkg) {
//            this.packageURI = Uri.fromFile(new File(pkg.codePath));
//            this.pkg = pkg;
//        }
//
//        long getApkSize() {
//            File file = new File(pkg.codePath);
//            return file.length();
//        }
//    }
//
//    private InstallParams sampleInstallFromRawResource(int flags, boolean cleanUp)
//            throws Exception {
//        return installFromRawResource("install.apk", android.R.raw.install, flags, cleanUp, false, -1,
//                PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
//    }

//    private void cleanUpInstall(InstallParams ip) {
//
//    }


    private void cleanUpInstall(String pkgName) throws Exception {
        if (pkgName == null) {
            return;
        }
        Log.i(TAG, "Deleting package : " + pkgName);
        try {
            final ApplicationInfo info = getPm().getApplicationInfo(pkgName,
                    PackageManager.MATCH_UNINSTALLED_PACKAGES);
            if (info != null) {
                //PackageManager.DELETE_ALL_USERS
                final LocalIntentReceiver localReceiver = new LocalIntentReceiver();
                //这是卸载,不调用
//                getPi().uninstall(pkgName,
//                        0x00000002,
//                        localReceiver.getIntentSender());
                localReceiver.getResult();
                assertUninstalled(info);
            }
        } catch (IllegalArgumentException | PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }
    }

    private static void assertUninstalled(ApplicationInfo info) throws Exception {
        File nativeLibraryFile = new File(info.nativeLibraryDir);
        Log.e(TAG, "Native library directory " + info.nativeLibraryDir
                + " should be erased" + nativeLibraryFile.exists());
    }

    private void invokeInstallPackage(Uri packageUri, int flags, GenericReceiver receiver,
                                      boolean shouldSucceed) {
        mContext.registerReceiver(receiver, receiver.filter);
        synchronized (receiver) {
            final String inPath = packageUri.getPath();
            PackageInstaller.Session session = null;
            try {
                final PackageInstaller.SessionParams sessionParams =
                        new PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL);
                try {
                    //sessionParams.installFlags = flags;
                    Field installFlags = sessionParams.getClass().getDeclaredField("installFlags");
                    installFlags.set(sessionParams, flags);
                } catch (NoSuchFieldException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
                final int sessionId = getPi().createSession(sessionParams);
                session = getPi().openSession(sessionId);
                writeSplitToInstallSession(session, inPath, "base.apk");
                final LocalIntentReceiver localReceiver = new LocalIntentReceiver();
                session.commit(localReceiver.getIntentSender());
                final Intent result = localReceiver.getResult();
                final int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
                        PackageInstaller.STATUS_FAILURE);
                if (shouldSucceed) {
                    if (status != PackageInstaller.STATUS_SUCCESS) {
                        Log.e(TAG, "Installation should have succeeded, but got code " + status);
                    }
                } else {
                    if (status == PackageInstaller.STATUS_SUCCESS) {
                        Log.e(TAG, "Installation should have failed");
                    }
                    // We'll never get a broadcast since the package failed to install
                    return;
                }
                // Verify we received the broadcast
                long waitTime = 0;
                while ((!receiver.isDone()) && (waitTime < MAX_WAIT_TIME)) {
                    try {
                        receiver.wait(WAIT_TIME_INCR);
                        waitTime += WAIT_TIME_INCR;
                    } catch (InterruptedException e) {
                        Log.i(TAG, "Interrupted during sleep", e);
                    }
                }
                if (!receiver.isDone()) {
                    Log.e(TAG, "Timed out waiting for PACKAGE_ADDED notification");
                }
            } catch (IllegalArgumentException | IOException | RemoteException e) {
                Log.e(TAG, "Failed to install package; path=" + inPath, e);
            } finally {
                IoUtils.closeQuietly(session);
                mContext.unregisterReceiver(receiver);
            }
        }
    }


    private abstract static class GenericReceiver extends BroadcastReceiver {
        private boolean doneFlag = false;
        boolean received = false;
        Intent intent;
        IntentFilter filter;

        abstract boolean notifyNow(Intent intent);

        @Override
        public void onReceive(Context context, Intent intent) {
            if (notifyNow(intent)) {
                synchronized (this) {
                    received = true;
                    doneFlag = true;
                    this.intent = intent;
                    notifyAll();
                }
            }
        }

        public boolean isDone() {
            return doneFlag;
        }

        public void setFilter(IntentFilter filter) {
            this.filter = filter;
        }
    }

    class ReplaceReceiver extends GenericReceiver {
        String pkgName;
        final static int INVALID = -1;
        final static int REMOVED = 1;
        final static int ADDED = 2;
        final static int REPLACED = 3;
        int removed = INVALID;
        // for updated system apps only
        boolean update = false;

        ReplaceReceiver(String pkgName) {
            this.pkgName = pkgName;
            filter = new IntentFilter(Intent.ACTION_PACKAGE_REMOVED);
            filter.addAction(Intent.ACTION_PACKAGE_ADDED);
            if (update) {
                filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
            }
            filter.addDataScheme("package");
            super.setFilter(filter);
        }

        public boolean notifyNow(Intent intent) {
            String action = intent.getAction();
            Uri data = intent.getData();
            String installedPkg = data.getEncodedSchemeSpecificPart();
            if (pkgName == null || !pkgName.equals(installedPkg)) {
                return false;
            }
            if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) {
                removed = REMOVED;
            } else if (Intent.ACTION_PACKAGE_ADDED.equals(action)) {
                if (removed != REMOVED) {
                    return false;
                }
                boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
                if (!replacing) {
                    return false;
                }
                removed = ADDED;
                if (!update) {
                    return true;
                }
            } else if (Intent.ACTION_PACKAGE_REPLACED.equals(action)) {
                if (removed != ADDED) {
                    return false;
                }
                removed = REPLACED;
                return true;
            }
            return false;
        }
    }
}

延伸 - 伪静默安装

为何说是伪? 因为这个其实并不是静默安装,而是郭霖大神早期的一款智能安装方法,这里主要记录下大致实现~

res/xml目录下新建accessibility_service_config.xml

<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
    android:packageNames="com.android.packageinstaller"
    android:description="@string/accessibility_service_description"
    android:accessibilityEventTypes="typeAllMask"
    android:accessibilityFlags="flagDefault"
    android:accessibilityFeedbackType="feedbackGeneric"
    android:canRetrieveWindowContent="true"
   />   

string.xml文件写入description指定内容

<resources>
    <string name="app_name">InstallTest</string>
    <string name="accessibility_service_description">智能安装服务,无需用户的任何操作就可以自动安装程序。</string>
</resources>

修改AndroidManifest.xml文件,在里面配置无障碍服务

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.installtest">
 
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
 
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        ......
 
        <service
            android:name=".MyAccessibilityService"
            android:label="我的智能安装"
            android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
            <intent-filter>
                <action android:name="android.accessibilityservice.AccessibilityService" />
            </intent-filter>
 
            <meta-data
                android:name="android.accessibilityservice"
                android:resource="@xml/accessibility_service_config" />
        </service>
    </application>
 
</manifest>

实现智能安装功能的具体逻辑了,创建一个MyAccessibilityService类并继承自AccessibilityService

/**
 * 智能安装功能的实现类。
 * 原文地址:http://blog.csdn.net/guolin_blog/article/details/47803149
 * @author guolin
 * @since 2015/12/7
 */
public class MyAccessibilityService extends AccessibilityService {
 
    Map<Integer, Boolean> handledMap = new HashMap<>();
 
    public MyAccessibilityService() {
    }
 
    @Override
    public void onAccessibilityEvent(AccessibilityEvent event) {
        AccessibilityNodeInfo nodeInfo = event.getSource();
        if (nodeInfo != null) {
            int eventType = event.getEventType();
            if (eventType== AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED ||
                    eventType == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
                if (handledMap.get(event.getWindowId()) == null) {
                    boolean handled = iterateNodesAndHandle(nodeInfo);
                    if (handled) {
                        handledMap.put(event.getWindowId(), true);
                    }
                }
            }
        }
    }
 
    private boolean iterateNodesAndHandle(AccessibilityNodeInfo nodeInfo) {
        if (nodeInfo != null) {
            int childCount = nodeInfo.getChildCount();
            if ("android.widget.Button".equals(nodeInfo.getClassName())) {
                String nodeContent = nodeInfo.getText().toString();
                Log.d("TAG", "content is " + nodeContent);
                if ("安装".equals(nodeContent)
                        || "完成".equals(nodeContent)
                        || "确定".equals(nodeContent)) {
                    nodeInfo.performAction(AccessibilityNodeInfo.ACTION_CLICK);
                    return true;
                }
            } else if ("android.widget.ScrollView".equals(nodeInfo.getClassName())) {
                nodeInfo.performAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
            }
            for (int i = 0; i < childCount; i++) {
                AccessibilityNodeInfo childNodeInfo = nodeInfo.getChild(i);
                if (iterateNodesAndHandle(childNodeInfo)) {
                    return true;
                }
            }
        }
        return false;
    }
 
    @Override
    public void onInterrupt() {
    }
 
}

MainActivity智能安装功能的调用

/**
 * 仿360手机助手秒装和智能安装功能的主Activity。
 * 原文地址:http://blog.csdn.net/guolin_blog/article/details/47803149
 * @author guolin
 * @since 2015/12/7
 */
public class MainActivity extends AppCompatActivity {
 
    ......
 
    public void onForwardToAccessibility(View view) {
        Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS);
        startActivity(intent);
    }
 
    public void onSmartInstall(View view) {
        if (TextUtils.isEmpty(apkPath)) {
            Toast.makeText(this, "请选择安装包!", Toast.LENGTH_SHORT).show();
            return;
        }
        Uri uri = Uri.fromFile(new File(apkPath));
        Intent localIntent = new Intent(Intent.ACTION_VIEW);
        localIntent.setDataAndType(uri, "application/vnd.android.package-archive");
        startActivity(localIntent);
    }
}

延伸 - 自启动

此方法未亲自尝试,仅当做一个参考方向

在真实场景中,我们一般都会接到静默安装app后,要求app自启动的需求,要么在自启动时因app已经退出安装,所以无法实现我们的诉求;有人就想着把framework层修改了,找到发广播的地方PackageInstallerService.java ~

自启动Blog

static class PackageInstallObserverAdapter extends PackageInstallObserver {
    ...
    @Override
        public void onPackageInstalled(String basePackageName, int returnCode, String msg,
                Bundle extras) {
            if (PackageManager.INSTALL_SUCCEEDED == returnCode && mShowNotification) {
                boolean update = (extras != null) && extras.getBoolean(Intent.EXTRA_REPLACING);
                Notification notification = buildSuccessNotification(mContext,
                        mContext.getResources()
                                .getString(update ? R.string.package_updated_device_owner :
                                        R.string.package_installed_device_owner),
                        basePackageName,
                        mUserId);
                if (notification != null) {
                    NotificationManager notificationManager = (NotificationManager)
                            mContext.getSystemService(Context.NOTIFICATION_SERVICE);
                    notificationManager.notify(basePackageName,
                            SystemMessage.NOTE_PACKAGE_STATE,
                            notification);
                }
            }
			//add by jueme for start customer app at 20200927
			if(basePackageName.equals("com.jueme.android.autoinstall")){
				new Thread(new Runnable(){
					@Override
					public void run(){
						try {
							Slog.d(TAG,"onPackageInstalled basePackageName "+basePackageName+" returnCode "+returnCode+" msg "+msg);
							//睡500ms是因为在测试过程中发现会概率性启动不了app,原因就是在还没有完全安装好就调用了启动的方法,加了延时之后就没发现问题了
							Thread.sleep(500);
							Intent intent = new Intent();
									intent.setClassName("com.jueme.android.autoinstall","com.jueme.android.autoinstall.MainActivity");
							intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
							mContext.startActivity(intent);
						} catch (Exception e) {
							Slog.e(TAG,"onPackageInstalled Exception "+e.toString());
						}
					}
				}).start();
			}
			//add end
			
            final Intent fillIn = new Intent();
            fillIn.putExtra(PackageInstaller.EXTRA_PACKAGE_NAME, basePackageName);
            fillIn.putExtra(PackageInstaller.EXTRA_SESSION_ID, mSessionId);
            fillIn.putExtra(PackageInstaller.EXTRA_STATUS,
                    PackageManager.installStatusToPublicStatus(returnCode));
            fillIn.putExtra(PackageInstaller.EXTRA_STATUS_MESSAGE,
                    PackageManager.installStatusToString(returnCode, msg));
            fillIn.putExtra(PackageInstaller.EXTRA_LEGACY_STATUS, returnCode);
            if (extras != null) {
                final String existing = extras.getString(
                        PackageManager.EXTRA_FAILURE_EXISTING_PACKAGE);
                if (!TextUtils.isEmpty(existing)) {
                    fillIn.putExtra(PackageInstaller.EXTRA_OTHER_PACKAGE_NAME, existing);
                }
            }
            try {
                mTarget.sendIntent(mContext, 0, fillIn, null, null);
            } catch (SendIntentException ignored) {
            }
        }
        ...
}

扩展 - 小方法
  • 9
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 10
    评论
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

远方那座山

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值