我使用DexClassLoader从Service
onCreate()
回调中的外部 dex 文件动态加载Android MyActivity
类:
public class MyActivity extends Activity {
private Class<Object> myServiceClass;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
String dexFile = "path/to/dexFile.dex";
DexClassLoader classLoader = new DexClassLoader(dexFile, getDir("tmp", 0).getAbsolutePath(), null, this.getClass().getClassLoader());
myServiceClass = (Class<Object>) classloader.loadClass("com.test.MyService");
//Here, I am able to use java relfection to successfully get those methods in myServiceClass.
//so, no problem here!
}
@Override
protected void onStart() {
super.onStart();
//PROBLEM HERE: I get null, failed to start service, why?
ComponentName name = startService(new Intent(this, myServiceClass.getClass()));
}
我还在 AndroidManifest.xml 中声明了MyService。
<service
android:name="com.test.MyService"
/>
为什么在onStart()
的{{1}}回调中启动我的服务时,我为空?
================更新(startService()现在返回组件名称)==========
我改为使用MyActivity
以上ComponentName name = startService(new Intent(this, myServiceClass));
会返回以下组件名称:
startService(...)
但我的 logcat 也向我显示错误:
ComponentInfo{com.project.myapp/com.test.MyService}
答案 0 :(得分:4)
虽然您通过自定义Service
成功加载了ClassLoader
课程,但Android API调用很快就会忘记实际的Class
实例:Intent
只会删除Class
1}}并保留其名称:
来自android.content.Intent
(API 18):
public Intent(Context packageContext, Class<?> cls) {
mComponent = new ComponentName(packageContext, cls);
}
来自android.content.ComponentName
:
public ComponentName(Context pkg, Class<?> cls) {
mPackage = pkg.getPackageName();
mClass = cls.getName();
}
android.app.ActivityThread
然后创建Service
实例,如下所示:
LoadedApk packageInfo = getPackageInfoNoCheck(
data.info.applicationInfo, data.compatInfo);
Service service = null;
try {
java.lang.ClassLoader cl = packageInfo.getClassLoader();
service = (Service) cl.loadClass(data.info.name).newInstance();
} catch (Exception e) {
if (!mInstrumentation.onException(service, e)) {
throw new RuntimeException(
"Unable to instantiate service " + data.info.name
+ ": " + e.toString(), e);
}
}
包类加载器将无法加载由自定义DexClassLoader加载的类。问题是缓存是 - 如果我没记错的话,已经有一段时间了,因为我必须实现自己的ClassLoader
- 由每个ClassLoader
执行,例如子ClassLoader
缓存它加载的Class
,父母无法访问它。
<强>更新强>
解决方法是包装器服务,它将所有调用委托给可以使用任何Service
加载的(ClassLoader
)实例。这可能会变得非常混乱,但它也可能完全解决问题。
假设您拥有包装服务 class WrapperService extends Service
,可以从您的dex文件加载interface ExtService
和class MyServiceClass implements ExtService
(其中MyServiceClass
) :
public WrapperService()
实例化DexClassLoader
,加载MyServiceClass
并将实例强制转换为存储在实例字段中的ExtService
。MyServiceClass
获取WrapperService
的引用,作为构造函数参数或通过ExtService
中定义的方法。ExtService
声明必须由WrapperService
调用的所有方法。WrapperService
实例委托对ExtService
的方法进行必要的方法调用。更新2:
我刚刚阅读了Android应用程序ClassLoader的一些部分(PathClassLoader
extends BaseDexClassLoader
),它使用final DexPathList
本身包含固定大小的final
数组。这使得常见的JAVA技巧(例如向URLClassLoader
添加网址)不可能,即使SecurityManager
允许这样做。