试想一下这个场景,如里你需要对framework.jar进行一些扩展,比如要添加一个android.view.TestView类,此时最好的做法不是在aosp的源码中进行开发。因为这样你只能在记事本或source insight等编辑工具中写代码,然后再使用mm这种系统的编译方式来编译,最后再运行。整个过程耗时耗力,所以此时应该直接在android studio中开发,等调试完毕后再把android.view.TestView类移植到aosp的framework模块中。
当你移植成功烧录完系统,再次在系统上运你的工程时,你会发现你再对你工程中的TestView类所做的任何修改再也不会生效了。因为系统的framework.jar类中也有一个TestView类,根据类加载器的原则会优先加载framework.jar中的TestView类。
要解决这个问题,其中有一个很容易想到的思路就是能不能修改系统的类加载器的加载顺序,如果app和系统中有一个同名的类,优先加载app中的类。
本文主要就这个问题进行讨论。
首先我们要新建一个ClassLoader用于从我们自己的apk加载类。
package com.example.test;
import android.content.Context;
import android.util.Log;
import dalvik.system.DexClassLoader;
public class TestDexClassLoader extends DexClassLoader {
public MyDexClassLoader(Context context, ClassLoader parentLoader) {
/*第一个参数最重要,第一个参数指明了findClass方法从哪里找类*/
super(context.getPackageCodePath(), context.getCacheDir().getAbsolutePath(), context.getApplicationInfo().nativeLibraryDir, parentLoader);
}
@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
Class<?> c;
c = findLoadedClass(name);//根据jdk文档的描述,这个是固定套路,如果已经加载过,直接返回加载过的Class
if (c != null) {
Log.i("MD_CLASS", "MyDexClassLoader findLoadedClass " + c + " with class loader " + c.getClassLoader().getClass().getSimpleName());
return c;
}
/*一般情况都是先调用父类的loadClass,如果父类找不到再调用findClass来加载类,但我们这里要反过来,因为我们希望先加载自己的,自己的没有再加载父类的*/
try {
c = findClass(name);
Log.i("MD_CLASS", "MyDexClassLoader findClass " + c + " with class loader " + c.getClassLoader().getClass().getSimpleName());
return c;
}catch (Exception e) {
c = super.loadClass(name, resolve);
Log.i("MD_CLASS", "MyDexClassLoader super.loadClass " + c+ " with class loader " + c.getClassLoader().getClass().getSimpleName());
return c;
}
}
}
我们自己的类加载器写好后,接下来我们要用自己的类加载器替换掉系统的。这个网上其实有挺多代码的,原理也很简单,就是找到系统类加载器的变量,然后赋值为我们自己的,比如我看看360的。
package com.example.test;/*
* Copyright (C) 2005-2017 Qihoo 360 Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed To in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
import android.app.Application;
import android.content.Context;
import android.util.Log;
/**
* 对宿主的HostClassLoader做修改。这是RePlugin中唯一需要修改宿主私有属性的位置了
*
* @author RePlugin Team
*/
public class PatchClassLoaderUtils {
private static final String PLUGIN_TAG = "PatchClassLoaderUtils";
private static final String TAG = "PatchClassLoaderUtils";
public static boolean patch(Application application, ClassLoader loader) {
try {
// 获取Application的BaseContext (来自ContextWrapper)
Context oBase = application.getBaseContext();
if (oBase == null) {
Log.e(PLUGIN_TAG, "pclu.p: nf mb. ap cl=" + application.getClass());
return false;
}
// 获取mBase.mPackageInfo
// 1. ApplicationContext - Android 2.1
// 2. ContextImpl - Android 2.2 and higher
// 3. AppContextImpl - Android 2.2 and higher
Object oPackageInfo = ReflectUtils.readField(oBase, "mPackageInfo");
if (oPackageInfo == null) {
Log.e(PLUGIN_TAG, "pclu.p: nf mpi. mb cl=" + oBase.getClass());
return false;
}
// mPackageInfo的类型主要有两种:
// 1. android.app.ActivityThread$PackageInfo - Android 2.1 - 2.3
// 2. android.app.LoadedApk - Android 2.3.3 and higher
Log.d(TAG, "patch: mBase cl=" + oBase.getClass() + "; mPackageInfo cl=" + oPackageInfo.getClass());
// 获取mPackageInfo.mClassLoader
ClassLoader oClassLoader = (ClassLoader) ReflectUtils.readField(oPackageInfo, "mClassLoader");
if (oClassLoader == null) {
Log.e(PLUGIN_TAG, "pclu.p: nf mpi. mb cl=" + oBase.getClass() + "; mpi cl=" + oPackageInfo.getClass());
return false;
}
// 外界可自定义ClassLoader的实现,但一定要基于RePluginClassLoader类
ClassLoader cl = loader;
// 将新的ClassLoader写入mPackageInfo.mClassLoader
ReflectUtils.writeField(oPackageInfo, "mClassLoader", cl);
// 设置线程上下文中的ClassLoader为RePluginClassLoader
// 防止在个别Java库用到了Thread.currentThread().getContextClassLoader()时,“用了原来的PathClassLoader”,或为空指针
Thread.currentThread().setContextClassLoader(cl);
Log.d(TAG, "patch: patch mClassLoader ok");
} catch (Throwable e) {
e.printStackTrace();
return false;
}
return true;
}
}
/*
* Copyright (C) 2005-2017 Qihoo 360 Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed To in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.example.test;
import android.content.Context;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
/**
* 和反射操作有关的Utils
*
* @author RePlugin Team
*/
public final class ReflectUtils {
// ----------------
// Class & Constructor
// ----------------
public static Class<?> getClass(final String className) throws ClassNotFoundException {
return Class.forName(className);
}
public static <T> T invokeConstructor(Class<T> cls, Class[] parameterTypes, Object... args) throws
NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Constructor<T> c = cls.getConstructor(parameterTypes);
if (c != null) {
c.setAccessible(true);
return c.newInstance(args);
}
return null;
}
// ----------------
// Field
// ----------------
public static Field getField(Class<?> cls, String fieldName) {
// From Apache: FieldUtils.getField()
// check up the superclass hierarchy
for (Class<?> acls = cls; acls != null; acls = acls.getSuperclass()) {
try {
final Field field = acls.getDeclaredField(fieldName);
// getDeclaredField checks for non-public scopes as well
// and it returns accurate results
setAccessible(field, true);
return field;
} catch (final NoSuchFieldException ex) { // NOPMD
// ignore
}
}
// check the public interface case. This must be manually searched for
// incase there is a public supersuperclass field hidden by a private/package
// superclass field.
Field match = null;
for (final Class<?> class1 : cls.getInterfaces()) {
try {
final Field test = class1.getField(fieldName);
/*Validate.isTrue(match == null, "Reference to field %s is ambiguous relative to %s"
+ "; a matching field exists on two or more implemented interfaces.", fieldName, cls);*/
match = test;
} catch (final NoSuchFieldException ex) { // NOPMD
// ignore
}
}
return match;
}
public static Object readStaticField(Class<?> c, String fieldName) throws NoSuchFieldException, IllegalAccessException {
return readField(c, null, fieldName);
}
public static Object readField(Object target, String fieldName) throws IllegalAccessException, NoSuchFieldException {
return readField(target.getClass(), target, fieldName);
}
public static Object readField(Class<?> c, Object target, String fieldName) throws IllegalAccessException, NoSuchFieldException {
Field f = getField(c, fieldName);
return readField(f, target);
}
public static Object readField(final Field field, final Object target) throws IllegalAccessException {
return field.get(target);
}
public static void writeField(Object target, String fName, Object value) throws NoSuchFieldException, IllegalAccessException {
writeField(target.getClass(), target, fName, value);
}
public static void writeField(Class<?> c, Object object, String fName, Object value) throws NoSuchFieldException, IllegalAccessException {
Field f = getField(c, fName);
writeField(f, object, value);
}
public static void writeField(final Field field, final Object target, final Object value) throws IllegalAccessException {
field.set(target, value);
}
public static List<Field> getAllFieldsList(final Class<?> cls) {
// From Apache: FieldUtils.getAllFieldsList()
//Validate.isTrue(cls != null, "The class must not be null");
final List<Field> allFields = new ArrayList<>();
Class<?> currentClass = cls;
while (currentClass != null) {
final Field[] declaredFields = currentClass.getDeclaredFields();
for (final Field field : declaredFields) {
allFields.add(field);
}
currentClass = currentClass.getSuperclass();
}
return allFields;
}
public static void removeFieldFinalModifier(final Field field) {
// From Apache: FieldUtils.removeFinalModifier()
//Validate.isTrue(field != null, "The field must not be null");
try {
if (Modifier.isFinal(field.getModifiers())) {
// Do all JREs implement Field with a private ivar called "modifiers"?
final Field modifiersField = Field.class.getDeclaredField("modifiers");
final boolean doForceAccess = !modifiersField.isAccessible();
if (doForceAccess) {
modifiersField.setAccessible(true);
}
try {
modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
} finally {
if (doForceAccess) {
modifiersField.setAccessible(false);
}
}
}
} catch (final NoSuchFieldException ignored) {
// The field class contains always a modifiers field
} catch (final IllegalAccessException ignored) {
// The modifiers field is made accessible
}
}
// ----------------
// Method
// ----------------
public static Method getMethod(Class<?> cls, String methodName, Class<?>... parameterTypes) {
// check up the superclass hierarchy
for (Class<?> acls = cls; acls != null; acls = acls.getSuperclass()) {
try {
final Method method = acls.getDeclaredMethod(methodName, parameterTypes);
// getDeclaredField checks for non-public scopes as well
// and it returns accurate results
setAccessible(method, true);
return method;
} catch (final NoSuchMethodException ex) { // NOPMD
// ignore
}
}
// check the public interface case. This must be manually searched for
// incase there is a public supersuperclass field hidden by a private/package
// superclass field.
Method match = null;
for (final Class<?> class1 : cls.getInterfaces()) {
try {
final Method test = class1.getMethod(methodName, parameterTypes);
/*Validate.isTrue(match == null, "Reference to field %s is ambiguous relative to %s"
+ "; a matching field exists on two or more implemented interfaces.", methodName, cls);*/
match = test;
} catch (final NoSuchMethodException ex) { // NOPMD
// ignore
}
}
return match;
}
public static Object invokeMethod(final Object object, final String methodName, Class<?>[] methodParamTypes, Object... args)
throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
Class clz = object.getClass();
Method m = getMethod(clz, methodName, methodParamTypes);
return m.invoke(args);
}
public static Object invokeMethod(ClassLoader loader, String clzName,
String methodName, Object methodReceiver,
Class<?>[] methodParamTypes, Object... methodParamValues) throws
ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
if (methodReceiver == null) {
return null;
}
Class clz = Class.forName(clzName, false, loader);
if (clz != null) {
Method med = clz.getMethod(methodName, methodParamTypes);
if (med != null) {
med.setAccessible(true);
return med.invoke(methodReceiver, methodParamValues);
}
}
return null;
}
public static void setAccessible(AccessibleObject ao, boolean value) {
if (ao.isAccessible() != value) {
ao.setAccessible(value);
}
}
// ----------------
// Other
// ----------------
public static final void dumpObject(Object object, FileDescriptor fd, PrintWriter writer, String[] args) {
try {
Class<?> c = object.getClass();
do {
writer.println("c=" + c.getName());
Field fields[] = c.getDeclaredFields();
for (Field f : fields) {
boolean acc = f.isAccessible();
if (!acc) {
f.setAccessible(true);
}
Object o = f.get(object);
writer.print(f.getName());
writer.print("=");
if (o != null) {
writer.println(o.toString());
} else {
writer.println("null");
}
if (!acc) {
f.setAccessible(acc);
}
}
c = c.getSuperclass();
} while (c != null && !c.equals(Object.class) && !c.equals(Context.class));
} catch (Throwable e) {
e.printStackTrace();
}
}
}
最后在自己的Application的onCreate方法中完成替换
package com.example.test;
import android.app.Application;
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
MyDexClassLoader loader = new MyDexClassLoader(this.getApplicationContext(), getClassLoader());
PatchClassLoaderUtils.patch(this, loader);
}
}