今天遇到一个关于Class类的坑,分享给大家。
有一个需求是,通过intent启动一个Activity来加载某插件,要在intent中传递插件的代理类(Class)到Activity中,由于intent里面只能传递基本类型或是可序列化的类型,故而将需要传递的所有参数全部放入json字符串中,在Activity中对json进行解析,得到其中代理类Class的名字,再利用Class类的方法Class.forName()获取该类名所对应的代理类。
示意代码如下:
发送方:
private void startInstallActivity(Context context) {
Intent intent = new Intent(context, MyInstallActivity);
JSONObject jsonObject = new JSONObject();
try {
jsonObject.put("mProxyActivityClass",Activity_A.class);
// 加入其它需要传递的参数……
} catch (JSONException e) {
e.printStackTrace();
}
intent.putExtra("jsonObject", jsonObject.toString());
context.startActivity(intent);
}
在接收方MyInstallActivity中,收到intent启动后,解析:
private Class parseIntent(Intent intent) {
Class proxyClass = null;
String jsonObjStr = intent.getStringExtra("jsonObject");
try {
JSONObject jsonObject = new JSONObject(jsonObjStr);
String theClassStr = jsonObject.optString("mProxyActivityClass");
Log.i(TAG, theClassStr);
//解析其它参数……
try {
proxyClass = Class.forName(theClassStr);
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} catch (JSONException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return proxyClass;
}
预期的情况是,在intent中传入Activity_A的类名,然后在接收方MyInstallActivity中,通过解析jsonString来得到Activity_A的类名,然后通过类加载器得到Activity_A类(即是需要用到的启动插件的代理类)。
然而,在实际运行过程中得到的结果并非与所预期的一样,jsonString中取得的类名无法解析出对应的类!
通过打印log,发现theClassStr字符串为“class xxx.Activity_A”,即是有一个前缀class加空格,再跟着是包名加类名,用Class.forName()无法得到对应的类!
……
再查看Class的相关API,分析一下,传入intent的类名实际是class.toString(),也就是类的默认toString方法,那么就看一下toString方法的源码:
@Override
public String toString() {
if (isPrimitive()) {
return getSimpleName();
} else {
return (isInterface() ? "interface " : "class ") + getName();
}
}
它的含义是,若该类是基本类型(预定义的基本类型如int,boolean,byte,char,short,long,float,double等),则返回其类名,否则返回一个前缀加上完整类名,前缀根据是接口还是类,分为”interface ” 和”class “。至此,真相大白!
通过toString得到的类名并不是我们预期的完整类名,但是通过看这里的源码可以推测出getName()方法可能是我们预期的完整类名。继续看一下其源码:
/**
* Returns the name of the class represented by this {@code Class}. For a
* description of the format which is used, see the class definition of
* {@link Class}.
*
* @return the name of the class represented by this {@code Class}.
*/
public String getName() {
String result = name;
return (result == null) ? (name = getNameNative()) : result;
}
private native String getNameNative();
通过源码,看到getNameNative()是native方法,这里并不能得到确切的解释其返回结果为类的包名+类名,那就通过自己写一个demo实例来具体验证一下吧:
private void test() {
String name = "" + MainActivity.class;
Log.w(TAG, "MainActivity.class = "+name);
nameOfClassForName(name);
name = "" + MainActivity.this.getClass();
Log.w(TAG, "MainActivity.this.getClass() = "+name);
nameOfClassForName(name);
name = "" + MainActivity.this.getClass().getName();
Log.w(TAG, "MainActivity.this.getClass().getName() = "+name);
nameOfClassForName(name);
name = "" + MainActivity.class.getName();
Log.w(TAG, "MainActivity.class.getName() = "+name);
nameOfClassForName(name);
name = "" + MainActivity.class.getSimpleName();
Log.w(TAG, "MainActivity.class.getSimpleName() = "+name);
nameOfClassForName(name);
}
private void nameOfClassForName(String name) {
try {
Class<?> theClass = Class.forName(name);
Log.w(TAG, "Class.forName = " + theClass);
} catch (ClassNotFoundException e) {
Log.e(TAG, "Class.forName is null!");
}
}
运行一下,看看log吧:
仔细分析,发现通过this.getClass().getName()和class.getName()最后得到的结果相同,都是完整的包名+类名。
Get!找到答案了,最终要用class.getName方法!
只要修改原先代码的一行就ok:
……
jsonObject.put("mProxyActivityClass",Activity_A.class.getName());
……
此坑,记下了,各位同学下次遇到这种奇葩问题,可以通过分析源码和实际demo验证来解决!