Android关于应用升级

作者: 浅吟且行的时光 | 来源:发表于2019-07-05 17:19 被阅读13次

    很多时候开发的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);
    

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


    安装界面.png

    无法自动安装

    • 使用辅助服务AccessibilityService可以模拟操作
    public class MyInstallAccessibilityService extends AccessibilityService {
    
        Map<Integer, Boolean> 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盘挂载的广播
    <receiver android:name=".common.MyReceiver">
                <intent-filter android:priority="1000">
                    <category android:name="android.intent.category.LAUNCHER" />
    
                    <action android:name="android.intent.action.MEDIA_MOUNTED" />
                    <action android:name="android.intent.action.MEDIA_UNMOUNTED" />
                    <action android:name="android.intent.action.MEDIA_REMOVED" />
    
                    <data android:scheme="file" />
                </intent-filter>
    </receiver>
    
    • 收到已挂载的广播后,弹出显示文件浏览的界面,选择安装即可

    4.其他升级方法

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

    5.总结

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

    相关文章

      网友评论

        本文标题:Android关于应用升级

        本文链接:https://www.haomeiwen.com/subject/nllqhctx.html