JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
Java反射机制主要提供了以下功能: 在运行时判断任意一个对象所属的类;在运行时构造任意一个类的对象;在运行时判断任意一个类所具有的成员变量和方法;在运行时调用任意一个对象的方法;生成动态代理。
查看类的继承关系,Android中的Activity和Service,是基于Context的。Context代表了用户和系统的交互过程。侠义理解,获得Context就获得了和系统进行交互的可能性。
说明:
- Room A, Room B, Room N,是没有被安装的APK格式文件,为了简单说明,里面只有一个Activity;
- Hall:被安装的APK,用于提供Context给Room A, Room B, Room N等Activity;
- Hall启动Room X使用的是反射技术和提供给Room X自己的Context,用于交互操作;
Room工具类:
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import dalvik.system.DexClassLoader;
public class RoomUtils implements OnClickListener {
private Context mContext;
/**
* 目标Activity的Class
*/
private Class<?> localClass;
/**
* 目标Activity的实例
*/
private Object localInstance;
/**
*
* @param context
* @param dexPath
* @param dexOutputPath
*/
public RoomUtils(Context context, String dexPath, String dexOutputPath) {
mContext = context;
loadAPK(dexPath, dexOutputPath);
}
/**
* 获得相关的View
* @return 返回一个可点击的按钮
*/
public View getView() {
Button button = new Button(mContext);
String message = null;
try {
message = getRemoteMessage();
button.setText(message);
button.setOnClickListener(this);
} catch (Exception e) {
e.printStackTrace();
message = null;
}
if (message == null) {
button.setVisibility(View.GONE);
}
return button;
}
/**
* 通过反射,获得描述性信息
* @return
* @throws Exception
*/
private String getRemoteMessage() throws Exception {
Method remoteMessageMethod = localClass.getDeclaredMethod("getRoomMessage", null);
remoteMessageMethod.setAccessible(true);
return (String) remoteMessageMethod.invoke(localInstance, null);
}
/**
* 通过反射,获得Activity的实例和Class
* @param dexPath
* @param dexOutputPath
*/
private void loadAPK(String dexPath, String dexOutputPath) {
try {
ClassLoader localClassLoader = ClassLoader.getSystemClassLoader();
DexClassLoader localDexClassLoader = new DexClassLoader(dexPath, dexOutputPath, null, localClassLoader);
PackageInfo localPackageInfo = mContext.getPackageManager().getPackageArchiveInfo(dexPath, PackageManager.GET_ACTIVITIES);
if ((localPackageInfo.activities != null) && (localPackageInfo.activities.length > 0)) {
String activityName = localPackageInfo.activities[0].name;
localClass = localDexClassLoader.loadClass(activityName);
Constructor<?> localConstructor = localClass.getConstructor(new Class[] {Context.class});
localInstance = localConstructor.newInstance(new Object[] {mContext});
}
} catch (Exception ex) {
ex.printStackTrace();
}
}
/**
* 通过反射,启动Activity
* @throws Exception
*/
private void startRemoteActivity() throws Exception {
Method onCreateMethod = localClass.getDeclaredMethod("onCreate", new Class[] {Bundle.class});
onCreateMethod.setAccessible(true);
onCreateMethod.invoke(localInstance, new Object[] {null});
}
@Override
public void onClick(View v) {
try {
startRemoteActivity();
} catch (Exception e) {
e.printStackTrace();
}
}
}
说明:
- 作用:使用DexClassLoader加载指定路径的APK文件,并提供按钮和按钮点击事件;
- localClass:通过反射获得的Room X中的Activity的Class引用,注意String activityName = localPackageInfo.activities[0].name;中指定的是第0个Activity,如果有多个Activity,要做其它处理;
- localInstance:通过反射获得的Room X中的Activity示例;
- Constructor<?> localConstructor = localClass.getConstructor(new Class[] {Context.class});在加载构造函数时,自定义了含有Context参数的构造函数;
- getView:返回一个和Room X相关联的按钮;
Hall中调用过程:
public class TestHallActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// 获得根容器
LinearLayout root = (LinearLayout) findViewById(R.id.root);
// 分别获得SDCARD下的APK并添加相关View到根容器
RoomUtils roomA = new RoomUtils(this, "/mnt/sdcard/TestRoomA.apk", "/mnt/sdcard/");
root.addView(roomA.getView());
RoomUtils roomB = new RoomUtils(this, "/mnt/sdcard/TestRoomB.apk", "/mnt/sdcard/");
root.addView(roomB.getView());
RoomUtils roomC = new RoomUtils(this, "/mnt/sdcard/TestRoomC.apk", "/mnt/sdcard/");
root.addView(roomC.getView());
}
}
说明:
- 创建RoomUtils实例,看到APK所在目录和DEX输出目录都在SDCARD下;
- 添加按钮到根视图中;
Room A中主要操作:
import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.Toast;
public class TestRoomAActivity extends Activity implements OnClickListener {
/**
* TestHallActivity的上下文引用
*/
private Activity otherActivity;
/**
* 无参构造函数
*/
public TestRoomAActivity() {
super();
}
/**
* 有参构造函数
* @param context
*/
public TestRoomAActivity(Context context) {
super();
otherActivity = (Activity) context;
}
@Override
public void onCreate(Bundle savedInstanceState) {
if (otherActivity != null) {
// 使用TestHallActivity的上下文引用创建View并添加到根视图
Button button = new Button(otherActivity);
button.setText("Room A");
button.setOnClickListener(this);
LinearLayout root = new LinearLayout(otherActivity);
root.addView(button);
// setContentView(root);使用的上下文是当前Activity的,而不是指定的TestHallActivity
otherActivity.setContentView(root);
} else {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
}
/**
* 返回当前Activity的描述信息
* @return
*/
private String getRoomMessage() {
return "Room A";
}
@Override
public void onClick(View v) {
if (otherActivity != null) {
// Toast.makeText(this, "This is Room A!", Toast.LENGTH_SHORT).show();是不合适的调用
Toast.makeText(otherActivity, "This is Room A!", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(this, "This is Room A!", Toast.LENGTH_SHORT).show();
}
}
}
说明:
- otherActivity:Hall对应上下文的引用;
- public TestRoomAActivity(Context context);有参构造函数;
- 当用到上下文的地方使用otherActivity代替,这样就可以使用otherActivity对应的Context做相关交互操作;
效果图如下:
说明:
- 由于使用的Context是Hall的,所以如果点击Back按钮,直接退出示例程序;
- Room B, Room C都和Room A类似,详见附件;
多说一句:
- 由于Hall和Room X使用同一个Context,在点击Back后,直接退出应用程序,是否有比较合适的解决方案(肯定有,要看具体要求而定罢了!+_+);
- Room X中只添加了一个Activity,对于多Activity的加载,跳转操作还需要测试;
- 如何做进一步调整;
- 相关代码见附件!:)