android中可以通过反射加载另一个进程的资源,可以通过AIDL传递资源包名和资源名称,通过包名和资源名称就可以加载资源来使用。下面介绍跨进程加载layout布局的流程。
服务端新建AIDL文件,只有addReflectionView和removeReflectionView添加和移除反射View的接口。
// IViewReflectionManager.aidl
package com.example.viewreflectiontest;
import android.os.Bundle;
interface IViewReflectionManager {
void addReflectionView(in Bundle bundle);
void removeReflectionView();
}
服务端通过Bundle获取客户端传入的包名和布局名称,然后通过反射资源id得到客户端的布局id,有了这个id就可以加载到布局View实例。
private final IViewReflectionManager mBinder = new IViewReflectionManager.Stub() {
@Override
public void addReflectionView(Bundle bundle) throws RemoteException {
String packageName = bundle.getString("package", null);
String layout = bundle.getString("layout", null);
try {
Context packageContext = createPackageContext(packageName,
Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY);
Class<?> cls = packageContext.getClassLoader().loadClass(packageName + ".R"); // 获得目标apk的R类
int layoutId = getResourceIdByName(cls, "layout", layout);
mView = LayoutInflater.from(packageContext).inflate(layoutId, null);
mUIHandler.post(new Runnable() {
@Override
public void run() {
if (!mIsWindowAdd) {
mIsWindowAdd = true;
mWindowManager.addView(mView, mLayoutParams);
}
}
});
} catch (PackageManager.NameNotFoundException | ClassNotFoundException e) {
e.printStackTrace();
}
}
@Override
public void removeReflectionView() throws RemoteException {
mUIHandler.post(new Runnable() {
@Override
public void run() {
if (mIsWindowAdd) {
mIsWindowAdd = false;
mWindowManager.removeView(mView);
}
}
});
}
};
通过反射获取不同进程的资源文件方法getResourceIdByName如下:
private int getResourceIdByName(Class<?> clazz, String className, String name) {
int id = 0;
try {
Class<?>[] classes = clazz.getClasses(); // 获取R.java里的所有静态内部类
Class<?> desireClass = null;
for (Class<?> aClass : classes) {
if (aClass.getName().split("\\$")[1].equals(className)) { // 查找指定的静态内部类
desireClass = aClass;
break;
}
}
if (desireClass != null) {
id = desireClass.getField(name).getInt(desireClass); // 从指定的静态内部类获得资源编号
}
} catch (IllegalArgumentException | SecurityException | IllegalAccessException | NoSuchFieldException e) {
e.printStackTrace();
}
return id;
}
客户端绑定服务端,调用接口即可动态让服务端加载客户端的布局。
Intent intent = new Intent("com.example.viewreflectiontest.action");
intent.setPackage("com.example.viewreflectiontest");
bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
btnAdd.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mIViewReflectionManager != null) {
Bundle bundle = new Bundle();
bundle.putString("package", "com.example.myapplication");
bundle.putString("layout", "layout_reflection");
try {
mIViewReflectionManager.addReflectionView(bundle);
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
});
btnRemove.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mIViewReflectionManager != null) {
try {
mIViewReflectionManager.removeReflectionView();
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
});
需要注意赋予服务端悬浮窗的权限才可正常添加窗口。
最终效果如下: