AndFix 源码分析之一

1, 概念

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的方法。

2,构造方法

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对象,检查修复包的签名安全。

2.1 是否支持热修复

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。

2.2 SecurityChecker初始化

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);
		}
	}

3, PatchManager初始化

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; //修复包中包含的类名
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值