启动服务(按类)在我的情况下返回null

时间:2013-12-20 17:32:05

标签: java android reflection

我使用DexClassLoaderService 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}

1 个答案:

答案 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 ExtServiceclass 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允许这样做。