AndFix是阿里巴巴的一个开源热修复框架,该框架使用方便,结构简单。
开发时,首先使用eclipse打开该框架,在activity中添加如下代码就可以完成开发。
private static final String APATCH_PATH = "/fix.apatch"; public static PatchManager mPatchManager;// 最好是单例模式 public static String TAG = "AndFix"; @Override public void onCreate(Bundle savedInstanceState){ super.onCreate(savedInstanceState); mPatchManager = new PatchManager(this); mPatchManager.init("1.0"); // version mPatchManager.loadPatch(); String patchFileString = Environment.getExternalStorageDirectory().getAbsolutePath() + APATCH_PATH; File apatchPath = new File(patchFileString); if (apatchPath.exists()) { try { mPatchManager.addPatch(patchFileString); //添加apatch文件 } catch (IOException e) { e.printStackTrace(); } } ••• }这里的修复包是直接放在本地的,在实际操作中会从网上去下载;并且修复包的名字为fix.apatch,保存在SD卡目录中。
以下几个章节逐个分析PatchManager的方法。
PatchManager的构造方法如下,
public PatchManager(Context context) { mContext = context; mAndFixManager = new AndFixManager(mContext);// 初始化 mPatchDir = new File(mContext.getFilesDir(), DIR); mPatchs = new ConcurrentSkipListSet<Patch>(); mLoaders = new ConcurrentHashMap<String, ClassLoader>(); }主要为5个变量赋值。 private final Context mContext; //进程上下文 private final AndFixManager mAndFixManager; // 热修复管理类 private final File mPatchDir;//apk中patch文件的绝对路径 private final SortedSet<Patch> mPatchs;// 保存patch文件 private final Map<String, ClassLoader> mLoaders;// ClassLoader哈希表如果是第三方apk,那么mPatchDir路径一般是
/data/data/包名/files/apatch/
从网上下载好修复包apatch文件之后,会调用addPatch方法,这时候会把修复包复制到这个地方,以后再次启动时就会遍历这个目录加载apatch文件。
AndFixManager初始化流程图如下,
AndFixManager的构造方法如下,
public AndFixManager(Context context) { mContext = context; mSupport = Compat.isSupport(); if (mSupport) { mSecurityChecker = new SecurityChecker(mContext); mOptDir = new File(mContext.getFilesDir(), DIR); if (!mOptDir.exists() && !mOptDir.mkdirs()) {//make directory fail mSupport = false; Log.e(TAG, "opt dir create error."); } else if (!mOptDir.isDirectory()) {// not directory mOptDir.delete(); mSupport = false; } } }该方法首先调用Compat的isSupport方法判断当前环境是否支持热修复,然后构造SecurityChecker对象,检查修复包的签名安全。
Compat的isSupport方法如下,
public static synchronized boolean isSupport() { if (isChecked) // 如果已经进行检查,直接返回上次检查的结果 return isSupport; isChecked = true; // not support alibaba's YunOs if (!isYunOS() && AndFix.setup() && isSupportSDKVersion()) { isSupport = true; } if (inBlackList()) { //黑名单,该方法返回false isSupport = false; } return isSupport; }可以进行热修复的条件:非YunOS系统,Android2.3-7.0系统版本,热修复native层设置是否成功.
isYunOS和isSupportSDKVersion方法很简单,这里主要看AndFix的setup方法,该方法如下,
public static boolean setup() { try { final String vmVersion = System.getProperty("java.vm.version"); boolean isArt = vmVersion != null && vmVersion.startsWith("2"); int apilevel = Build.VERSION.SDK_INT; return setup(isArt, apilevel); } catch (Exception e) { Log.e(TAG, "setup", e); return false; } }Android 5.0及以后都是使用ART,而之前都是使用dalvik虚拟机。为了兼容不同的android系统,本地C/C++使用了2种类。如下图,
带参数的setup方法通过JNI机制进行调用。AndFix.java 对应的C++文件是andfix.cpp。
andfix.cpp的setup方法如下,
static jboolean setup(JNIEnv* env, jclass clazz, jboolean isart, jint apilevel) { isArt = isart; LOGD("vm is: %s , apilevel is: %i", (isArt ? "art" : "dalvik"), (int )apilevel); if (isArt) { return art_setup(env, (int) apilevel); } else { return dalvik_setup(env, (int) apilevel); } }首先看ART中art_method_replace.cpp的art_setup方法,该方法直接返回true。
extern jboolean __attribute__ ((visibility ("hidden"))) art_setup(JNIEnv* env, int level) { apilevel = level; return JNI_TRUE; }Dalvik中对应dalvik_method_replace.cpp的dalvik _setup方法如下,
extern jboolean __attribute__ ((visibility ("hidden"))) dalvik_setup( JNIEnv* env, int apilevel) { void* dvm_hand = dlopen("libdvm.so", RTLD_NOW); if (dvm_hand) { dvmDecodeIndirectRef_fnPtr = dvm_dlsym(dvm_hand, apilevel > 10 ? "_Z20dvmDecodeIndirectRefP6ThreadP8_jobject" : "dvmDecodeIndirectRef"); if (!dvmDecodeIndirectRef_fnPtr) { return JNI_FALSE; } dvmThreadSelf_fnPtr = dvm_dlsym(dvm_hand, apilevel > 10 ? "_Z13dvmThreadSelfv" : "dvmThreadSelf"); if (!dvmThreadSelf_fnPtr) { return JNI_FALSE; } jclass clazz = env->FindClass("java/lang/reflect/Method"); jClassMethod = env->GetMethodID(clazz, "getDeclaringClass", "()Ljava/lang/Class;"); return JNI_TRUE; } else { return JNI_FALSE; } }这里主要做一些初始化操作,获取一些函数指针,准备后续的replaceMethod函数中使用:
1、在libdvm.so动态获取dvmDecodeIndirectRef函数指针和获取dvmThreadSelf函数指针。
2、调用dest的 Method.getDeclaringClass方法获取method的类对象clazz。
SecurityChecker的构造方法如下,
public SecurityChecker(Context context) { mContext = context; init(mContext); // 进行初始化 }Init方法主要是进行初始化变量,为后面的热修复做准备。
private void init(Context context) { try { PackageManager pm = context.getPackageManager(); String packageName = context.getPackageName(); PackageInfo packageInfo = pm.getPackageInfo(packageName, PackageManager.GET_SIGNATURES); CertificateFactory certFactory = CertificateFactory .getInstance("X.509"); ByteArrayInputStream stream = new ByteArrayInputStream( packageInfo.signatures[0].toByteArray()); X509Certificate cert = (X509Certificate) certFactory .generateCertificate(stream); mDebuggable = cert.getSubjectX500Principal().equals(DEBUG_DN); mPublicKey = cert.getPublicKey(); } catch (NameNotFoundException e) { Log.e(TAG, "init", e); } catch (CertificateException e) { Log.e(TAG, "init", e); } }PatchManager的init方法调用流程图如下,
init方法如下,
public void init(String appVersion) { if (!mPatchDir.exists() && !mPatchDir.mkdirs()) {// make directory fail Log.e(TAG, "patch dir create error."); return; } else if (!mPatchDir.isDirectory()) {// not directory mPatchDir.delete(); return; } SharedPreferences sp = mContext.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE); String ver = sp.getString(SP_VERSION, null); if (ver == null || !ver.equalsIgnoreCase(appVersion)) { cleanPatch(); sp.edit().putString(SP_VERSION, appVersion).commit(); } else { initPatchs(); } }判断当前PatchManager的版本号是否发生变化,如果发生变化就清空本地所有的修复包。
如果没有变化,直接调用initPatchs方法初始化修复包。
private void initPatchs() { File[] files = mPatchDir.listFiles(); for (File file : files) { addPatch(file); } }逐个对该apk 路径 /data/data/包名/files/apatch/ 下的文件调用addPatch方法,该方法如下,
private static final String SUFFIX = ".apatch";private Patch addPatch(File file) { Patch patch = null; if (file.getName().endsWith(SUFFIX)) { try { patch = new Patch(file); mPatchs.add(patch); } catch (IOException e) { Log.e(TAG, "addPatch", e); } } return patch; }
如果文件以.apatch结尾(文件是.apatch类型的文件),就创建Patch对象,然后添加到mPatchs集合中。
实际上就是把该目录下所有的修复包文件加到列表中。
Patch中的构造方法会调用init方法,
private void init() throws IOException { JarFile jarFile = null; InputStream inputStream = null; try { jarFile = new JarFile(mFile); JarEntry entry = jarFile.getJarEntry(ENTRY_NAME); inputStream = jarFile.getInputStream(entry); Manifest manifest = new Manifest(inputStream); Attributes main = manifest.getMainAttributes(); mName = main.getValue(PATCH_NAME); mTime = new Date(main.getValue(CREATED_TIME)); mClassesMap = new HashMap<String, List<String>>(); Attributes.Name attrName; String name; List<String> strings; for (Iterator<?> it = main.keySet().iterator(); it.hasNext();) { attrName = (Attributes.Name) it.next(); name = attrName.toString(); if (name.endsWith(CLASSES)) { strings = Arrays.asList(main.getValue(attrName).split(",")); if (name.equalsIgnoreCase(PATCH_CLASSES)) { mClassesMap.put(mName, strings); } else { mClassesMap.put( name.trim().substring(0, name.length() - 8),// remove // "-Classes" strings); } } } } finally { if (jarFile != null) { jarFile.close(); } if (inputStream != null) { inputStream.close(); } } }主要就是通过JarFile类解析修复包文件,读取META-INF\PATCH.MF文件内容,获取需要修复类的名称,
多个修复类之间用逗号分隔。修复包的相关信息都存储在以下变量中,
private final File mFile; // 修复包文件对象 private String mName; // 修复包名字 private Date mTime; // 创建时间 private Map<String, List<String>> mClassesMap; //修复包中包含的类名