Android关于应用升级

很多时候开发的app运行在定制过的设备上,不需要适配各种各样的系统版本,但是往往没有外网连接,应用作为系统的桌面,一直保持运行。这时应用通常选择本机安装和远程升级,以下主要分析用到的关键技术点。

1.静默安装(系统ROOT的情况下)

  • 接收到升级包后可以进行静默安装
/**
     * install slient
     *
     * @param context
     * @param filePath
     * @return 0 means normal, 1 means file not exist, 2 means other exception error
     */
    public static int installSlient(Context context, String filePath) {
        File file = new File(filePath);
        if (filePath == null || filePath.length() == 0 || (file = new File(filePath)) == null || file.length() <= 0
                || !file.exists() || !file.isFile()) {
            return 1;
        }

        String[] args = {"pm", "install", "-r", filePath};
        ProcessBuilder processBuilder = new ProcessBuilder(args);

        Process process = null;
        BufferedReader successResult = null;
        BufferedReader errorResult = null;
        StringBuilder successMsg = new StringBuilder();
        StringBuilder errorMsg = new StringBuilder();
        int result;
        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);
            }
        } catch (IOException e) {
            e.printStackTrace();
            result = 2;
        } catch (Exception e) {
            e.printStackTrace();
            result = 2;
        } finally {
            try {
                if (successResult != null) {
                    successResult.close();
                }
                if (errorResult != null) {
                    errorResult.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            if (process != null) {
                process.destroy();
            }
        }

        // TODO should add memory is not enough here
        if (successMsg.toString().contains("Success") || successMsg.toString().contains("success")) {
            result = 0;
        } else {
            result = 2;
        }
        Log.d("installSlient", "successMsg:" + successMsg + ", ErrorMsg:" + errorMsg);
        return result;
    }
  • 安装之后保证新的应用自动启动起来
    注册系统广播监听安装完成并启动:
public class MyReceiver extends BroadcastReceiver {

    private static final String PACKAGE_ID = "mi.com.demo";

    @Override
    public void onReceive(Context context, Intent intent) {

        if (intent.getAction() == null){
            return;
        }

        if (intent.getAction().equals(Intent.ACTION_PACKAGE_REMOVED)) {
            if (intent.getData() == null){
                return;
            }
            String packageName = intent.getData().getSchemeSpecificPart();
            Log.e("MyReceiver","卸载成功"+packageName);

        }
        if (intent.getAction().equals(Intent.ACTION_PACKAGE_REPLACED)) {
            String packageName = intent.getData().getSchemeSpecificPart();
            Log.e("MyReceiver","替换成功"+packageName);

            if (packageName.equals(PACKAGE_ID)){

                Intent newIntent;
                PackageManager packageManager = context.getPackageManager();
                newIntent = packageManager.getLaunchIntentForPackage(packageName);
                if (newIntent == null){
                    return;
                }
                newIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED | Intent.FLAG_ACTIVITY_CLEAR_TOP) ;
                context.startActivity(newIntent);
                Log.e("MyReceiver","start success !");
            }
        }

    }
}
  • 但是应用在升级的过程中把原来的应用进程完全删了,所以不会收到系统广播,这时做法是:把接收广播的程序放到一个单独的应用中,并且在每次升级前检查此应用是否启动运行

2.(智能安装)系统在未ROOT的情况下

  • 准确来说系统在未ROOT的情况下实现的不是真正意义上的静默安装,而是自动安装
  • 安装方法:
        String apkPath = "";
        Uri uri = Uri.fromFile(new File(apkPath));
        Intent localIntent = new Intent(Intent.ACTION_VIEW);
        localIntent.setDataAndType(uri, "application/vnd.android.package-archive");
        startActivity(localIntent);

执行后就出现了下面界面:


Android关于应用升级_第1张图片
安装界面.png

无法自动安装

  • 使用辅助服务AccessibilityService可以模拟操作
public class MyInstallAccessibilityService extends AccessibilityService {

    Map handledMap = new HashMap<>();

    public static boolean isStop = false;

    @Override
    public void onAccessibilityEvent(AccessibilityEvent event) {
        Log.e("TAG","onAccessibilityEvent");
        if (isStop){
            return;
        }
        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)
                        || "完成".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
    protected void onServiceConnected() {
        super.onServiceConnected();
        Log.e("TAG","onServiceConnected");
    }
}
  • 这个辅助服务同样要放在一个独立的应用内,否则自动安装好后把原来应用清除了,无法再执行打开新应用的操作
  • 由于辅助服务手动打开才能用,所以检测到未开启的情况下提示用户打开,这就要两个应用之间可以相互通信,推荐使用AIDL进行应用间通信,每次升级前确保AccessibilityService是开启状态

3.关于U盘安装

  • 由于应用会作为系统的桌面,使用USB进行应用升级时要回到系统桌面找到文件浏览器读取安装包,这样的体验不太好,较好的办法:应用监听系统U盘挂载的广播

            
                

                
                
                

                
            

  • 收到已挂载的广播后,弹出显示文件浏览的界面,选择安装即可

4.其他升级方法

  • web升级app,app作为服务端给给前端上传网页
  • pc升级app,好处可以使用广播查询app,不用进行IP输入,但通信交互比web升级稍微复杂

5.总结

  • 静默升级应用最好在系统root,或能用系统签名打包,再或者提供sdk支持静默安装时使用,否则最好不用AccessibilityService。因为像华为,小米等定制过的系统,当清除后台时,辅助服务被关闭了,总会提示用户打开,体验不好,在一些原生系统测试(Android5.1)辅助服务开启后一直保持开启,开关机不受影响,除非应用卸载重装。
  • 至于选哪种升级方法,要根据实际情况进行选则,比如项目中已经有一套web后台配置系统,这时没必要再写一个别的单独软件进行升级。

你可能感兴趣的:(Android关于应用升级)