欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Android进阶:Small源码分析 更新详情

程序员文章站 2022-03-23 20:07:21
前言 Small的更新有两种方式,一种是将插件放在插件目录,一种是将插件放在补丁目录下。更新插件的方法可以通过以下思路进行更新,本篇主要是通过Sample的例子来请求更新补丁,更...

前言

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

更新插件的思路

将下载好的插件放进sdcard中 从sdcard将插件写入到Small指定的插件目录 设置加载插件的标志位
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 https://wequick.github.io/small/upgrade/bundles.json
                URL url = new URL("https://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 infos = new ArrayList(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 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 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 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++;
    }
    ......
}

结语

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

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