Android中插件开发篇之----类加载器
前言
关于插件,已经在各大平台上出现过很多,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- /*
- *Copyright(C)2008TheAndroidOpenSourceProject
- *
- *LicensedundertheApacheLicense,Version2.0(the"License");
- *youmaynotusethisfileexceptincompliancewiththeLicense.
- *YoumayobtainacopyoftheLicenseat
- *
- *http://www.apache.org/licenses/LICENSE-2.0
- *
- *Unlessrequiredbyapplicablelaworagreedtoinwriting,software
- *distributedundertheLicenseisdistributedonan"ASIS"BASIS,
- *WITHOUTWARRANTIESORCONDITIONSOFANYKIND,eitherexpressorimplied.
- *SeetheLicenseforthespecificlanguagegoverningpermissionsand
- *limitationsundertheLicense.
- */
- packagedalvik.system;
- importjava.io.File;
- importjava.io.IOException;
- importjava.net.MalformedURLException;
- importjava.net.URL;
- importjava.util.zip.ZipFile;
- /**
- *Providesasimple{@linkClassLoader}implementationthatoperatesona
- *listofjar/apkfileswithclasses.dexentries.Thedirectorythat
- *holdstheoptimizedformofthefilesisspecifiedexplicitly.This
- *canbeusedtoexecutecodenotinstalledaspartofanapplication.
- *
- *ThebestplacetoputtheoptimizedDEXfilesisinapp-specific
- *storage,sothatremovaloftheappwillautomaticallyremovethe
- *optimizedDEXfiles.Ifotherstorageisused(e.g./sdcard),the
- *appmaynothaveanopportunitytoremovethem.
- */
- publicclassDexClassLoaderextendsClassLoader{
- privatestaticfinalbooleanVERBOSE_DEBUG=false;
- /*constructorargs,heldforinit*/
- privatefinalStringmRawDexPath;
- privatefinalStringmRawLibPath;
- privatefinalStringmDexOutputPath;
- /*
- *Parallelarraysforjar/apkfiles.
- *
- *(couldstufftheseintoanobjectandhaveasinglearray;
- *improvesclaritybutaddsoverhead)
- */
- privatefinalFile[]mFiles;//sourcefileFiles,forrsrcURLs
- privatefinalZipFile[]mZips;//sourcezipfiles,withresources
- privatefinalDexFile[]mDexs;//opened,preppedDEXfiles
- /**
- *Nativelibrarypath.
- */
- privatefinalString[]mLibPaths;
- /**
- *Createsa{@codeDexClassLoader}thatfindsinterpretedandnative
- *code.InterpretedclassesarefoundinasetofDEXfilescontained
- *inJarorAPKfiles.
- *
- *Thepathlistsareseparatedusingthecharacterspecifiedby
- *the"path.separator"systemproperty,whichdefaultsto":".
- *
- *@paramdexPath
- *thelistofjar/apkfilescontainingclassesandresources
- *@paramdexOutputDir
- *directorywhereoptimizedDEXfilesshouldbewritten
- *@paramlibPath
- *thelistofdirectoriescontainingnativelibraries;maybenull
- *@paramparent
- *theparentclassloader
- */
- publicDexClassLoader(StringdexPath,StringdexOutputDir,StringlibPath,
- ClassLoaderparent){
- super(parent);
- ......
dexPath:是加载apk/dex/jar的路径
dexOutDir:是dex的输出路径(因为加载apk/jar的时候会解压除dex文件,这个路径就是保存dex文件的)
libPath:是加载的时候需要用到的lib库,这个一般不用
parent:给DexClassLoader指定父加载器
我们在来看一下PathClassLoader的源码
PathClassLoader.java
[java] view plain copy- /*
- *Copyright(C)2007TheAndroidOpenSourceProject
- *
- *LicensedundertheApacheLicense,Version2.0(the"License");
- *youmaynotusethisfileexceptincompliancewiththeLicense.
- *YoumayobtainacopyoftheLicenseat
- *
- *http://www.apache.org/licenses/LICENSE-2.0
- *
- *Unlessrequiredbyapplicablelaworagreedtoinwriting,software
- *distributedundertheLicenseisdistributedonan"ASIS"BASIS,
- *WITHOUTWARRANTIESORCONDITIONSOFANYKIND,eitherexpressorimplied.
- *SeetheLicenseforthespecificlanguagegoverningpermissionsand
- *limitationsundertheLicense.
- */
- packagedalvik.system;
- importjava.io.ByteArrayOutputStream;
- importjava.io.File;
- importjava.io.FileNotFoundException;
- importjava.io.IOException;
- importjava.io.InputStream;
- importjava.io.RandomAccessFile;
- importjava.net.MalformedURLException;
- importjava.net.URL;
- importjava.util.ArrayList;
- importjava.util.Enumeration;
- importjava.util.List;
- importjava.util.NoSuchElementException;
- importjava.util.zip.ZipEntry;
- importjava.util.zip.ZipFile;
- /**
- *Providesasimple{@linkClassLoader}implementationthatoperatesonalist
- *offilesanddirectoriesinthelocalfilesystem,butdoesnotattemptto
- *loadclassesfromthenetwork.Androidusesthisclassforitssystemclass
- *loaderandforitsapplicationclassloader(s).
- */
- publicclassPathClassLoaderextendsClassLoader{
- privatefinalStringpath;
- privatefinalStringlibPath;
- /*
- *Parallelarraysforjar/apkfiles.
- *
- *(couldstufftheseintoanobjectandhaveasinglearray;
- *improvesclaritybutaddsoverhead)
- */
- privatefinalString[]mPaths;
- privatefinalFile[]mFiles;
- privatefinalZipFile[]mZips;
- privatefinalDexFile[]mDexs;
- /**
- *Nativelibrarypath.
- */
- privatefinalList<String>libraryPathElements;
- /**
- *Createsa{@codePathClassLoader}thatoperatesonagivenlistoffiles
- *anddirectories.Thismethodisequivalenttocalling
- *{@link#PathClassLoader(String,String,ClassLoader)}witha
- *{@codenull}valueforthesecondargument(seedescriptionthere).
- *
- *@parampath
- *thelistoffilesanddirectories
- *
- *@paramparent
- *theparentclassloader
- */
- publicPathClassLoader(Stringpath,ClassLoaderparent){
- this(path,null,parent);
- }
- /**
- *Createsa{@codePathClassLoader}thatoperatesontwogiven
- *listsoffilesanddirectories.Theentriesofthefirstlist
- *shouldbeoneofthefollowing:
- *
- *<ul>
- *<li>Directoriescontainingclassesorresources.
- *<li>JAR/ZIP/APKfiles,possiblycontaininga"classes.dex"file.
- *<li>"classes.dex"files.
- *</ul>
- *
- *Theentriesofthesecondlistshouldbedirectoriescontaining
- *nativelibraryfiles.Bothlistsareseparatedusingthe
- *characterspecifiedbythe"path.separator"systemproperty,
- *which,onAndroid,defaultsto":".
- *
- *@parampath
- *thelistoffilesanddirectoriescontainingclassesand
- *resources
- *
- *@paramlibPath
- *thelistofdirectoriescontainingnativelibraries
- *
- *@paramparent
- *theparentclassloader
- */
- publicPathClassLoader(Stringpath,StringlibPath,ClassLoaderparent){
- super(parent);
- ....
/data/dalvik-cache
这个释放解压操作是系统做的。所以PathClassLoader可以不需要这个参数的。
上面看了他们两的区别,下面在来看一下Android中的各种类加载器分别加载哪些类:
[java] view plain copy- packagecom.example.androiddemo;
- importandroid.app.Activity;
- importandroid.content.Context;
- importandroid.os.Bundle;
- importandroid.util.Log;
- importandroid.widget.ListView;
- publicclassMainActivityextendsActivity{
- @Override
- protectedvoidonCreate(BundlesavedInstanceState){
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- Log.i("DEMO","Context的类加载加载器:"+Context.class.getClassLoader());
- Log.i("DEMO","ListView的类加载器:"+ListView.class.getClassLoader());
- Log.i("DEMO","应用程序默认加载器:"+getClassLoader());
- Log.i("DEMO","系统类加载器:"+ClassLoader.getSystemClassLoader());
- Log.i("DEMO","系统类加载器和Context的类加载器是否相等:"+(Context.class.getClassLoader()==ClassLoader.getSystemClassLoader()));
- Log.i("DEMO","系统类加载器和应用程序默认加载器是否相等:"+(getClassLoader()==ClassLoader.getSystemClassLoader()));
- Log.i("DEMO","打印应用程序默认加载器的委派机制:");
- ClassLoaderclassLoader=getClassLoader();
- while(classLoader!=null){
- Log.i("DEMO","类加载器:"+classLoader);
- classLoader=classLoader.getParent();
- }
- Log.i("DEMO","打印系统加载器的委派机制:");
- classLoader=ClassLoader.getSystemClassLoader();
- while(classLoader!=null){
- Log.i("DEMO","类加载器:"+classLoader);
- classLoader=classLoader.getParent();
- }
- }
- }
依次来看一下
1) 系统类的加载器
[java] view plain copy- Log.i("DEMO","Context的类加载加载器:"+Context.class.getClassLoader());
- Log.i("DEMO","ListView的类加载器:"+ListView.class.getClassLoader());
看到他也是继承了ClassLoader类。
2) 应用程序的默认加载器
[java] view plain copy- Log.i("DEMO","应用程序默认加载器:"+getClassLoader());
默认类加载器是PathClassLoader,同时可以看到加载的apk路径,libPath(一般包括/vendor/lib和/system/lib)
3) 系统类加载器
[java] view plain copy- Log.i("DEMO","系统类加载器:"+ClassLoader.getSystemClassLoader());
4) 默认加载器的委派机制关系
[java] view plain copy- Log.i("DEMO","打印应用程序默认加载器的委派机制:");
- ClassLoaderclassLoader=getClassLoader();
- while(classLoader!=null){
- Log.i("DEMO","类加载器:"+classLoader);
- classLoader=classLoader.getParent();
- }
默认加载器PathClassLoader的父亲是BootClassLoader
5) 系统加载器的委派机制关系
[java] view plain copy- Log.i("DEMO","打印系统加载器的委派机制:");
- classLoader=ClassLoader.getSystemClassLoader();
- while(classLoader!=null){
- Log.i("DEMO","类加载器:"+classLoader);
- classLoader=classLoader.getParent();
- }
可以看到系统加载器的父亲也是BootClassLoader
二、分析遇到的问题的原因和解决办法
DexClassLoader加载原理和分析在实现插件时不同操作造成错误的原因分析
这里主要用了三个工程:
PluginImpl:插件接口工程(只是接口的定义)
PluginSDK:插件工程(实现插件接口,定义具体的功能)
HostProject:宿主工程(需要引用插件接口工程,然后动态的加载插件工程)(例子项目中名字是PluginDemos)
第一、项目介绍
下面来看一下源代码:
1、PluginImpl工程:
1) IBean.java
[java] view plain copy- packagecom.pluginsdk.interfaces;
- publicabstractinterfaceIBean{
- publicabstractStringgetName();
- publicabstractvoidsetName(StringparamString);
- }
- packagecom.pluginsdk.interfaces;
- importandroid.content.Context;
- publicabstractinterfaceIDynamic{
- publicabstractvoidmethodWithCallBack(YKCallBackparamYKCallBack);
- publicabstractvoidshowPluginWindow(ContextparamContext);
- publicabstractvoidstartPluginActivity(Contextcontext,Class<?>cls);
- publicabstractStringgetStringForResId(Contextcontext);
- }
2、PluginSDK工程:
1) Dynamic.java
[java] view plain copy- /**
- *Dynamic1.java
- *com.youku.pluginsdk.imp
- *
- *Function:TODO
- *
- *verdateauthor
- *──────────────────────────────────
- *2014-10-20Administrator
- *
- *Copyright(c)2014,TNTAllRightsReserved.
- */
- packagecom.pluginsdk.imp;
- importandroid.app.AlertDialog;
- importandroid.app.AlertDialog.Builder;
- importandroid.app.Dialog;
- importandroid.content.Context;
- importandroid.content.DialogInterface;
- importandroid.content.Intent;
- importcom.pluginsdk.bean.Bean;
- importcom.pluginsdk.interfaces.IDynamic;
- importcom.pluginsdk.interfaces.YKCallBack;
- importcom.youku.pluginsdk.R;
- /**
- *ClassName:Dynamic1
- *
- *@authorjiangwei
- *@version
- *@sinceVer1.1
- *@Date2014-10-20下午5:57:10
- */
- publicclassDynamicimplementsIDynamic{
- /**
- */
- publicvoidmethodWithCallBack(YKCallBackcallback){
- Beanbean=newBean();
- bean.setName("PLUGIN_SDK_USER");
- callback.callback(bean);
- }
- publicvoidshowPluginWindow(Contextcontext){
- AlertDialog.Builderbuilder=newBuilder(context);
- builder.setMessage("对话框");
- builder.setTitle(R.string.hello_world);
- builder.setNegativeButton("取消",newDialog.OnClickListener(){
- @Override
- publicvoidonClick(DialogInterfacedialog,intwhich){
- dialog.dismiss();
- }
- });
- Dialogdialog=builder.create();//.show();
- dialog.show();
- }
- publicvoidstartPluginActivity(Contextcontext,Class<?>cls){
- /**
- *这里要注意几点:
- *1、如果单纯的写一个MainActivity的话,在主工程中也有一个MainActivity,开启的Activity还是主工程中的MainActivity
- *2、如果这里将MainActivity写成全名的话,还是有问题,会报找不到这个Activity的错误
- */
- Intentintent=newIntent(context,cls);
- context.startActivity(intent);
- }
- publicStringgetStringForResId(Contextcontext){
- returncontext.getResources().getString(R.string.hello_world);
- }
- }
- /**
- *User.java
- *com.youku.pluginsdk.bean
- *
- *Function:TODO
- *
- *verdateauthor
- *──────────────────────────────────
- *2014-10-20Administrator
- *
- *Copyright(c)2014,TNTAllRightsReserved.
- */
- packagecom.pluginsdk.bean;
- /**
- *ClassName:User
- *
- *@authorjiangwei
- *@version
- *@sinceVer1.1
- *@Date2014-10-20下午1:35:16
- */
- publicclassBeanimplementscom.pluginsdk.interfaces.IBean{
- /**
- *
- */
- privateStringname="这是来自于插件工程中设置的初始化的名字";
- publicStringgetName(){
- returnname;
- }
- publicvoidsetName(Stringname){
- this.name=name;
- }
- }
3、宿主工程HostProject
1) MainActivity.java
[java] view plain copy- packagecom.plugindemo;
- importjava.io.File;
- importjava.lang.reflect.Method;
- importandroid.annotation.SuppressLint;
- importandroid.app.Activity;
- importandroid.content.Context;
- importandroid.content.res.AssetManager;
- importandroid.content.res.Resources;
- importandroid.content.res.Resources.Theme;
- importandroid.os.Bundle;
- importandroid.os.Environment;
- importandroid.util.Log;
- importandroid.view.View;
- importandroid.widget.Button;
- importandroid.widget.ListView;
- importandroid.widget.Toast;
- importcom.pluginsdk.interfaces.IBean;
- importcom.pluginsdk.interfaces.IDynamic;
- importcom.pluginsdk.interfaces.YKCallBack;
- importcom.youku.plugindemo.R;
- importdalvik.system.DexClassLoader;
- publicclassMainActivityextendsActivity{
- privateAssetManagermAssetManager;//资源管理器
- privateResourcesmResources;//资源
- privateThememTheme;//主题
- privateStringapkFileName="PluginSDKs.apk";
- privateStringdexpath=null;//apk文件地址
- privateFilefileRelease=null;//释放目录
- privateDexClassLoaderclassLoader=null;
- @SuppressLint("NewApi")
- @Override
- protectedvoidonCreate(BundlesavedInstanceState){
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- dexpath=Environment.getExternalStorageDirectory()+File.separator+apkFileName;
- fileRelease=getDir("dex",0);
- /*初始化classloader
- *dexpathdex文件地址
- *fileRelease文件释放地址
- *父classLoader
- */
- Log.d("DEMO",(getClassLoader()==ListView.class.getClassLoader())+"");
- Log.d("DEMO",ListView.class.getClassLoader()+"");
- Log.d("DEMO",Context.class.getClassLoader()+"");
- Log.d("DEMO",Context.class.getClassLoader().getSystemClassLoader()+"");
- Log.d("DEMO",Activity.class.getClassLoader()+"");
- Log.d("DEMO",(Context.class.getClassLoader().getSystemClassLoader()==ClassLoader.getSystemClassLoader())+"");
- Log.d("DEMO",ClassLoader.getSystemClassLoader()+"");
- classLoader=newDexClassLoader(dexpath,fileRelease.getAbsolutePath(),null,getClassLoader());
- Buttonbtn_1=(Button)findViewById(R.id.btn_1);
- Buttonbtn_2=(Button)findViewById(R.id.btn_2);
- Buttonbtn_3=(Button)findViewById(R.id.btn_3);
- Buttonbtn_4=(Button)findViewById(R.id.btn_4);
- Buttonbtn_5=(Button)findViewById(R.id.btn_5);
- Buttonbtn_6=(Button)findViewById(R.id.btn_6);
- btn_1.setOnClickListener(newView.OnClickListener(){//普通调用反射的方式
- @Override
- publicvoidonClick(Viewarg0){
- ClassmLoadClassBean;
- try{
- mLoadClassBean=classLoader.loadClass("com.pluginsdk.bean.Bean");
- ObjectbeanObject=mLoadClassBean.newInstance();
- Log.d("DEMO","ClassLoader:"+mLoadClassBean.getClassLoader());
- Log.d("DEMO","ClassLoader:"+mLoadClassBean.getClassLoader().getParent());
- MethodgetNameMethod=mLoadClassBean.getMethod("getName");
- getNameMethod.setAccessible(true);
- Stringname=(String)getNameMethod.invoke(beanObject);
- Toast.makeText(MainActivity.this,name,Toast.LENGTH_SHORT).show();
- }catch(Exceptione){
- Log.e("DEMO","msg:"+e.getMessage());
- }
- }
- });
- btn_2.setOnClickListener(newView.OnClickListener(){//带参数调用
- @Override
- publicvoidonClick(Viewarg0){
- ClassmLoadClassBean;
- try{
- mLoadClassBean=classLoader.loadClass("com.pluginsdk.bean.Bean");
- ObjectbeanObject=mLoadClassBean.newInstance();
- //接口形式调用
- Log.d("DEMO",beanObject.getClass().getClassLoader()+"");
- Log.d("DEMO",IBean.class.getClassLoader()+"");
- Log.d("DEMO",ClassLoader.getSystemClassLoader()+"");
- IBeanbean=(IBean)beanObject;
- bean.setName("宿主程序设置的新名字");
- Toast.makeText(MainActivity.this,bean.getName(),Toast.LENGTH_SHORT).show();
- }catch(Exceptione){
- Log.e("DEMO","msg:"+e.getMessage());
- }
- }
- });
- btn_3.setOnClickListener(newView.OnClickListener(){//带回调函数的调用
- @Override
- publicvoidonClick(Viewarg0){
- ClassmLoadClassDynamic;
- try{
- mLoadClassDynamic=classLoader.loadClass("com.pluginsdk.imp.Dynamic");
- ObjectdynamicObject=mLoadClassDynamic.newInstance();
- //接口形式调用
- IDynamicdynamic=(IDynamic)dynamicObject;
- //回调函数调用
- YKCallBackcallback=newYKCallBack(){//回调接口的定义
- publicvoidcallback(IBeanarg0){
- Toast.makeText(MainActivity.this,arg0.getName(),Toast.LENGTH_SHORT).show();
- };
- };
- dynamic.methodWithCallBack(callback);
- }catch(Exceptione){
- Log.e("DEMO","msg:"+e.getMessage());
- }
- }
- });
- btn_4.setOnClickListener(newView.OnClickListener(){//带资源文件的调用
- @Override
- publicvoidonClick(Viewarg0){
- loadResources();
- ClassmLoadClassDynamic;
- try{
- mLoadClassDynamic=classLoader.loadClass("com.pluginsdk.imp.Dynamic");
- ObjectdynamicObject=mLoadClassDynamic.newInstance();
- //接口形式调用
- IDynamicdynamic=(IDynamic)dynamicObject;
- dynamic.showPluginWindow(MainActivity.this);
- }catch(Exceptione){
- Log.e("DEMO","msg:"+e.getMessage());
- }
- }
- });
- btn_5.setOnClickListener(newView.OnClickListener(){//带资源文件的调用
- @Override
- publicvoidonClick(Viewarg0){
- loadResources();
- ClassmLoadClassDynamic;
- try{
- mLoadClassDynamic=classLoader.loadClass("com.pluginsdk.imp.Dynamic");
- ObjectdynamicObject=mLoadClassDynamic.newInstance();
- //接口形式调用
- IDynamicdynamic=(IDynamic)dynamicObject;
- dynamic.startPluginActivity(MainActivity.this,
- classLoader.loadClass("com.plugindemo.MainActivity"));
- }catch(Exceptione){
- Log.e("DEMO","msg:"+e.getMessage());
- }
- }
- });
- btn_6.setOnClickListener(newView.OnClickListener(){//带资源文件的调用
- @Override
- publicvoidonClick(Viewarg0){
- loadResources();
- ClassmLoadClassDynamic;
- try{
- mLoadClassDynamic=classLoader.loadClass("com.pluginsdk.imp.Dynamic");
- ObjectdynamicObject=mLoadClassDynamic.newInstance();
- //接口形式调用
- IDynamicdynamic=(IDynamic)dynamicObject;
- Stringcontent=dynamic.getStringForResId(MainActivity.this);
- Toast.makeText(getApplicationContext(),content+"",Toast.LENGTH_LONG).show();
- }catch(Exceptione){
- Log.e("DEMO","msg:"+e.getMessage());
- }
- }
- });
- }
- protectedvoidloadResources(){
- try{
- AssetManagerassetManager=AssetManager.class.newInstance();
- MethodaddAssetPath=assetManager.getClass().getMethod("addAssetPath",String.class);
- addAssetPath.invoke(assetManager,dexpath);
- mAssetManager=assetManager;
- }catch(Exceptione){
- e.printStackTrace();
- }
- ResourcessuperRes=super.getResources();
- superRes.getDisplayMetrics();
- superRes.getConfiguration();
- mResources=newResources(mAssetManager,superRes.getDisplayMetrics(),superRes.getConfiguration());
- mTheme=mResources.newTheme();
- mTheme.setTo(super.getTheme());
- }
- @Override
- publicAssetManagergetAssets(){
- returnmAssetManager==null?super.getAssets():mAssetManager;
- }
- @Override
- publicResourcesgetResources(){
- returnmResources==null?super.getResources():mResources;
- }
- @Override
- publicThemegetTheme(){
- returnmTheme==null?super.getTheme():mTheme;
- }
- }
第二、项目引用关系
工程文件现在大致看完了,我们看一下他们的引用关系吧: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的时候,首先会让宿主apk的PathClassLoader加载器去加载,这个好多人有点迷糊了,为什么会先让PathClassLoader加载器去加载呢?
这个就是Java中的类加载机制的双亲委派机制:http://blog.csdn.net/jiangwei0910410003/article/details/17733153
Android中的加载机制也是类似的,我们这里的代码设置了DexClassLoader的父加载器为当前类加载器(宿主apk的PathClassLoader),不行的话,可以打印一下getClassLoader()方法的返回结果看一下。
[java] view plain copy- classLoader=newDexClassLoader(dexpath,fileRelease.getAbsolutePath(),null,getClassLoader());
Resolve.c源码(这个是在虚拟机dalvik中的):源码下载地址为:http://blog.csdn.net/jiangwei0910410003/article/details/37988637
我们来看一下他的一个主要函数:
- /*
- *Findtheclasscorrespondingto"classIdx",whichmapstoaclassname
- *string.ItmightbeinthesameDEXfileas"referrer",inadifferent
- *DEXfile,generatedbyaclassloader,orgeneratedbytheVM(e.g.
- *arrayclasses).
- *
- *BecausetheDexTypeIdisassociatedwiththereferringclass'DEXfile,
- *wemayhavetoresolvethesameclassmorethanonceifit'sreferred
- *tofromclassesinmultipleDEXfiles.Thisisanecessarypropertyfor
- *DEXfilesassociatedwithdifferentclassloaders.
- *
- *WecacheacopyofthelookupintheDexFile's"resolvedclass"table,
- *sofuturereferencesto"classIdx"arefaster.
- *
- *Notethat"referrer"maybeintheprocessofbeinglinked.
- *
- *TraditionalVMsmightdoaccesscheckshere,butinDalviktheclass
- *"constantpool"issharedbetweenallclassesintheDEXfile.Werely
- *ontheverifiertodothechecksforus.
- *
- *Doesnotinitializetheclass.
- *
- *"fromUnverifiedConstant"shouldonlybesetifthiscallisthedirect
- *resultofexecutinga"const-class"or"instance-of"instruction,which
- *useclassconstantsnotresolvedbythebytecodeverifier.
- *
- *ReturnsNULLwithanexceptionraisedonfailure.
- */
- ClassObject*dvmResolveClass(constClassObject*referrer,u4classIdx,
- boolfromUnverifiedConstant)
- {
- DvmDex*pDvmDex=referrer->pDvmDex;
- ClassObject*resClass;
- constchar*className;
- /*
- *Checkthetablefirst--thisgetscalledfromtheother"resolve"
- *methods.
- */
- resClass=dvmDexGetResolvedClass(pDvmDex,classIdx);
- if(resClass!=NULL)
- returnresClass;
- LOGVV("---resolvingclass%u(referrer=%scl=%p)\n",
- classIdx,referrer->descriptor,referrer->classLoader);
- /*
- *Classhasn'tbeenloadedyet,orisintheprocessofbeingloaded
- *andinitializednow.Trytogetacopy.Ifwefindone,putthe
- *pointerintheDexTypeId.Thereisn'taraceconditionhere--
- *32-bitwritesareguaranteedatomiconalltargetplatforms.Worst
- *casewehavetwothreadsstoringthesamevalue.
- *
- *Ifthisisanarrayclass,we'llgenerateithere.
- */
- className=dexStringByTypeIdx(pDvmDex->pDexFile,classIdx);
- if(className[0]!='\0'&&className[1]=='\0'){
- /*primitivetype*/
- resClass=dvmFindPrimitiveClass(className[0]);
- }else{
- resClass=dvmFindClassNoInit(className,referrer->classLoader);
- }
- if(resClass!=NULL){
- /*
- *Ifthereferrerwaspre-verified,theresolvedclassmustcome
- *fromthesameDEXorfromabootstrapclass.Thepre-verifier
- *makesassumptionsthatcouldbeinvalidatedbyawackyclass
- *loader.(Seethenotesatthetopofoo/Class.c.)
- *
- *Theverifierdoes*not*failaclassforusingaconst-class
- *orinstance-ofinstructionreferringtoanunresolveableclass,
- *becausetheresultoftheinstructionissimplyaClassobject
- *orboolean--there'snoneedtoresolvetheclassobjectduring
- *verification.Instancefieldandvirtualmethodaccessescan
- *breakdangerouslyifwegetthewrongclass,butconst-classand
- *instance-ofareonlyinterestingatexecutiontime.So,ifwe
- *wegothereaspartofexecutingoneofthe"unverifiedclass"
- *instructions,weskiptheadditionalcheck.
- *
- *Dittoforclassreferencesfromannotationsandexception
- *handlerlists.
- */
- if(!fromUnverifiedConstant&&
- IS_CLASS_FLAG_SET(referrer,CLASS_ISPREVERIFIED))
- {
- ClassObject*resClassCheck=resClass;
- if(dvmIsArrayClass(resClassCheck))
- resClassCheck=resClassCheck->elementClass;
- if(referrer->pDvmDex!=resClassCheck->pDvmDex&&
- resClassCheck->classLoader!=NULL)
- {
- LOGW("ClassresolvedbyunexpectedDEX:"
- "%s(%p):%pref[%s]%s(%p):%p\n",
- referrer->descriptor,referrer->classLoader,
- referrer->pDvmDex,
- resClass->descriptor,resClassCheck->descriptor,
- resClassCheck->classLoader,resClassCheck->pDvmDex);
- LOGW("(%shadusedadifferent%sduringpre-verification)\n",
- referrer->descriptor,resClass->descriptor);
- dvmThrowException("Ljava/lang/IllegalAccessError;",
- "Classrefinpre-verifiedclassresolvedtounexpected"
- "implementation");
- returnNULL;
- }
- }
- LOGVV("#####+ResolveClass(%s):referrer=%sdex=%pldr=%pref=%d\n",
- resClass->descriptor,referrer->descriptor,referrer->pDvmDex,
- referrer->classLoader,classIdx);
- /*
- *Addwhatwefoundtothelistsowecanskiptheclasssearch
- *nexttimethrough.
- *
- *TODO:shouldwebedoingthiswhenfromUnverifiedConstant==true?
- *(seecommentsattopofoo/Class.c)
- */
- dvmDexSetResolvedClass(pDvmDex,classIdx,resClass);
- }else{
- /*notfound,exceptionshouldberaised*/
- LOGVV("Classnotfound:%s\n",
- dexStringByTypeIdx(pDvmDex->pDexFile,classIdx));
- assert(dvmCheckException(dvmThreadSelf()));
- }
- returnresClass;
- }
红色部分内容,他的意思是我们需要解决从不同的dex文件中加载相同的class,需要使用不同的类加载器。
说白了就是,同一个类加载器从不同的dex文件中加载相同的class。所以上面是同一个类加载器PathClassLoader去加载(宿主apk和插件apk)来自不同的dex中的相同的类IBean。所以我们在做动态加载的时候都说过:不要把接口的jar一起打包成jar/dex/apk
问题三:Connot be cast to....(类型转化异常)
这个问题产生的操作:
插件工程PluginSDKs和宿主工程都是用Library方式引用工程(或者是libs),同时将上面的一行代码
[java] view plain copy- classLoader=newDexClassLoader(dexpath,fileRelease.getAbsolutePath(),null,getClassLoader());
修改成:
[java] view plain copy- classLoader=newDexClassLoader(dexpath,fileRelease.getAbsolutePath(),null,ClassLoader.getSystemClassLoader());
- 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
更多相关文章
- Android(安卓)Studio 翻译插件Translation和strings.xml多语言文
- Android动态加载第三方APK的View研究过程
- Android中JNI的使用之一:Java原生JNI的使用、javah指令的使用以及
- Android的GridView和Gallery结合Demo
- Android中的动态加载机制
- Android集结号
- Gradle入门系列(1):简介
- Android中Fragment的用法总结
- [置顶] Android图片异步加载之Android-Universal-Image-Loader