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(); Listinfos = 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 Listupdates = 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); Listprocesses = 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记录下补丁的信息 当下次退出重新打开的时候,就会重定位到补丁上就行加载 最后重置下更新的标志位