十、反射和类加载

反射

当谈到拆开 Android 应用并让它们在适合你的状态下运行时,反射是许多王牌之一。简单地说,反射是一个 API,可以用来在运行时访问、检查和修改对象——这包括字段、方法、类和接口(如图 10-1 所示)。

img/509502_1_En_10_Fig1_HTML.png

图 10-1

Java 反射图

下面列出了这些组件的摘要:

  • -类是一个蓝图/模板,当使用时,可以从它们创建单独的对象。例如,可以用一个installedRam变量、getRAM()方法和setRAM()方法创建一个Computer类。使用这个类可以创建一个对象,例如,Computer myComputer = new Computer();然后方法setRAM()可以用在myComputer对象上,例如myComputer.setRAM(32);.

  • 方法——方法是一段代码,具有特定的用途,在被调用时运行,可以是类的一部分,也可以是独立的。方法可以传递一系列类型化参数,并且可以返回指定类型的变量。当作为类的一部分时,方法可以是静态的或实例的。实例方法需要在使用之前创建其类的对象,而静态方法不依赖于已初始化的对象。例如,类计算机可能有一个将两个数相加并返回结果的sum静态方法,以及一个为所创建对象的特定实例设置 ram 变量的setRAM()实例方法。

  • 构造函数 -构造函数是一种特殊类型的方法,作为对象(如类)初始化的一部分,用来设置变量和调用方法。例如,House 类可能有一个构造函数方法,它将三个变量作为参数:hightnumberOfRoomshasGarden。然后可以用House myHouse = new House(10, 2, false);.创建一个房子对象

  • 接口——接口是一个抽象类,它包含一组带有空体的方法。例如,拥有一个生物接口可能有像move()speak()eat()这样的方法,这些方法都需要根据实现接口的职业(生物类型)来填充。

在下面的例子中,将使用两个类来帮助显示一系列不同的反射技术。如果不使用这些示例类,请替换代码示例中适用的引用:

一个 助手类 演示反思:

public class Loadable {
    private final static String description = "This is a class that contains an assortment of access modifiers to test different types of reflection.";
    private Context context;
    private long uniqueId = 0;
    private long time = 0;
    private DeviceData deviceData = new DeviceData();

    public void setDeviceInfo() {
        deviceData.setDeviceInfo();
    }

    public long getTime() {
        return time;
    }

    private Loadable(Context context, long uniqueId) {
        this.context = context;
        this.uniqueId = uniqueId;
    }

    private void setTime(){
        this.time = System.currentTimeMillis();
    }

    private static String getDeviceName(){
        return android.os.Build.MODEL;
    }

    protected static Loadable construct(Context context){

        final int uniqueId = new Random().nextInt((1000) + 1);

        Loadable loadable = new Loadable(context, uniqueId);
        loadable.setDeviceInfo();
        return loadable;
    }
}

助手类 支持加载时呈现一系列功能:

public class DeviceData {

    String version = ""; // OS version
    String sdkLevel = ""; // API Level
    String device = "";  // Device
    String model = "";   // Model
    String product = ""; // Product

    public void setDeviceInfo(){
        version = System.getProperty("os.version");
        sdkLevel = android.os.Build.VERSION.SDK;
        device = android.os.Build.DEVICE;
        model = android.os.Build.MODEL;
        product = android.os.Build.PRODUCT;
    }

    @Override
    public String toString() {
        return "DeviceData{" +
                "version='" + version + '\'' +
                ", sdkLevel='" + sdkLevel + '\'' +
                ", device='" + device + '\'' +
                ", model='" + model + '\'' +
                ", product='" + product + '\'' +
                '}';
    }
}

创建类的实例

在下面的例子中,使用反射,创建了一个新的DeviceData类实例,并且在记录这些字段之一的初始化状态之前运行了一个对setDeviceInfo方法的调用(以填充它的字段)。

初始化一个类 :

try {
    Object initialisedDeviceData= DeviceData.class.newInstance();
    initialisedDeviceData.getClass().getDeclaredMethod("setDeviceInfo").invoke(initialisedDeviceData);
    String model = (String) initialisedDeviceData.getClass().getDeclaredField("model").get(initialisedDeviceData);
    Log.v(TAG, model);

} catch (IllegalAccessException e) {
    e.printStackTrace();
} catch (InstantiationException e) {
    e.printStackTrace();
} catch (NoSuchMethodException e) {
    e.printStackTrace();
} catch (InvocationTargetException e) {
    e.printStackTrace();
} catch (NoSuchFieldException e) {
    e.printStackTrace();
}

。getDeclaredMethod 与进行了比较。获取方法

在下面的例子中,我们看到了方法getMethodsgetDeclaredMethods之间的区别——这对于getFieldsgetDeclaredFields也是一样的。getMethods将返回一个数组,该数组包含类或接口的public方法,以及任何从超类或超接口继承的方法(超类/超接口是一个可以从中创建多个子对象的对象)。getDeclaredMethods另一方面,返回类或接口的所有声明的方法(不仅仅是public)。

这里的主要区别是,如果需要访问私有方法,将使用getDeclaredMethods方法,然后用.setAccessible方法设置可访问性,而如果需要访问superclassessuperinterfaces, getMethods的方法,将改为使用。

getMethods()示例:

for (Method method : Loadable.class.getMethods()){
     Log.v(TAG, method.getName());
 }

getDeclaredMethods()示例:

for (Method method : Loadable.class.getDeclaredMethods()){
     method.setAccessible(true);
     Log.v(TAG, method.getName());
 }

静态方法

在静态方法的情况下,使用反射不需要类的实例。

静态方法示例:

try {
    Method getDeviceName = Loadable.class.getDeclaredMethod("getDeviceName");
    getDeviceName.setAccessible(true);
    Log.v(TAG,(String) getDeviceName.invoke(Loadable.class));
} catch (NoSuchMethodException e) {
    e.printStackTrace();
} catch (IllegalAccessException e) {
    e.printStackTrace();
} catch (InvocationTargetException e) {
    e.printStackTrace();
}

私有构造函数

在类的构造函数是私有的情况下,反射仍然可以用来构造类和访问它的字段和方法。

谈论构造函数时的一个额外的怪癖是,当一个成员变量在一个类中定义时——比如String myMemberVariable = android.os.Build.VERSION.SDK;——它被编译器移动到该类的构造函数中。1T4】

下面是一个用私有构造函数构造类的例子:

try {
    Constructor<?> constructor = Loadable.class.getDeclaredConstructor(Context.class, long.class);
    constructor.setAccessible(true);
    Object instance = constructor.newInstance(getApplicationContext(), (Object) 12); // constructor takes a context and an id.
    Field uniqueIdField = instance.getClass().getDeclaredField("uniqueId");
    uniqueIdField.setAccessible(true);
    long uniqueId = (long) uniqueIdField.get(instance);
    Log.v(TAG, ""+uniqueId);

} catch (InstantiationException e) {
    e.printStackTrace();
} catch (InvocationTargetException e) {
    e.printStackTrace();
} catch (NoSuchMethodException e) {
    e.printStackTrace();
} catch (IllegalAccessException e) {
    e.printStackTrace();
} catch (NoSuchFieldException e) {
    e.printStackTrace();
}

将类初始化为其他类的字段

下面的例子使用反射两次:第一次初始化一个类并获得对它的一个字段的访问,第二次在那个字段(它是一个自己的类)上使用反射来访问它的一个字段(它是一个字符串)。

实例类 实例:

try {
    // The loadable class has a static method that can be used to construct it in this example, however, if the constructor isn't public,
    // this can also be done with the private constructor example.
    // and can be done as in the public class example.
    Object instance = Loadable.class.getDeclaredMethod("construct", Context.class)
            .invoke(Loadable.class, getApplicationContext());

    // Retrieve the field device data which is the class we're looking to get the data of.
    Field devicdDataField = instance.getClass().getDeclaredField("deviceData");
    devicdDataField.setAccessible(true);
    Object initialisedDeviceData = devicdDataField.get(instance);

    // After accessing the value from the field we're looking to access the filds of we can use the same type of reflection again after getting it's class.
    Field modelField = initialisedDeviceData.getClass().getDeclaredField("device");
    modelField.setAccessible(true);
    String model = (String) modelField.get(initialisedDeviceData);

    Log.v(TAG,model);

} catch (IllegalAccessException e) {
    e.printStackTrace();
} catch (InvocationTargetException e) {
    e.printStackTrace();
} catch (NoSuchMethodException e) {
    e.printStackTrace();
} catch (NoSuchFieldException e) {
    e.printStackTrace();
}

类别加载

Java 类加载器 2 是 Java 运行时环境(JRE)的一个组件,它将 Java 类加载到 Java 虚拟机(JVM)/Dalvik 虚拟机(DVM)/Android 运行时(ART)中。不是所有的类都被同时加载,也不是用同一个类加载器。上下文方法getClassLoader()可以用来获取当前的类加载器。Android 中有几种类型的类加载,它们是:

  • PathClassLoader -这是 Android 系统为其系统和应用类加载器使用的。

  • DexClassLoader -加载包含一个.dex文件的文件类型(如.jar.apk.dex文件直接加载)。这些.dex文件(Dalvik 可执行文件)包含 Dalvik 字节码。

  • URL class loader——这是用来通过 URL 路径检索类或资源的。以/结尾的路径被假定为目录,否则它们被假定为.jar文件。

下面使用 Dalvik 可执行文件和DexClassLoader 执行类加载。

检索当前的类加载器:

ClassLoader loader = getApplicationContext().getClassLoader();

从 API 级(Android O)开始,可以直接从内存中读取一个 dex 文件。为此,读取文件的 ByteBuffer,并使用 MemoryDexClassLoader 中的类。下面是一个将文件读入字节数组的帮助函数:

private static byte[] readFileToByteArray(File file){
    FileInputStream fis = null;

    byte[] bArray = new byte[(int) file.length()];
    try{
        fis = new FileInputStream(file);
        fis.read(bArray);
        fis.close();

    }catch(IOException ioExp){
        ioExp.printStackTrace();
    }
    return bArray;
}

内存中 dex 类加载 :

dexLoader = new InMemoryDexClassLoader(ByteBuffer.wrap(readFileToByteArray(filePath)), loader);

另一种方法是直接从文件中加载 dex 文件。DexClassLoader 类采用。dex 文件,optimizedDirectory - where。odex(优化的 dex 文件)存储在 Android API level 26 之前,librarySearchPath - a 字符串列表(由 File.pathSeparator 分隔;)声明包含本地库的目录,以及 parent -父类加载器。

dexLoader = new DexClassLoader(filePath, dexCacheDirectory.getAbsolutePath(), null, loader);

创建一个 dex 类加载器后,选择要加载的类,作为一个字符串:

loadedClass = dexLoader.loadClass("me.jamesstevenson.dexloadable.MainActivity"); //alter path for your use case

在这个阶段,未初始化的类可以正常使用,如反射部分所述。下面展示了如何安全地初始化这个类:

initialisedClass = loadedClass != null ? loadedClass.newInstance() : null;

在初始化这个类 之后,可以调用一个特定的方法,作为一个字符串,它的响应可以像前面用标准反射所做的那样返回:

method = loadedClass != null ? loadedClass.getMethod("loadMeAndIllTakeContext", Context.class) : null;
Object methodResponse = method != null ? method.invoke(initialisedClass, getApplicationContext()) : null;
Footnotes [1](#Fn1_source) “我是否应该在我的接收器中使用 Android:process = ":remote….” [`https://stackoverflow.com/questions/4311069/should-i-use-android-process-remote-in-my-receiver`](https://stackoverflow.com/questions/4311069/should-i-use-android-process-remote-in-my-receiver) 。5 月 21 日访问。2020.   [2](#Fn2_source) " Catherine22/ClassLoader:加载 apk 或类...——GitHub。” [`https://github.com/Catherine22/ClassLoader`](https://github.com/Catherine22/ClassLoader) 。于 5 月 16 日访问。2020.