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

前言

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

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

  1. 将下载好的插件放进sdcard中
  2. 从sdcard将插件写入到Small指定的插件目录
  3. 设置加载插件的标志位
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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()

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
//补丁更新的入口
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();
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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()

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
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()

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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的生命周期

主要作用:杀死进程、

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
@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()

主要作用:重置更新标志

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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. 最后重置下更新的标志位
坚持原创技术分享,您的支持将鼓励我继续创作!