热更新

应用场景

刚发布的应用出现了比较严重的bug
有一些小的功能想即时的推送给用户去使用

热更新流程

1、线上检测到严重的crash
2、拉出bugfix分支并在分支上修复问题
3、jenkins构建和补丁生成
4、app通过推送或者主动拉取补丁文件
5、将bugfix代码合到master分支上

热更新原理

	BaseDexClassLoader
		PathClassLoader
		DexClassLoader

​ Android中使用PathClassLoader类作为Android的默认的类加载器,其功能是简单的从文件系统中加载类文件

源码分析

//BaseDexClassLoader重写了findClass()
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
    List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
    //通过pathList对象的findClass()获取Class对象。pathList是在BaseDexClassLoader的构造函数中new出来的
    Class c = pathList.findClass(name, suppressedExceptions);
    if (c == null) {
        ClassNotFoundException cnfe = new ClassNotFoundException("Didn't find class \"" + name + "\" on path: " + pathList);
        for (Throwable t : suppressedExceptions) {
            cnfe.addSuppressed(t);
        }
        throw cnfe;
    }
    return c;
}
//pathList的findClass()
public Class findClass(String name, List<Throwable> suppressed) {
    for (Element element : dexElements) {
        DexFile dex = element.dexFile;
        if (dex != null) {
            //遍历dexElements列表,调用loadClassBinaryName()来加载类
            Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);
            if (clazz != null) {
                //dexElements前面出现的dex会被优先加载,一旦Class被加载成功
                return clazz;
            }
        }
    }
    if (dexElementsSuppressedExceptions != null) {
       suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
    }
    return null;
}

具体实现

案例见本文件夹

构造一个DexClassLoader对象来加载新的dex文件,调用一次dexClassLoader.loadClass(dummyClassName)让dexClassLoader.pathList.dexElements中,包含新的dex
通过把dexClassLoader.pathList.dexElements**(整个列表)**插入到系统默认的classLoader.pathList.dexElements列表前面,就可以让系统优先加载新的dex中的类

优点

Dexposed的AOP实现是完全非侵入式的,没有使用任何注解处理器,编织器或者字节码重写器
Dexposed实现的hooking,不仅可以hook应用中的自定义函数,也可以hook应用中调用的Android框架的函数

使用场景

AOP编程
插桩(例如测试,性能监控等)
在线热更新,修复严重的,紧急的或者安全性的bug
SDK hooking以提供更好的开发体验

集成

//添加依赖
buildscript {
	repositories {
		mavenCentral()
	}
	dependencies {
		classpath 'com.android.tools.build:gradle:0.10.+'
		classpath 'com.nabilhachicha:android-native-dependencies:0.1'
	}
}
...
//导入.so包,有本地文件可直接拷贝,此处可省略
native_dependencies {
    artifact 'com.taobao.dexposed:dexposed_l:0.2+:armeabi'
    artifact 'com.taobao.dexposed:dexposed:0.2+:armeabi'
}
//导入jar包
dependencies {
    compile files('libs/dexposedbridge.jar')
}
//初始化
public class MyApplication extends Application {
    private boolean mIsSupported = false; // 设备是否支持dexposed
    private boolean mIsLDevice = false;  // 是否是Android 5.0及以上
    @Override
    public void onCreate() {
        super.onCreate();
        // check device if support and auto load libs
        mIsSupported = DexposedBridge.canDexposed(this);
        mIsLDevice = Build.VERSION.SDK_INT >= 21;
    }
    public boolean isSupported() {
        return mIsSupported;
    }
    public boolean isLDevice() {
        return mIsLDevice;
    }
}

在线热更新案例

//添加依赖,见上述
//宿主apk,类路径:com.dexposed.MainActivity
public class MainActivity extends Activity {
	private void showDialog() {
		AlertDialog.Builder builder = new AlertDialog.Builder(this);
		builder.setTitle("宿主的标题")
			.setMessage("宿主的信息")
			.setPositiveButton("宿主的按钮", newDialogInterface.OnClickListener() {
 				public void onClick(DialogInterface dialog, int whichButton) {}
		}).create().show();
	}
}
//补丁apk,替换
//添加依赖
dependencies {
    provided files('libs/dexposedbridge.jar')	//使用provided防止冲突
    provided files('libs/patchloader.jar')
}
public class DialogPatch implements IPatch {
    @Override public void handlePatch(final PatchParam arg0) throws Throwable {       
        Class<?> cls = null;
        try {
            cls= arg0.context.getClassLoader()
                .loadClass("com.dexposed.MainActivity");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
            return;
        }       
        DexposedBridge.findAndHookMethod(cls, "showDialog",
            new XC_MethodReplacement() {
            @Override
            protected Object replaceHookedMethod(MethodHookParam param) throws Throwable {
                Activity mainActivity = (Activity) param.thisObject;
                AlertDialog.Builder builder = new AlertDialog.Builder(mainActivity);
                builder.setTitle("补丁的标题")
                        .setMessage("补丁的信息")
                        .setPositiveButton("ok", new DialogInterface.OnClickListener() {
                            public void onClick(DialogInterface dialog, int whichButton) {}
                        }).create().show();
                return null;                 
            }
        });
    }
}
//宿主apk
public void runPatchApk(View view) {
    Log.d("dexposed", "runPatchApk button clicked.");
    if (isLDevice) {
        showLog("dexposed", "It doesn't support this function on L device.");
        return;
    }
    if (!isSupport) {
        Log.d("dexposed", "This device doesn't support dexposed!");
        return;
    }
    File cacheDir = getExternalCacheDir();
    if(cacheDir != null){
        //fullpath: /data/user/0/com.example.myhotfix/cache/patch.apk
        String fullpath = cacheDir.getAbsolutePath() + File.separator + "patch.apk";
        PatchResult result = PatchMain.load(this, fullpath, null);
        if (result.isSuccess()) {
            Log.e("Hotpatch", "patch success!");
        } else {
            Log.e("Hotpatch", "patch error is " + result.getErrorInfo());
        }
    }
    showDialog();
}
Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐