十、反射和类加载
反射
当谈到拆开 Android 应用并让它们在适合你的状态下运行时,反射是许多王牌之一。简单地说,反射是一个 API,可以用来在运行时访问、检查和修改对象——这包括字段、方法、类和接口(如图 10-1 所示)。
图 10-1
Java 反射图
下面列出了这些组件的摘要:
-
类 -类是一个蓝图/模板,当使用时,可以从它们创建单独的对象。例如,可以用一个
installedRam
变量、getRAM()
方法和setRAM()
方法创建一个Computer
类。使用这个类可以创建一个对象,例如,Computer myComputer = new Computer();
然后方法setRAM()
可以用在myComputer
对象上,例如myComputer.setRAM(32);.
-
方法——方法是一段代码,具有特定的用途,在被调用时运行,可以是类的一部分,也可以是独立的。方法可以传递一系列类型化参数,并且可以返回指定类型的变量。当作为类的一部分时,方法可以是静态的或实例的。实例方法需要在使用之前创建其类的对象,而静态方法不依赖于已初始化的对象。例如,类计算机可能有一个将两个数相加并返回结果的
sum
静态方法,以及一个为所创建对象的特定实例设置 ram 变量的setRAM()
实例方法。 -
构造函数 -构造函数是一种特殊类型的方法,作为对象(如类)初始化的一部分,用来设置变量和调用方法。例如,House 类可能有一个构造函数方法,它将三个变量作为参数:
hight
、numberOfRooms
和hasGarden
。然后可以用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 与进行了比较。获取方法
在下面的例子中,我们看到了方法getMethods
和getDeclaredMethods
之间的区别——这对于getFields
和getDeclaredFields
也是一样的。getMethods
将返回一个数组,该数组包含类或接口的public
方法,以及任何从超类或超接口继承的方法(超类/超接口是一个可以从中创建多个子对象的对象)。getDeclaredMethods
另一方面,返回类或接口的所有声明的方法(不仅仅是public
)。
这里的主要区别是,如果需要访问私有方法,将使用getDeclaredMethods
方法,然后用.setAccessible
方法设置可访问性,而如果需要访问superclasses
或superinterfaces,
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;
版权属于:月萌API www.moonapi.com,转载请注明出处