前言

关于插件,已经在各大平台上出现过很多,eclipse插件、chrome插件、3dmax插件,所有这些插件大概都为了在一个主程序中实现比较通用的功能,把业务相关或者让可以让用户自定义扩展的功能不附加在主程序中,主程序可在运行时安装和卸载。在android如何实现插件也已经被广泛传播,实现的原理都是实现一套插件接口,把插件实现编成apk或者dex,然后在运行时使用DexClassLoader动态加载进来,不过在这个开发过程中会遇到很多的问题,所以这一片就先不介绍如何开发插件,而是先解决一下开发过程中会遇到的问题,这里主要就是介绍DexClassLoader这个类使用的过程中出现的错误


导读

Java中的类加载器:http://blog.csdn.net/jiangwei0910410003/article/details/17733153

Android中的动态加载机制:http://blog.csdn.net/jiangwei0910410003/article/details/17679823

System.loadLibrary的执行过程:http://blog.csdn.net/jiangwei0910410003/article/details/41490133


一、预备知识

Android中的各种加载器介绍

插件开发的过程中DexClassLoader和PathClassLoader这两个类加载器了是很重要的,但是他们也是有区别的,而且我们也知道PathClassLoader是Android应用中的默认加载器。他们的区别是:

DexClassLoader可以加载任何路径的apk/dex/jar

PathClassLoader只能加载/data/app中的apk,也就是已经安装到手机中的apk。这个也是PathClassLoader作为默认的类加载器的原因,因为一般程序都是安装了,在打开,这时候PathClassLoader就去加载指定的apk(解压成dex,然后在优化成odex)就可以了。


我们可以看一下他们的源码:

DexClassLoader.java

[java] view plain copy
  1. /*
  2. *Copyright(C)2008TheAndroidOpenSourceProject
  3. *
  4. *LicensedundertheApacheLicense,Version2.0(the"License");
  5. *youmaynotusethisfileexceptincompliancewiththeLicense.
  6. *YoumayobtainacopyoftheLicenseat
  7. *
  8. *http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. *Unlessrequiredbyapplicablelaworagreedtoinwriting,software
  11. *distributedundertheLicenseisdistributedonan"ASIS"BASIS,
  12. *WITHOUTWARRANTIESORCONDITIONSOFANYKIND,eitherexpressorimplied.
  13. *SeetheLicenseforthespecificlanguagegoverningpermissionsand
  14. *limitationsundertheLicense.
  15. */
  16. packagedalvik.system;
  17. importjava.io.File;
  18. importjava.io.IOException;
  19. importjava.net.MalformedURLException;
  20. importjava.net.URL;
  21. importjava.util.zip.ZipFile;
  22. /**
  23. *Providesasimple{@linkClassLoader}implementationthatoperatesona
  24. *listofjar/apkfileswithclasses.dexentries.Thedirectorythat
  25. *holdstheoptimizedformofthefilesisspecifiedexplicitly.This
  26. *canbeusedtoexecutecodenotinstalledaspartofanapplication.
  27. *
  28. *ThebestplacetoputtheoptimizedDEXfilesisinapp-specific
  29. *storage,sothatremovaloftheappwillautomaticallyremovethe
  30. *optimizedDEXfiles.Ifotherstorageisused(e.g./sdcard),the
  31. *appmaynothaveanopportunitytoremovethem.
  32. */
  33. publicclassDexClassLoaderextendsClassLoader{
  34. privatestaticfinalbooleanVERBOSE_DEBUG=false;
  35. /*constructorargs,heldforinit*/
  36. privatefinalStringmRawDexPath;
  37. privatefinalStringmRawLibPath;
  38. privatefinalStringmDexOutputPath;
  39. /*
  40. *Parallelarraysforjar/apkfiles.
  41. *
  42. *(couldstufftheseintoanobjectandhaveasinglearray;
  43. *improvesclaritybutaddsoverhead)
  44. */
  45. privatefinalFile[]mFiles;//sourcefileFiles,forrsrcURLs
  46. privatefinalZipFile[]mZips;//sourcezipfiles,withresources
  47. privatefinalDexFile[]mDexs;//opened,preppedDEXfiles
  48. /**
  49. *Nativelibrarypath.
  50. */
  51. privatefinalString[]mLibPaths;
  52. /**
  53. *Createsa{@codeDexClassLoader}thatfindsinterpretedandnative
  54. *code.InterpretedclassesarefoundinasetofDEXfilescontained
  55. *inJarorAPKfiles.
  56. *
  57. *Thepathlistsareseparatedusingthecharacterspecifiedby
  58. *the"path.separator"systemproperty,whichdefaultsto":".
  59. *
  60. *@paramdexPath
  61. *thelistofjar/apkfilescontainingclassesandresources
  62. *@paramdexOutputDir
  63. *directorywhereoptimizedDEXfilesshouldbewritten
  64. *@paramlibPath
  65. *thelistofdirectoriescontainingnativelibraries;maybenull
  66. *@paramparent
  67. *theparentclassloader
  68. */
  69. publicDexClassLoader(StringdexPath,StringdexOutputDir,StringlibPath,
  70. ClassLoaderparent){
  71. super(parent);
  72. ......
我们看到,他是继承了ClassLoader类的,ClassLoader是类加载器的鼻祖类。同时我们也会发现DexClassLoader只有一个构造函数,而且这个构造函数是:dexPath、dexOutDir、libPath、parent

dexPath:是加载apk/dex/jar的路径

dexOutDir:是dex的输出路径(因为加载apk/jar的时候会解压除dex文件,这个路径就是保存dex文件的)

libPath:是加载的时候需要用到的lib库,这个一般不用

parent:给DexClassLoader指定父加载器


我们在来看一下PathClassLoader的源码

PathClassLoader.java

[java] view plain copy
  1. /*
  2. *Copyright(C)2007TheAndroidOpenSourceProject
  3. *
  4. *LicensedundertheApacheLicense,Version2.0(the"License");
  5. *youmaynotusethisfileexceptincompliancewiththeLicense.
  6. *YoumayobtainacopyoftheLicenseat
  7. *
  8. *http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. *Unlessrequiredbyapplicablelaworagreedtoinwriting,software
  11. *distributedundertheLicenseisdistributedonan"ASIS"BASIS,
  12. *WITHOUTWARRANTIESORCONDITIONSOFANYKIND,eitherexpressorimplied.
  13. *SeetheLicenseforthespecificlanguagegoverningpermissionsand
  14. *limitationsundertheLicense.
  15. */
  16. packagedalvik.system;
  17. importjava.io.ByteArrayOutputStream;
  18. importjava.io.File;
  19. importjava.io.FileNotFoundException;
  20. importjava.io.IOException;
  21. importjava.io.InputStream;
  22. importjava.io.RandomAccessFile;
  23. importjava.net.MalformedURLException;
  24. importjava.net.URL;
  25. importjava.util.ArrayList;
  26. importjava.util.Enumeration;
  27. importjava.util.List;
  28. importjava.util.NoSuchElementException;
  29. importjava.util.zip.ZipEntry;
  30. importjava.util.zip.ZipFile;
  31. /**
  32. *Providesasimple{@linkClassLoader}implementationthatoperatesonalist
  33. *offilesanddirectoriesinthelocalfilesystem,butdoesnotattemptto
  34. *loadclassesfromthenetwork.Androidusesthisclassforitssystemclass
  35. *loaderandforitsapplicationclassloader(s).
  36. */
  37. publicclassPathClassLoaderextendsClassLoader{
  38. privatefinalStringpath;
  39. privatefinalStringlibPath;
  40. /*
  41. *Parallelarraysforjar/apkfiles.
  42. *
  43. *(couldstufftheseintoanobjectandhaveasinglearray;
  44. *improvesclaritybutaddsoverhead)
  45. */
  46. privatefinalString[]mPaths;
  47. privatefinalFile[]mFiles;
  48. privatefinalZipFile[]mZips;
  49. privatefinalDexFile[]mDexs;
  50. /**
  51. *Nativelibrarypath.
  52. */
  53. privatefinalList<String>libraryPathElements;
  54. /**
  55. *Createsa{@codePathClassLoader}thatoperatesonagivenlistoffiles
  56. *anddirectories.Thismethodisequivalenttocalling
  57. *{@link#PathClassLoader(String,String,ClassLoader)}witha
  58. *{@codenull}valueforthesecondargument(seedescriptionthere).
  59. *
  60. *@parampath
  61. *thelistoffilesanddirectories
  62. *
  63. *@paramparent
  64. *theparentclassloader
  65. */
  66. publicPathClassLoader(Stringpath,ClassLoaderparent){
  67. this(path,null,parent);
  68. }
  69. /**
  70. *Createsa{@codePathClassLoader}thatoperatesontwogiven
  71. *listsoffilesanddirectories.Theentriesofthefirstlist
  72. *shouldbeoneofthefollowing:
  73. *
  74. *<ul>
  75. *<li>Directoriescontainingclassesorresources.
  76. *<li>JAR/ZIP/APKfiles,possiblycontaininga"classes.dex"file.
  77. *<li>"classes.dex"files.
  78. *</ul>
  79. *
  80. *Theentriesofthesecondlistshouldbedirectoriescontaining
  81. *nativelibraryfiles.Bothlistsareseparatedusingthe
  82. *characterspecifiedbythe"path.separator"systemproperty,
  83. *which,onAndroid,defaultsto":".
  84. *
  85. *@parampath
  86. *thelistoffilesanddirectoriescontainingclassesand
  87. *resources
  88. *
  89. *@paramlibPath
  90. *thelistofdirectoriescontainingnativelibraries
  91. *
  92. *@paramparent
  93. *theparentclassloader
  94. */
  95. publicPathClassLoader(Stringpath,StringlibPath,ClassLoaderparent){
  96. super(parent);
  97. ....
看到了PathClassLoader类也是继承了ClassLoader的,但是他的构造函数和DexClassLoader有点区别就是,少了一个dexOutDir,这个原因也是很简单,因为PathClassLoader是加载/data/app中的apk,而这部分的apk都会解压释放dex到指定的目录:

/data/dalvik-cache


这个释放解压操作是系统做的。所以PathClassLoader可以不需要这个参数的。


上面看了他们两的区别,下面在来看一下Android中的各种类加载器分别加载哪些类:

[java] view plain copy
  1. packagecom.example.androiddemo;
  2. importandroid.app.Activity;
  3. importandroid.content.Context;
  4. importandroid.os.Bundle;
  5. importandroid.util.Log;
  6. importandroid.widget.ListView;
  7. publicclassMainActivityextendsActivity{
  8. @Override
  9. protectedvoidonCreate(BundlesavedInstanceState){
  10. super.onCreate(savedInstanceState);
  11. setContentView(R.layout.activity_main);
  12. Log.i("DEMO","Context的类加载加载器:"+Context.class.getClassLoader());
  13. Log.i("DEMO","ListView的类加载器:"+ListView.class.getClassLoader());
  14. Log.i("DEMO","应用程序默认加载器:"+getClassLoader());
  15. Log.i("DEMO","系统类加载器:"+ClassLoader.getSystemClassLoader());
  16. Log.i("DEMO","系统类加载器和Context的类加载器是否相等:"+(Context.class.getClassLoader()==ClassLoader.getSystemClassLoader()));
  17. Log.i("DEMO","系统类加载器和应用程序默认加载器是否相等:"+(getClassLoader()==ClassLoader.getSystemClassLoader()));
  18. Log.i("DEMO","打印应用程序默认加载器的委派机制:");
  19. ClassLoaderclassLoader=getClassLoader();
  20. while(classLoader!=null){
  21. Log.i("DEMO","类加载器:"+classLoader);
  22. classLoader=classLoader.getParent();
  23. }
  24. Log.i("DEMO","打印系统加载器的委派机制:");
  25. classLoader=ClassLoader.getSystemClassLoader();
  26. while(classLoader!=null){
  27. Log.i("DEMO","类加载器:"+classLoader);
  28. classLoader=classLoader.getParent();
  29. }
  30. }
  31. }
运行结果:



依次来看一下

1) 系统类的加载器

[java] view plain copy
  1. Log.i("DEMO","Context的类加载加载器:"+Context.class.getClassLoader());
  2. Log.i("DEMO","ListView的类加载器:"+ListView.class.getClassLoader());
从结果看到他们的加载器是:BootClassLoader,关于他源码我没有找到,只找到了class文件(用jd-gui查看):


看到他也是继承了ClassLoader类。


2) 应用程序的默认加载器

[java] view plain copy
  1. Log.i("DEMO","应用程序默认加载器:"+getClassLoader());
运行结果:


默认类加载器是PathClassLoader,同时可以看到加载的apk路径,libPath(一般包括/vendor/lib和/system/lib)


3) 系统类加载器

[java] view plain copy
  1. Log.i("DEMO","系统类加载器:"+ClassLoader.getSystemClassLoader());
运行结果:


系统类加载器其实还是PathClassLoader,只是加载的apk路径不是/data/app/xxx.apk了,而是系统apk的路径:/system/app/xxx.apk


4) 默认加载器的委派机制关系

[java] view plain copy
  1. Log.i("DEMO","打印应用程序默认加载器的委派机制:");
  2. ClassLoaderclassLoader=getClassLoader();
  3. while(classLoader!=null){
  4. Log.i("DEMO","类加载器:"+classLoader);
  5. classLoader=classLoader.getParent();
  6. }
打印结果:

默认加载器PathClassLoader的父亲是BootClassLoader

5) 系统加载器的委派机制关系

[java] view plain copy
  1. Log.i("DEMO","打印系统加载器的委派机制:");
  2. classLoader=ClassLoader.getSystemClassLoader();
  3. while(classLoader!=null){
  4. Log.i("DEMO","类加载器:"+classLoader);
  5. classLoader=classLoader.getParent();
  6. }
运行结果:


可以看到系统加载器的父亲也是BootClassLoader


二、分析遇到的问题的原因和解决办法

DexClassLoader加载原理和分析在实现插件时不同操作造成错误的原因分析

这里主要用了三个工程:

PluginImpl:插件接口工程(只是接口的定义)

PluginSDK:插件工程(实现插件接口,定义具体的功能)

HostProject:宿主工程(需要引用插件接口工程,然后动态的加载插件工程)(例子项目中名字是PluginDemos)



第一、项目介绍

下面来看一下源代码:

1、PluginImpl工程:

1) IBean.java

[java] view plain copy
  1. packagecom.pluginsdk.interfaces;
  2. publicabstractinterfaceIBean{
  3. publicabstractStringgetName();
  4. publicabstractvoidsetName(StringparamString);
  5. }


2) IDynamic.java [java] view plain copy
  1. packagecom.pluginsdk.interfaces;
  2. importandroid.content.Context;
  3. publicabstractinterfaceIDynamic{
  4. publicabstractvoidmethodWithCallBack(YKCallBackparamYKCallBack);
  5. publicabstractvoidshowPluginWindow(ContextparamContext);
  6. publicabstractvoidstartPluginActivity(Contextcontext,Class<?>cls);
  7. publicabstractStringgetStringForResId(Contextcontext);
  8. }
其他的就不列举了。


2、PluginSDK工程:

1) Dynamic.java

[java] view plain copy
  1. /**
  2. *Dynamic1.java
  3. *com.youku.pluginsdk.imp
  4. *
  5. *Function:TODO
  6. *
  7. *verdateauthor
  8. *──────────────────────────────────
  9. *2014-10-20Administrator
  10. *
  11. *Copyright(c)2014,TNTAllRightsReserved.
  12. */
  13. packagecom.pluginsdk.imp;
  14. importandroid.app.AlertDialog;
  15. importandroid.app.AlertDialog.Builder;
  16. importandroid.app.Dialog;
  17. importandroid.content.Context;
  18. importandroid.content.DialogInterface;
  19. importandroid.content.Intent;
  20. importcom.pluginsdk.bean.Bean;
  21. importcom.pluginsdk.interfaces.IDynamic;
  22. importcom.pluginsdk.interfaces.YKCallBack;
  23. importcom.youku.pluginsdk.R;
  24. /**
  25. *ClassName:Dynamic1
  26. *
  27. *@authorjiangwei
  28. *@version
  29. *@sinceVer1.1
  30. *@Date2014-10-20下午5:57:10
  31. */
  32. publicclassDynamicimplementsIDynamic{
  33. /**
  34. */
  35. publicvoidmethodWithCallBack(YKCallBackcallback){
  36. Beanbean=newBean();
  37. bean.setName("PLUGIN_SDK_USER");
  38. callback.callback(bean);
  39. }
  40. publicvoidshowPluginWindow(Contextcontext){
  41. AlertDialog.Builderbuilder=newBuilder(context);
  42. builder.setMessage("对话框");
  43. builder.setTitle(R.string.hello_world);
  44. builder.setNegativeButton("取消",newDialog.OnClickListener(){
  45. @Override
  46. publicvoidonClick(DialogInterfacedialog,intwhich){
  47. dialog.dismiss();
  48. }
  49. });
  50. Dialogdialog=builder.create();//.show();
  51. dialog.show();
  52. }
  53. publicvoidstartPluginActivity(Contextcontext,Class<?>cls){
  54. /**
  55. *这里要注意几点:
  56. *1、如果单纯的写一个MainActivity的话,在主工程中也有一个MainActivity,开启的Activity还是主工程中的MainActivity
  57. *2、如果这里将MainActivity写成全名的话,还是有问题,会报找不到这个Activity的错误
  58. */
  59. Intentintent=newIntent(context,cls);
  60. context.startActivity(intent);
  61. }
  62. publicStringgetStringForResId(Contextcontext){
  63. returncontext.getResources().getString(R.string.hello_world);
  64. }
  65. }


2) Bean.java [java] view plain copy
  1. /**
  2. *User.java
  3. *com.youku.pluginsdk.bean
  4. *
  5. *Function:TODO
  6. *
  7. *verdateauthor
  8. *──────────────────────────────────
  9. *2014-10-20Administrator
  10. *
  11. *Copyright(c)2014,TNTAllRightsReserved.
  12. */
  13. packagecom.pluginsdk.bean;
  14. /**
  15. *ClassName:User
  16. *
  17. *@authorjiangwei
  18. *@version
  19. *@sinceVer1.1
  20. *@Date2014-10-20下午1:35:16
  21. */
  22. publicclassBeanimplementscom.pluginsdk.interfaces.IBean{
  23. /**
  24. *
  25. */
  26. privateStringname="这是来自于插件工程中设置的初始化的名字";
  27. publicStringgetName(){
  28. returnname;
  29. }
  30. publicvoidsetName(Stringname){
  31. this.name=name;
  32. }
  33. }

3、宿主工程HostProject

1) MainActivity.java

[java] view plain copy
  1. packagecom.plugindemo;
  2. importjava.io.File;
  3. importjava.lang.reflect.Method;
  4. importandroid.annotation.SuppressLint;
  5. importandroid.app.Activity;
  6. importandroid.content.Context;
  7. importandroid.content.res.AssetManager;
  8. importandroid.content.res.Resources;
  9. importandroid.content.res.Resources.Theme;
  10. importandroid.os.Bundle;
  11. importandroid.os.Environment;
  12. importandroid.util.Log;
  13. importandroid.view.View;
  14. importandroid.widget.Button;
  15. importandroid.widget.ListView;
  16. importandroid.widget.Toast;
  17. importcom.pluginsdk.interfaces.IBean;
  18. importcom.pluginsdk.interfaces.IDynamic;
  19. importcom.pluginsdk.interfaces.YKCallBack;
  20. importcom.youku.plugindemo.R;
  21. importdalvik.system.DexClassLoader;
  22. publicclassMainActivityextendsActivity{
  23. privateAssetManagermAssetManager;//资源管理器
  24. privateResourcesmResources;//资源
  25. privateThememTheme;//主题
  26. privateStringapkFileName="PluginSDKs.apk";
  27. privateStringdexpath=null;//apk文件地址
  28. privateFilefileRelease=null;//释放目录
  29. privateDexClassLoaderclassLoader=null;
  30. @SuppressLint("NewApi")
  31. @Override
  32. protectedvoidonCreate(BundlesavedInstanceState){
  33. super.onCreate(savedInstanceState);
  34. setContentView(R.layout.activity_main);
  35. dexpath=Environment.getExternalStorageDirectory()+File.separator+apkFileName;
  36. fileRelease=getDir("dex",0);
  37. /*初始化classloader
  38. *dexpathdex文件地址
  39. *fileRelease文件释放地址
  40. *父classLoader
  41. */
  42. Log.d("DEMO",(getClassLoader()==ListView.class.getClassLoader())+"");
  43. Log.d("DEMO",ListView.class.getClassLoader()+"");
  44. Log.d("DEMO",Context.class.getClassLoader()+"");
  45. Log.d("DEMO",Context.class.getClassLoader().getSystemClassLoader()+"");
  46. Log.d("DEMO",Activity.class.getClassLoader()+"");
  47. Log.d("DEMO",(Context.class.getClassLoader().getSystemClassLoader()==ClassLoader.getSystemClassLoader())+"");
  48. Log.d("DEMO",ClassLoader.getSystemClassLoader()+"");
  49. classLoader=newDexClassLoader(dexpath,fileRelease.getAbsolutePath(),null,getClassLoader());
  50. Buttonbtn_1=(Button)findViewById(R.id.btn_1);
  51. Buttonbtn_2=(Button)findViewById(R.id.btn_2);
  52. Buttonbtn_3=(Button)findViewById(R.id.btn_3);
  53. Buttonbtn_4=(Button)findViewById(R.id.btn_4);
  54. Buttonbtn_5=(Button)findViewById(R.id.btn_5);
  55. Buttonbtn_6=(Button)findViewById(R.id.btn_6);
  56. btn_1.setOnClickListener(newView.OnClickListener(){//普通调用反射的方式
  57. @Override
  58. publicvoidonClick(Viewarg0){
  59. ClassmLoadClassBean;
  60. try{
  61. mLoadClassBean=classLoader.loadClass("com.pluginsdk.bean.Bean");
  62. ObjectbeanObject=mLoadClassBean.newInstance();
  63. Log.d("DEMO","ClassLoader:"+mLoadClassBean.getClassLoader());
  64. Log.d("DEMO","ClassLoader:"+mLoadClassBean.getClassLoader().getParent());
  65. MethodgetNameMethod=mLoadClassBean.getMethod("getName");
  66. getNameMethod.setAccessible(true);
  67. Stringname=(String)getNameMethod.invoke(beanObject);
  68. Toast.makeText(MainActivity.this,name,Toast.LENGTH_SHORT).show();
  69. }catch(Exceptione){
  70. Log.e("DEMO","msg:"+e.getMessage());
  71. }
  72. }
  73. });
  74. btn_2.setOnClickListener(newView.OnClickListener(){//带参数调用
  75. @Override
  76. publicvoidonClick(Viewarg0){
  77. ClassmLoadClassBean;
  78. try{
  79. mLoadClassBean=classLoader.loadClass("com.pluginsdk.bean.Bean");
  80. ObjectbeanObject=mLoadClassBean.newInstance();
  81. //接口形式调用
  82. Log.d("DEMO",beanObject.getClass().getClassLoader()+"");
  83. Log.d("DEMO",IBean.class.getClassLoader()+"");
  84. Log.d("DEMO",ClassLoader.getSystemClassLoader()+"");
  85. IBeanbean=(IBean)beanObject;
  86. bean.setName("宿主程序设置的新名字");
  87. Toast.makeText(MainActivity.this,bean.getName(),Toast.LENGTH_SHORT).show();
  88. }catch(Exceptione){
  89. Log.e("DEMO","msg:"+e.getMessage());
  90. }
  91. }
  92. });
  93. btn_3.setOnClickListener(newView.OnClickListener(){//带回调函数的调用
  94. @Override
  95. publicvoidonClick(Viewarg0){
  96. ClassmLoadClassDynamic;
  97. try{
  98. mLoadClassDynamic=classLoader.loadClass("com.pluginsdk.imp.Dynamic");
  99. ObjectdynamicObject=mLoadClassDynamic.newInstance();
  100. //接口形式调用
  101. IDynamicdynamic=(IDynamic)dynamicObject;
  102. //回调函数调用
  103. YKCallBackcallback=newYKCallBack(){//回调接口的定义
  104. publicvoidcallback(IBeanarg0){
  105. Toast.makeText(MainActivity.this,arg0.getName(),Toast.LENGTH_SHORT).show();
  106. };
  107. };
  108. dynamic.methodWithCallBack(callback);
  109. }catch(Exceptione){
  110. Log.e("DEMO","msg:"+e.getMessage());
  111. }
  112. }
  113. });
  114. btn_4.setOnClickListener(newView.OnClickListener(){//带资源文件的调用
  115. @Override
  116. publicvoidonClick(Viewarg0){
  117. loadResources();
  118. ClassmLoadClassDynamic;
  119. try{
  120. mLoadClassDynamic=classLoader.loadClass("com.pluginsdk.imp.Dynamic");
  121. ObjectdynamicObject=mLoadClassDynamic.newInstance();
  122. //接口形式调用
  123. IDynamicdynamic=(IDynamic)dynamicObject;
  124. dynamic.showPluginWindow(MainActivity.this);
  125. }catch(Exceptione){
  126. Log.e("DEMO","msg:"+e.getMessage());
  127. }
  128. }
  129. });
  130. btn_5.setOnClickListener(newView.OnClickListener(){//带资源文件的调用
  131. @Override
  132. publicvoidonClick(Viewarg0){
  133. loadResources();
  134. ClassmLoadClassDynamic;
  135. try{
  136. mLoadClassDynamic=classLoader.loadClass("com.pluginsdk.imp.Dynamic");
  137. ObjectdynamicObject=mLoadClassDynamic.newInstance();
  138. //接口形式调用
  139. IDynamicdynamic=(IDynamic)dynamicObject;
  140. dynamic.startPluginActivity(MainActivity.this,
  141. classLoader.loadClass("com.plugindemo.MainActivity"));
  142. }catch(Exceptione){
  143. Log.e("DEMO","msg:"+e.getMessage());
  144. }
  145. }
  146. });
  147. btn_6.setOnClickListener(newView.OnClickListener(){//带资源文件的调用
  148. @Override
  149. publicvoidonClick(Viewarg0){
  150. loadResources();
  151. ClassmLoadClassDynamic;
  152. try{
  153. mLoadClassDynamic=classLoader.loadClass("com.pluginsdk.imp.Dynamic");
  154. ObjectdynamicObject=mLoadClassDynamic.newInstance();
  155. //接口形式调用
  156. IDynamicdynamic=(IDynamic)dynamicObject;
  157. Stringcontent=dynamic.getStringForResId(MainActivity.this);
  158. Toast.makeText(getApplicationContext(),content+"",Toast.LENGTH_LONG).show();
  159. }catch(Exceptione){
  160. Log.e("DEMO","msg:"+e.getMessage());
  161. }
  162. }
  163. });
  164. }
  165. protectedvoidloadResources(){
  166. try{
  167. AssetManagerassetManager=AssetManager.class.newInstance();
  168. MethodaddAssetPath=assetManager.getClass().getMethod("addAssetPath",String.class);
  169. addAssetPath.invoke(assetManager,dexpath);
  170. mAssetManager=assetManager;
  171. }catch(Exceptione){
  172. e.printStackTrace();
  173. }
  174. ResourcessuperRes=super.getResources();
  175. superRes.getDisplayMetrics();
  176. superRes.getConfiguration();
  177. mResources=newResources(mAssetManager,superRes.getDisplayMetrics(),superRes.getConfiguration());
  178. mTheme=mResources.newTheme();
  179. mTheme.setTo(super.getTheme());
  180. }
  181. @Override
  182. publicAssetManagergetAssets(){
  183. returnmAssetManager==null?super.getAssets():mAssetManager;
  184. }
  185. @Override
  186. publicResourcesgetResources(){
  187. returnmResources==null?super.getResources():mResources;
  188. }
  189. @Override
  190. publicThemegetTheme(){
  191. returnmTheme==null?super.getTheme():mTheme;
  192. }
  193. }
三个工程的下载地址:http://download.csdn.net/detail/jiangwei0910410003/8188011

第二、项目引用关系

工程文件现在大致看完了,我们看一下他们的引用关系吧:

1、将接口工程PluginImpl设置成一个Library



2、插件工程PluginSDKs引用插件的jar

注意是lib文件夹,不是libs,这个是有区别的,后面会说道



3、HostProject项目引用PluginImpl这个library



项目引用完成之后,我们编译PluginSDKs项目,生成PluginSDKs.apk放到手机的sdcard的根目录(因为我代码中是从这个目录进行加载apk的,当然这个目录是可以修改的),然后运行HostProject

看到效果了吧。运行成功,其实这个对话框是在插件中定义的,但是我们知道定义对话框是需要context变量的,所以这个变量就是通过参数从宿主工程中传递到插件工程即可,成功了就不能这么了事,因为我还没有说道我遇到的问题,下面就来看一下遇到的几个问题


三、问题分析

问题一:Could not find class...(找不到指定的类)


这个问题产生的操作:

插件工程PluginSDKs的引用方式不变,宿主工程PluginDemos的引用方式改变


在说这个原因之前先来了解一下Eclipse中引用工程的不同方式和区别

第一种:最常用的将引用工程打成jar放到需要引用工程的libs下面(这里是将PluginImpl打成jar,放到HostProject工程的libs中)

这种方式是Eclipse推荐使用的,当我们在建立一个项目的时候也会自动产生这个文件夹,当我们将我们需要引用的工程打成jar,然后放到这个文件夹之后,Eclipse就自动导入了(这个功能是Eclipse3.7之后有的)。

第二种:和第一种的区别是,我们可以从新新建一个文件夹比如是lib,然后将引用的jar放到这个文件夹中,但是此时Eclipse是不会自动导入的,需要我们手动的导入(add build path...),但是这个是一个区别,还有一个区别,也是到这个这个报错原因的区别,就是libs文件夹中的jar,在运行的时候是会将这个jar集成到程序中的,而我们新建的文件夹(名字非libs即可),及时我们手动的导入,编译是没有问题的,但是运行的时候,是不会将jar集成到程序中。

第三种:和前两种的区别是不需要将引用工程打成jar,直接引用这个工程


这种方式其实效果和第一种差不多,唯一的区别就是不需要打成jar,但是运行的时候是不会将引用工程集成到程序中的。

第四种:和第三种的方式是一样的,也是不需要将引用工程打成jar,直接引用工程:


这个前提是需要设置PluginImpl项目为Library,同时引用的项目和被引用的项目必须在一个工作空间中,不然会报错,这种的效果和第二种是一样的,在运行的时候是会将引用工程集成到程序中的。

第五种:和第一种、第二种差不多,导入jar:


这里有很多种方式选择jar的位置,但是这些操作的效果和第一种是一样的,运行的时候是不会将引用的jar集成到程序中的。


总结上面的五种方式,我们可以看到,第二种和第四种的效果是一样的,也是最普遍的导入引用工程的方式,因为其他三种方式的话,其实在编译的时候是不会有问题的,但是在运行的时候会报错(找不到指定的类,可以依次尝试一下),不过这三种方式只要一步就可以和那两种方式实现的效果一样了


只要设置导出的时候勾选上这个jar就可以了。那么其实这五种方式都是可以的,性质和效果是一样的。


说完了Eclipse中引用工程的各种方式以及区别之后,我们在回过头来看一下,上面遇到的问题:Could not find class...

其实这个问题就简单了,原因是:插件工程PluginSDKs使用的是lib文件夹导入的jar(这个jar是不会集成到程序中的),而宿主工程PluginDemos的引用工程的方式也变成了lib文件夹(jar也是不会集成到程序中的)。那么程序运行的时候就会出现错误:

Could not find class 'com.pluginsdk.interfaces.IBean'


问题二:Class ref in pre-verified class resolved to unexpected implementation(相同的类加载了两次)


这个问题产生的操作:

插件工程PluginSDKs和宿主工程PluginDemos引用工程的方式都变成library(或者是都用libs文件夹导入jar)


这个错误的原因也是很多做插件的开发者第一次都会遇到的问题,其实这个问题的本质是PluginImpl中的接口被加载了两次,因为插件工程和宿主工程在运行的时候都会把PluginImpl集成到程序中。对于这个问题,我们来分析一下,首先对于宿主apk,他的类加载器是PathClassLoader(这个对于每个应用来说是默认的加载器,原因很简单,PathClassLoader只能加载/data/app目录下的apk,就是已经安装的apk,一般我们的apk都是安装之后在运行,所以用这个加载器也是理所当然的)。这个加载器开始加载插件接口工程(宿主工程中引入的PluginImpl)中的IBean。当使用DexClassLoader加载PluginSDKs.apk的时候,首先会让宿主apkPathClassLoader加载器去加载,这个好多人有点迷糊了,为什么会先让PathClassLoader加载器去加载呢?

这个就是Java中的类加载机制的双亲委派机制:http://blog.csdn.net/jiangwei0910410003/article/details/17733153

Android中的加载机制也是类似的,我们这里的代码设置了DexClassLoader的父加载器为当前类加载器(宿主apk的PathClassLoader),不行的话,可以打印一下getClassLoader()方法的返回结果看一下。

[java] view plain copy
  1. classLoader=newDexClassLoader(dexpath,fileRelease.getAbsolutePath(),null,getClassLoader());
那么加载器就是一样的了(宿主apk的PathClassLoader),那么就奇怪了,都是一个为什么还有错误呢?查看系统源码可以了解:

Resolve.c源码(这个是在虚拟机dalvik中的):源码下载地址为:http://blog.csdn.net/jiangwei0910410003/article/details/37988637
我们来看一下他的一个主要函数:

[cpp] view plain copy
  1. /*
  2. *Findtheclasscorrespondingto"classIdx",whichmapstoaclassname
  3. *string.ItmightbeinthesameDEXfileas"referrer",inadifferent
  4. *DEXfile,generatedbyaclassloader,orgeneratedbytheVM(e.g.
  5. *arrayclasses).
  6. *
  7. *BecausetheDexTypeIdisassociatedwiththereferringclass'DEXfile,
  8. *wemayhavetoresolvethesameclassmorethanonceifit'sreferred
  9. *tofromclassesinmultipleDEXfiles.Thisisanecessarypropertyfor
  10. *DEXfilesassociatedwithdifferentclassloaders.
  11. *
  12. *WecacheacopyofthelookupintheDexFile's"resolvedclass"table,
  13. *sofuturereferencesto"classIdx"arefaster.
  14. *
  15. *Notethat"referrer"maybeintheprocessofbeinglinked.
  16. *
  17. *TraditionalVMsmightdoaccesscheckshere,butinDalviktheclass
  18. *"constantpool"issharedbetweenallclassesintheDEXfile.Werely
  19. *ontheverifiertodothechecksforus.
  20. *
  21. *Doesnotinitializetheclass.
  22. *
  23. *"fromUnverifiedConstant"shouldonlybesetifthiscallisthedirect
  24. *resultofexecutinga"const-class"or"instance-of"instruction,which
  25. *useclassconstantsnotresolvedbythebytecodeverifier.
  26. *
  27. *ReturnsNULLwithanexceptionraisedonfailure.
  28. */
  29. ClassObject*dvmResolveClass(constClassObject*referrer,u4classIdx,
  30. boolfromUnverifiedConstant)
  31. {
  32. DvmDex*pDvmDex=referrer->pDvmDex;
  33. ClassObject*resClass;
  34. constchar*className;
  35. /*
  36. *Checkthetablefirst--thisgetscalledfromtheother"resolve"
  37. *methods.
  38. */
  39. resClass=dvmDexGetResolvedClass(pDvmDex,classIdx);
  40. if(resClass!=NULL)
  41. returnresClass;
  42. LOGVV("---resolvingclass%u(referrer=%scl=%p)\n",
  43. classIdx,referrer->descriptor,referrer->classLoader);
  44. /*
  45. *Classhasn'tbeenloadedyet,orisintheprocessofbeingloaded
  46. *andinitializednow.Trytogetacopy.Ifwefindone,putthe
  47. *pointerintheDexTypeId.Thereisn'taraceconditionhere--
  48. *32-bitwritesareguaranteedatomiconalltargetplatforms.Worst
  49. *casewehavetwothreadsstoringthesamevalue.
  50. *
  51. *Ifthisisanarrayclass,we'llgenerateithere.
  52. */
  53. className=dexStringByTypeIdx(pDvmDex->pDexFile,classIdx);
  54. if(className[0]!='\0'&&className[1]=='\0'){
  55. /*primitivetype*/
  56. resClass=dvmFindPrimitiveClass(className[0]);
  57. }else{
  58. resClass=dvmFindClassNoInit(className,referrer->classLoader);
  59. }
  60. if(resClass!=NULL){
  61. /*
  62. *Ifthereferrerwaspre-verified,theresolvedclassmustcome
  63. *fromthesameDEXorfromabootstrapclass.Thepre-verifier
  64. *makesassumptionsthatcouldbeinvalidatedbyawackyclass
  65. *loader.(Seethenotesatthetopofoo/Class.c.)
  66. *
  67. *Theverifierdoes*not*failaclassforusingaconst-class
  68. *orinstance-ofinstructionreferringtoanunresolveableclass,
  69. *becausetheresultoftheinstructionissimplyaClassobject
  70. *orboolean--there'snoneedtoresolvetheclassobjectduring
  71. *verification.Instancefieldandvirtualmethodaccessescan
  72. *breakdangerouslyifwegetthewrongclass,butconst-classand
  73. *instance-ofareonlyinterestingatexecutiontime.So,ifwe
  74. *wegothereaspartofexecutingoneofthe"unverifiedclass"
  75. *instructions,weskiptheadditionalcheck.
  76. *
  77. *Dittoforclassreferencesfromannotationsandexception
  78. *handlerlists.
  79. */
  80. if(!fromUnverifiedConstant&&
  81. IS_CLASS_FLAG_SET(referrer,CLASS_ISPREVERIFIED))
  82. {
  83. ClassObject*resClassCheck=resClass;
  84. if(dvmIsArrayClass(resClassCheck))
  85. resClassCheck=resClassCheck->elementClass;
  86. if(referrer->pDvmDex!=resClassCheck->pDvmDex&&
  87. resClassCheck->classLoader!=NULL)
  88. {
  89. LOGW("ClassresolvedbyunexpectedDEX:"
  90. "%s(%p):%pref[%s]%s(%p):%p\n",
  91. referrer->descriptor,referrer->classLoader,
  92. referrer->pDvmDex,
  93. resClass->descriptor,resClassCheck->descriptor,
  94. resClassCheck->classLoader,resClassCheck->pDvmDex);
  95. LOGW("(%shadusedadifferent%sduringpre-verification)\n",
  96. referrer->descriptor,resClass->descriptor);
  97. dvmThrowException("Ljava/lang/IllegalAccessError;",
  98. "Classrefinpre-verifiedclassresolvedtounexpected"
  99. "implementation");
  100. returnNULL;
  101. }
  102. }
  103. LOGVV("#####+ResolveClass(%s):referrer=%sdex=%pldr=%pref=%d\n",
  104. resClass->descriptor,referrer->descriptor,referrer->pDvmDex,
  105. referrer->classLoader,classIdx);
  106. /*
  107. *Addwhatwefoundtothelistsowecanskiptheclasssearch
  108. *nexttimethrough.
  109. *
  110. *TODO:shouldwebedoingthiswhenfromUnverifiedConstant==true?
  111. *(seecommentsattopofoo/Class.c)
  112. */
  113. dvmDexSetResolvedClass(pDvmDex,classIdx,resClass);
  114. }else{
  115. /*notfound,exceptionshouldberaised*/
  116. LOGVV("Classnotfound:%s\n",
  117. dexStringByTypeIdx(pDvmDex->pDexFile,classIdx));
  118. assert(dvmCheckException(dvmThreadSelf()));
  119. }
  120. returnresClass;
  121. }
我们看下面的判断可以得到,就是在这里抛出的异常,代码逻辑我们就不看了,因为太多的头文件相互引用,看起来很费劲,直接看一下函数的说明:


红色部分内容,他的意思是我们需要解决从不同的dex文件中加载相同的class,需要使用不同的类加载器。

说白了就是,同一个类加载器从不同的dex文件中加载相同的class。所以上面是同一个类加载器PathClassLoader去加载(宿主apk和插件apk)来自不同的dex中的相同的类IBean。所以我们在做动态加载的时候都说过:不要把接口的jar一起打包成jar/dex/apk


问题三:Connot be cast to....(类型转化异常)


这个问题产生的操作:

插件工程PluginSDKs和宿主工程都是用Library方式引用工程(或者是libs),同时将上面的一行代码

[java] view plain copy
  1. classLoader=newDexClassLoader(dexpath,fileRelease.getAbsolutePath(),null,getClassLoader());

修改成:

[java] view plain copy
  1. classLoader=newDexClassLoader(dexpath,fileRelease.getAbsolutePath(),null,ClassLoader.getSystemClassLoader());
就是将DexClassLoader的父加载器修改了一下:我们知道getClassLoader()获取到的是应用的默认加载器PathClassLoader,而ClassLoader.getSystemClassLoader()是获取系统类加载器,这样修改之后会出现这样的错误的原因是:插件工程和宿主工程都集成了PluginImpl,所以DexClassLoader在加载Bean的时候,首先会让ClassLoader.getSystemClassLoader()类加载器(DexClassLoader的父加载器)去查找,因为Bean是实现了IBean接口,这时候ClassLoader.getSystemClassLoader就会从插件工程的apk中查找这个接口,结果没找到,没找到的话就让DexClassLoader去找,结果在PluginSDKs.apk中找到了,就加载进来,同时宿主工程中也集成了插件接口PluginImpl,他使用PathClassLoader去宿主工程中去查找,结果也是查找到了,也加载进来了,但是在进行类型转化的时候出现了错误: [java] view plain copy
  1. IBeanbean=(IBean)beanObject;
原因说白了就是:同一个类,用不同的类加载器进行加载产生出来的对象是不同的,不能进行相互赋值,负责就会出现转化异常。


总结

上面就说到了一些开发插件的过程中会遇到的一些问题,当我们知道这些问题之后,解决方法自然就会有了,

1) 为了避免Could not find class...,我们必须要集成PluginImpl,方式是使用Library或者是libs文件夹导入jar

(这里要注意,因为我们运行的其实是宿主工程apk,所以宿主工程一定要集成PluginImpl,如果他不集成的话,即使插件工程apk集成了也还是没有效果的)

2) 为了避免Class ref in pre-verified class resolved to unexpected implementation,我们在宿主工程和插件工程中只能集成一份PluginImpl,在结合上面的错误避免方式,可以得到正确的方式:

一定是宿主工程集成PluginImpl,插件工程一定不能集成PluginImpl。

(以后再制作插件的时候记住一句话就可以了,插件工程打包不能集成接口jar,宿主工程打包一定要集成接口jar)

关于第三个问题,其实在开发的过程中一般不会碰到,这里说一下主要是为了马上介绍Android中的类加载器的相关只是来做铺垫的


(PS:问题都解决了,后续就要介绍插件的制作了~~)


转自 http://blog.csdn.net/jiangwei0910410003/article/details/41384667

更多相关文章

  1. Android(安卓)Studio 翻译插件Translation和strings.xml多语言文
  2. Android动态加载第三方APK的View研究过程
  3. Android中JNI的使用之一:Java原生JNI的使用、javah指令的使用以及
  4. Android的GridView和Gallery结合Demo
  5. Android中的动态加载机制
  6. Android集结号
  7. Gradle入门系列(1):简介
  8. Android中Fragment的用法总结
  9. [置顶] Android图片异步加载之Android-Universal-Image-Loader

随机推荐

  1. Android(安卓)widget使用
  2. Android保存图片到图库,Android扫描文件到
  3. java 后端实现WebSocket学习篇和客户端An
  4. Qt for Android(安卓)拉起微信登录、分享
  5. 日趋饱和的Android程序员行业,这一行在未
  6. Android中使用JUnit测试
  7. android,java代码设置背景色
  8. android 底片,浮雕,老照片效果
  9. android 更加复杂的小鱼游
  10. android之SQlite创建数据库操作