进入目录视图点击置顶文章查看博客目录(全站式导航)

恪守本分,勿起躁念,脚踏实地,踏浪前行,坚持原创,宁缺勿滥,请点击文章左上角+号查看目录...

Android进阶——Small源码分析之更新流程详解

前言

Small的更新有两种方式,一种是将插件放在插件目录,一种是将插件放在补丁目录下。更新插件的方法可以通过以下思路进行更新,本篇主要是通过Sample的例子来请求更新补丁,更新插件的方式就给出代码。这里不建议直接更新插件的方式,因为你覆盖住插件的文件后,如果插件下载失败那么就会加载不成功,如果是下载补丁失败的话,找不到补丁的情况下,它还可以去原本的插件上进行加载,这样就不会导致程序崩溃的问题

下面是更新插件的思路和代码

  1. 将下载好的插件放进sdcard中
  2. 从sdcard将插件写入到Small指定的插件目录
  3. 设置加载插件的标志位
private void initPlug() {
    //设置加载插件的标志位
    Small.setLoadFromAssets(true);
    try {
        //将下载好的插件放进sdcard中
        File dstFile = new File(FileUtils.getInternalBundlePath(), "com.demo.small.update.app.upgrade.apk");
        if (!dstFile.exists()) {
            dstFile.createNewFile();
        }
        //从sdcard将插件写入到Small指定的插件目录
        File srcFile = new File(Environment.getExternalStorageDirectory().toString() + "/Small/" + "libcom_demo_small_update_app_upgrade.so");
        FileInputStream inputStream = new FileInputStream(srcFile);
        OutputStream outputStream = new FileOutputStream(dstFile);
        byte[] buffer = new byte[1024];
        int length;
        while ((length = inputStream.read(buffer)) != -1) {
            outputStream.write(buffer, 0, length);
        }
        outputStream.flush();
        outputStream.close();
        inputStream.close();
    } catch (Exception e) {
        e.printStackTrace();
    }
}

更新流程

更新流程主要以Sample的MainFragment为例子进行分析

一、requestUpgradeInfo()

主要作用:请求更新插件的信息

//补丁更新的入口
private void checkUpgrade() {
    new UpgradeManager(getContext()).checkUpgrade();
}

public void checkUpgrade() {
    mProgressDlg = ProgressDialog.show(mContext, "Small", "Checking for updates...");
    //1、Small.getBundleVersions():从SharedPreferences拿到补丁版本
    //2、requestUpgradeInfo():从服务器请求补丁的版本信息
    requestUpgradeInfo(Small.getBundleVersions(), new OnResponseListener() {
        @Override
        public void onResponse(UpgradeInfo info) {
            mProgressDlg.setMessage("Upgrading...");
            upgradeBundles(info,
                    new OnUpgradeListener() {
                        @Override
                        public void onUpgrade(boolean succeed) {
                            mProgressDlg.dismiss();
                            mProgressDlg = null;
                            String text = succeed ?
                                    "Upgrade Success! Switch to background and back to foreground to see changes."
                                    : "Upgrade Failed!";
                            Toast.makeText(mContext, text, Toast.LENGTH_SHORT).show();
                        }
                    });
        }
    });
}

private void requestUpgradeInfo(Map versions, OnResponseListener listener) {
    System.out.println(versions); // this should be passed as HTTP parameters
    mResponseHandler = new ResponseHandler(listener);
    new Thread() {
        @Override
        public void run() {
            try {
                // Example HTTP request to get the upgrade bundles information.
                // Json format see http://wequick.github.io/small/upgrade/bundles.json
                URL url = new URL("http://wequick.github.io/small/upgrade/bundles.json");
                HttpURLConnection conn = (HttpURLConnection) url.openConnection();
                StringBuilder sb = new StringBuilder();
                InputStream is = conn.getInputStream();
                byte[] buffer = new byte[1024];
                int length;
                while ((length = is.read(buffer)) != -1) {
                    sb.append(new String(buffer, 0, length));
                }

                // Parse json
                JSONObject jo = new JSONObject(sb.toString());
                JSONObject mf = jo.has("manifest") ? jo.getJSONObject("manifest") : null;
                JSONArray updates = jo.getJSONArray("updates");
                int N = updates.length();
                List<UpdateInfo> infos = new ArrayList<UpdateInfo>(N);
                for (int i = 0; i < N; i++) {
                    JSONObject o = updates.getJSONObject(i);
                    UpdateInfo info = new UpdateInfo();
                    info.packageName = o.getString("pkg");
                    info.downloadUrl = o.getString("url");
                    infos.add(info);
                }

                // Post message
                UpgradeInfo ui = new UpgradeInfo();
                ui.manifest = mf;
                ui.updates = infos;
                //3、请求到参数后保存起来,通过mResponseHandler通知更新
                Message.obtain(mResponseHandler, 1, ui).sendToTarget();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }.start();
}

接收的消息会回调接口进行更新

public void checkUpgrade() {
    mProgressDlg = ProgressDialog.show(mContext, "Small", "Checking for updates...");
    requestUpgradeInfo(Small.getBundleVersions(), new OnResponseListener() {
        @Override
        public void onResponse(UpgradeInfo info) {
            mProgressDlg.setMessage("Upgrading...");
            //4、进入这个回调,请求更新补丁
            upgradeBundles(info,
                    new OnUpgradeListener() {
                        @Override
                        public void onUpgrade(boolean succeed) {
                            mProgressDlg.dismiss();
                            mProgressDlg = null;
                            String text = succeed ?
                                    "Upgrade Success! Switch to background and back to foreground to see changes."
                                    : "Upgrade Failed!";
                            Toast.makeText(mContext, text, Toast.LENGTH_SHORT).show();
                        }
                    });
        }
    });
}

二、upgradeBundles()

主要作用:下载补丁文件,进行更新操作

private void upgradeBundles(finalUpgradeInfo info,
                            final OnUpgradeListener listener) {
    // Just for example, you can do this by OkHttp or something.
    mHandler = new DownloadHandler(listener);
    new Thread() {
        @Override
        public void run() {
            try {
                // Update manifest
                if (info.manifest != null) {
                    if (!Small.updateManifest(info.manifest, false)) {
                        Message.obtain(mHandler, 1, false).sendToTarget();
                        return;
                    }
                }
                // Download bundles
                List<UpdateInfo> updates = info.updates;
                for (UpdateInfo u : updates) {
                    //5、获取补丁的文件路径
                    net.wequick.small.Bundle bundle = Small.getBundle(u.packageName);
                    File file = bundle.getPatchFile();

                    // Download
                    URL url = new URL(u.downloadUrl);
                    HttpURLConnection urlConn = (HttpURLConnection) url.openConnection();
                    InputStream is = urlConn.getInputStream();
                    OutputStream os = new FileOutputStream(file);
                    byte[] buffer = new byte[1024];
                    int length;
                    while ((length = is.read(buffer)) != -1) {
                        os.write(buffer, 0, length);
                    }
                    os.flush();
                    os.close();
                    is.close();

                    //6、更新补丁
                    bundle.upgrade();
                }

                Message.obtain(mHandler, 1, true).sendToTarget();
            } catch (IOException e) {
                e.printStackTrace();
                Message.obtain(mHandler, 1, false).sendToTarget();
            }
        }
    }.start();
}

三、bundle.upgrade()

主要作用:更新补丁的信息

public void upgrade() {
    if(mApplicableLauncher == null) return;
    //7、由于这个方法没有被复写,只能跳到BundleLauncher的upgradeBundle
    mApplicableLauncher.upgradeBundle(this);
}

public void upgradeBundle(Bundle bundle) {
    //8、将包名传递过去
    Small.setBundleUpgraded(bundle.getPackageName(), true);
    // TODO: Hotfix
//        bundle.setPatching(true);
//        resolveBundle(bundle);
//        bundle.setPatching(false);
}

public static void setBundleUpgraded(String bundleName, boolean flag) {
    //9、更新下插件的信息,下次加载的时候,就会自动去拿到这个包名去文件夹进行搜索并更新
    SharedPreferences sp = getContext().
            getSharedPreferences(SHARED_PREFERENCES_BUNDLE_UPGRADES, 0);
    SharedPreferences.Editor editor = sp.edit();
    editor.putBoolean(bundleName, flag);
    editor.apply();
}

四、InstrumentationWrapper

到目前为止,我们只是将补丁的信息下载并记录下来了。此时,如果我们按home键,就会调用之前占坑的InstrumentationWrapper的生命周期

主要作用:杀死进程、

@Override
public void callActivityOnStop(Activity activity) {
    //回调生命周期
    sHostInstrumentation.callActivityOnStop(activity);
    //如果没有更新就直接返回
    if (!Small.isUpgrading()) return;

    // If is upgrading, we are going to kill self while application turn into background,
    // and while we are back to foreground, all the things(code & layout) will be reload.
    // Don't worry about the data missing in current activity, you can do all the backups
    // with your activity's `onSaveInstanceState' and `onRestoreInstanceState'.

    // Get all the processes of device (1)
    ActivityManager am = (ActivityManager) activity.getSystemService(Context.ACTIVITY_SERVICE);
    List<RunningAppProcessInfo> processes = am.getRunningAppProcesses();
    if (processes == null) return;

    // Gather all the processes of current application (2)
    // Above 5.1.1, this may be equals to (1), on the safe side, we also
    // filter the processes with current package name.
    String pkg = activity.getApplicationContext().getPackageName();
    final List<RunningAppProcessInfo> currentAppProcesses = new ArrayList<>(processes.size());
    for (RunningAppProcessInfo p : processes) {
        if (p.pkgList == null) continue;

        boolean match = false;
        int N = p.pkgList.length;
        for (int i = 0; i < N; i++) {
            if (p.pkgList[i].equals(pkg)) {
                match = true;
                break;
            }
        }
        if (!match) continue;

        currentAppProcesses.add(p);
    }
    if (currentAppProcesses.isEmpty()) return;

    // The top process of current application processes.
    RunningAppProcessInfo currentProcess = currentAppProcesses.get(0);
    if (currentProcess.importance == RunningAppProcessInfo.IMPORTANCE_FOREGROUND) return;

    // Seems should delay some time to ensure the activity can be successfully
    // restarted after the application restart.
    // FIXME: remove following thread if you find the better place to `killProcess'
    new Thread() {
        @Override
        public void run() {
            try {
                sleep(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            for (RunningAppProcessInfo p : currentAppProcesses) {
                //10、如果是更新过的,那么就会直接将进程杀死,这样下次进来的时候就能去加载补丁了
                android.os.Process.killProcess(p.pid);
            }
        }
    }.start();
}

五、postSetUp()

主要作用:重置更新标志

public void postSetUp() {
    super.postSetUp();
    ......
    for (LoadedApk apk : apks) {
        dexPaths[i] = apk.path;
        dexFiles[i] = apk.dexFile;
        if (Small.getBundleUpgraded(apk.packageName)) {
            // If upgraded, delete the opt dex file for recreating
            if (apk.optDexFile.exists()) apk.optDexFile.delete();
            //11、重置更新标志
            Small.setBundleUpgraded(apk.packageName, false);
        }
        i++;
    }
    ......
}

结语

其实更新流程很简单,主要是

  1. 检查补丁的版本是否符合更新要求
  2. 下载补丁的文件,放在之前已经初始化的目录下
  3. 通过SharedPreferences记录下补丁的信息
  4. 当下次退出重新打开的时候,就会重定位到补丁上就行加载
  5. 最后重置下更新的标志位
阅读更多

扫码向博主提问

去开通我的Chat快问

qq_30379689

博客专家

职业规划、职业发展、YY福利说
  • 擅长领域:
  • Android
  • 职业规划
  • 职业发展
  • 内推YY
  • 面试套路分享
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_30379689/article/details/79242653
个人分类: 今年大四了
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

加入CSDN,享受更精准的内容推荐,与500万程序员共同成长!
关闭
关闭