@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
 
 在CODE上查看代码片派生到我的代码片
  1. /* 
  2.  * Copyright (C) 2008 The Android Open Source Project 
  3.  * 
  4.  * Licensed under the Apache License, Version 2.0 (the "License"); 
  5.  * you may not use this file except in compliance with the License. 
  6.  * You may obtain a copy of the License at 
  7.  * 
  8.  *      http://www.apache.org/licenses/LICENSE-2.0 
  9.  * 
  10.  * Unless required by applicable law or agreed to in writing, software 
  11.  * distributed under the License is distributed on an "AS IS" BASIS, 
  12.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
  13.  * See the License for the specific language governing permissions and 
  14.  * limitations under the License. 
  15.  */  
  16.   
  17. package dalvik.system;  
  18.   
  19. import java.io.File;  
  20. import java.io.IOException;  
  21. import java.net.MalformedURLException;  
  22. import java.net.URL;  
  23. import java.util.zip.ZipFile;  
  24.   
  25. /** 
  26.  * Provides a simple {@link ClassLoader} implementation that operates on a 
  27.  * list of jar/apk files with classes.dex entries.  The directory that 
  28.  * holds the optimized form of the files is specified explicitly.  This 
  29.  * can be used to execute code not installed as part of an application. 
  30.  * 
  31.  * The best place to put the optimized DEX files is in app-specific 
  32.  * storage, so that removal of the app will automatically remove the 
  33.  * optimized DEX files.  If other storage is used (e.g. /sdcard), the 
  34.  * app may not have an opportunity to remove them. 
  35.  */  
  36. public class DexClassLoader extends ClassLoader {  
  37.   
  38.     private static final boolean VERBOSE_DEBUG = false;  
  39.   
  40.     /* constructor args, held for init */  
  41.     private final String mRawDexPath;  
  42.     private final String mRawLibPath;  
  43.     private final String mDexOutputPath;  
  44.   
  45.     /* 
  46.      * Parallel arrays for jar/apk files. 
  47.      * 
  48.      * (could stuff these into an object and have a single array; 
  49.      * improves clarity but adds overhead) 
  50.      */  
  51.     private final File[] mFiles;         // source file Files, for rsrc URLs  
  52.     private final ZipFile[] mZips;       // source zip files, with resources  
  53.     private final DexFile[] mDexs;       // opened, prepped DEX files  
  54.   
  55.     /** 
  56.      * Native library path. 
  57.      */  
  58.     private final String[] mLibPaths;  
  59.   
  60.     /** 
  61.      * Creates a {@code DexClassLoader} that finds interpreted and native 
  62.      * code.  Interpreted classes are found in a set of DEX files contained 
  63.      * in Jar or APK files. 
  64.      * 
  65.      * The path lists are separated using the character specified by 
  66.      * the "path.separator" system property, which defaults to ":". 
  67.      * 
  68.      * @param dexPath 
  69.      *  the list of jar/apk files containing classes and resources 
  70.      * @param dexOutputDir 
  71.      *  directory where optimized DEX files should be written 
  72.      * @param libPath 
  73.      *  the list of directories containing native libraries; may be null 
  74.      * @param parent 
  75.      *  the parent class loader 
  76.      */  
  77.     public DexClassLoader(String dexPath, String dexOutputDir, String libPath,  
  78.         ClassLoader parent) {  
  79.   
  80.         super(parent);  
  81. ......  

我们看到,他是继承了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
 
 在CODE上查看代码片派生到我的代码片
  1. /* 
  2.  * Copyright (C) 2007 The Android Open Source Project 
  3.  * 
  4.  * Licensed under the Apache License, Version 2.0 (the "License"); 
  5.  * you may not use this file except in compliance with the License. 
  6.  * You may obtain a copy of the License at 
  7.  * 
  8.  *      http://www.apache.org/licenses/LICENSE-2.0 
  9.  * 
  10.  * Unless required by applicable law or agreed to in writing, software 
  11.  * distributed under the License is distributed on an "AS IS" BASIS, 
  12.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
  13.  * See the License for the specific language governing permissions and 
  14.  * limitations under the License. 
  15.  */  
  16.   
  17. package dalvik.system;  
  18.   
  19. import java.io.ByteArrayOutputStream;  
  20. import java.io.File;  
  21. import java.io.FileNotFoundException;  
  22. import java.io.IOException;  
  23. import java.io.InputStream;  
  24. import java.io.RandomAccessFile;  
  25. import java.net.MalformedURLException;  
  26. import java.net.URL;  
  27. import java.util.ArrayList;  
  28. import java.util.Enumeration;  
  29. import java.util.List;  
  30. import java.util.NoSuchElementException;  
  31. import java.util.zip.ZipEntry;  
  32. import java.util.zip.ZipFile;  
  33.   
  34. /** 
  35.  * Provides a simple {@link ClassLoader} implementation that operates on a list 
  36.  * of files and directories in the local file system, but does not attempt to 
  37.  * load classes from the network. Android uses this class for its system class 
  38.  * loader and for its application class loader(s). 
  39.  */  
  40. public class PathClassLoader extends ClassLoader {  
  41.   
  42.     private final String path;  
  43.     private final String libPath;  
  44.   
  45.     /* 
  46.      * Parallel arrays for jar/apk files. 
  47.      * 
  48.      * (could stuff these into an object and have a single array; 
  49.      * improves clarity but adds overhead) 
  50.      */  
  51.     private final String[] mPaths;  
  52.     private final File[] mFiles;  
  53.     private final ZipFile[] mZips;  
  54.     private final DexFile[] mDexs;  
  55.   
  56.     /** 
  57.      * Native library path. 
  58.      */  
  59.     private final List<String> libraryPathElements;  
  60.   
  61.     /** 
  62.      * Creates a {@code PathClassLoader} that operates on a given list of files 
  63.      * and directories. This method is equivalent to calling 
  64.      * {@link #PathClassLoader(String, String, ClassLoader)} with a 
  65.      * {@code null} value for the second argument (see description there). 
  66.      * 
  67.      * @param path 
  68.      *            the list of files and directories 
  69.      * 
  70.      * @param parent 
  71.      *            the parent class loader 
  72.      */  
  73.     public PathClassLoader(String path, ClassLoader parent) {  
  74.         this(path, null, parent);  
  75.     }  
  76.   
  77.     /** 
  78.      * Creates a {@code PathClassLoader} that operates on two given 
  79.      * lists of files and directories. The entries of the first list 
  80.      * should be one of the following: 
  81.      * 
  82.      * <ul> 
  83.      * <li>Directories containing classes or resources. 
  84.      * <li>JAR/ZIP/APK files, possibly containing a "classes.dex" file. 
  85.      * <li>"classes.dex" files. 
  86.      * </ul> 
  87.      * 
  88.      * The entries of the second list should be directories containing 
  89.      * native library files. Both lists are separated using the 
  90.      * character specified by the "path.separator" system property, 
  91.      * which, on Android, defaults to ":". 
  92.      * 
  93.      * @param path 
  94.      *            the list of files and directories containing classes and 
  95.      *            resources 
  96.      * 
  97.      * @param libPath 
  98.      *            the list of directories containing native libraries 
  99.      * 
  100.      * @param parent 
  101.      *            the parent class loader 
  102.      */  
  103.     public PathClassLoader(String path, String libPath, ClassLoader parent) {  
  104.         super(parent);  
  105. ....  

看到了PathClassLoader类也是继承了ClassLoader的,但是他的构造函数和DexClassLoader有点区别就是,少了一个dexOutDir,这个原因也是很简单,因为PathClassLoader是加载/data/app中的apk,而这部分的apk都会解压释放dex到指定的目录:

 

/data/dalvik-cache

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

 

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

 

[java] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. package com.example.androiddemo;  
  2.   
  3. import android.app.Activity;  
  4. import android.content.Context;  
  5. import android.os.Bundle;  
  6. import android.util.Log;  
  7. import android.widget.ListView;  
  8.   
  9. public class MainActivity extends Activity {  
  10.   
  11.     @Override  
  12.     protected void onCreate(Bundle savedInstanceState) {  
  13.         super.onCreate(savedInstanceState);  
  14.         setContentView(R.layout.activity_main);  
  15.           
  16.         Log.i("DEMO", "Context的类加载加载器:"+Context.class.getClassLoader());  
  17.         Log.i("DEMO", "ListView的类加载器:"+ListView.class.getClassLoader());  
  18.         Log.i("DEMO", "应用程序默认加载器:"+getClassLoader());  
  19.         Log.i("DEMO", "系统类加载器:"+ClassLoader.getSystemClassLoader());  
  20.         Log.i("DEMO", "系统类加载器和Context的类加载器是否相等:"+(Context.class.getClassLoader()==ClassLoader.getSystemClassLoader()));  
  21.         Log.i("DEMO", "系统类加载器和应用程序默认加载器是否相等:"+(getClassLoader()==ClassLoader.getSystemClassLoader()));  
  22.           
  23.         Log.i("DEMO","打印应用程序默认加载器的委派机制:");  
  24.         ClassLoader classLoader = getClassLoader();  
  25.         while(classLoader != null){  
  26.             Log.i("DEMO", "类加载器:"+classLoader);  
  27.             classLoader = classLoader.getParent();  
  28.         }  
  29.           
  30.         Log.i("DEMO","打印系统加载器的委派机制:");  
  31.         classLoader = ClassLoader.getSystemClassLoader();  
  32.         while(classLoader != null){  
  33.             Log.i("DEMO", "类加载器:"+classLoader);  
  34.             classLoader = classLoader.getParent();  
  35.         }  
  36.           
  37.     }  
  38.   
  39. }  

 

运行结果:



依次来看一下

1) 系统类的加载器

 

[java] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  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
 
 在CODE上查看代码片派生到我的代码片
  1. Log.i("DEMO", "系统类加载器:"+ClassLoader.getSystemClassLoader());  

运行结果:

 

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

 

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

 

[java] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. Log.i("DEMO","打印应用程序默认加载器的委派机制:");  
  2. ClassLoader classLoader = 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
 
 在CODE上查看代码片派生到我的代码片
  1. package com.pluginsdk.interfaces;  
  2.   
  3. public abstract interface IBean{  
  4.   public abstract String getName();  
  5.   public abstract void setName(String paramString);  
  6. }  

 

 

2) IDynamic.java

 

[java] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. package com.pluginsdk.interfaces;  
  2.   
  3. import android.content.Context;  
  4.   
  5. public abstract interface IDynamic{  
  6.   public abstract void methodWithCallBack(YKCallBack paramYKCallBack);  
  7.   public abstract void showPluginWindow(Context paramContext);  
  8.   public abstract void startPluginActivity(Context context,Class<?> cls);  
  9.   public abstract String getStringForResId(Context context);  
  10. }  

其他的就不列举了。

 

 

2、PluginSDK工程:

1) Dynamic.java

 

[java] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. /** 
  2.  * Dynamic1.java 
  3.  * com.youku.pluginsdk.imp 
  4.  * 
  5.  * Function: TODO  
  6.  * 
  7.  *   ver     date           author 
  8.  * ────────────────────────────────── 
  9.  *           2014-10-20         Administrator 
  10.  * 
  11.  * Copyright (c) 2014, TNT All Rights Reserved. 
  12. */  
  13.   
  14. package com.pluginsdk.imp;  
  15.   
  16. import android.app.AlertDialog;  
  17. import android.app.AlertDialog.Builder;  
  18. import android.app.Dialog;  
  19. import android.content.Context;  
  20. import android.content.DialogInterface;  
  21. import android.content.Intent;  
  22.   
  23. import com.pluginsdk.bean.Bean;  
  24. import com.pluginsdk.interfaces.IDynamic;  
  25. import com.pluginsdk.interfaces.YKCallBack;  
  26. import com.youku.pluginsdk.R;  
  27.   
  28. /** 
  29.  * ClassName:Dynamic1 
  30.  * 
  31.  * @author   jiangwei 
  32.  * @version   
  33.  * @since    Ver 1.1 
  34.  * @Date     2014-10-20     下午5:57:10 
  35.  */  
  36. public class Dynamic implements IDynamic{  
  37.     /** 
  38.  
  39.      */  
  40.     public void methodWithCallBack(YKCallBack callback) {  
  41.         Bean bean = new Bean();  
  42.         bean.setName("PLUGIN_SDK_USER");  
  43.         callback.callback(bean);  
  44.     }  
  45.       
  46.     public void showPluginWindow(Context context) {  
  47.          AlertDialog.Builder builder = new Builder(context);  
  48.           builder.setMessage("对话框");  
  49.           builder.setTitle(R.string.hello_world);  
  50.           builder.setNegativeButton("取消", new Dialog.OnClickListener() {  
  51.                @Override  
  52.                public void onClick(DialogInterface dialog, int which) {  
  53.                    dialog.dismiss();  
  54.                }  
  55.               });  
  56.           Dialog dialog = builder.create();//.show();  
  57.           dialog.show();  
  58.     }  
  59.       
  60.     public void startPluginActivity(Context context,Class<?> cls){  
  61.         /** 
  62.         *这里要注意几点: 
  63.         *1、如果单纯的写一个MainActivity的话,在主工程中也有一个MainActivity,开启的Activity还是主工程中的MainActivity 
  64.         *2、如果这里将MainActivity写成全名的话,还是有问题,会报找不到这个Activity的错误 
  65.         */  
  66.         Intent intent = new Intent(context,cls);  
  67.         context.startActivity(intent);  
  68.     }  
  69.       
  70.     public String getStringForResId(Context context){  
  71.         return context.getResources().getString(R.string.hello_world);  
  72.     }  
  73.   
  74. }  

 

 

2) Bean.java

 

[java] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. /** 
  2.  * User.java 
  3.  * com.youku.pluginsdk.bean 
  4.  * 
  5.  * Function: TODO  
  6.  * 
  7.  *   ver     date           author 
  8.  * ────────────────────────────────── 
  9.  *           2014-10-20         Administrator 
  10.  * 
  11.  * Copyright (c) 2014, TNT All Rights Reserved. 
  12. */  
  13.   
  14. package com.pluginsdk.bean;  
  15.   
  16.   
  17. /** 
  18.  * ClassName:User 
  19.  * 
  20.  * @author   jiangwei 
  21.  * @version   
  22.  * @since    Ver 1.1 
  23.  * @Date     2014-10-20     下午1:35:16 
  24.  */  
  25. public class Bean implements com.pluginsdk.interfaces.IBean{  
  26.   
  27.     /** 
  28.      * 
  29.      */  
  30.     private String name = "这是来自于插件工程中设置的初始化的名字";  
  31.   
  32.     public String getName() {  
  33.         return name;  
  34.     }  
  35.   
  36.     public void setName(String name) {  
  37.         this.name = name;  
  38.     }  
  39.   
  40. }  

 

3、宿主工程HostProject

 

1) MainActivity.java

 

[java] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. package com.plugindemo;  
  2. import java.io.File;  
  3. import java.lang.reflect.Method;  
  4.   
  5. import android.annotation.SuppressLint;  
  6. import android.app.Activity;  
  7. import android.content.Context;  
  8. import android.content.res.AssetManager;  
  9. import android.content.res.Resources;  
  10. import android.content.res.Resources.Theme;  
  11. import android.os.Bundle;  
  12. import android.os.Environment;  
  13. import android.util.Log;  
  14. import android.view.View;  
  15. import android.widget.Button;  
  16. import android.widget.ListView;  
  17. import android.widget.Toast;  
  18.   
  19. import com.pluginsdk.interfaces.IBean;  
  20. import com.pluginsdk.interfaces.IDynamic;  
  21. import com.pluginsdk.interfaces.YKCallBack;  
  22. import com.youku.plugindemo.R;  
  23.   
  24. import dalvik.system.DexClassLoader;  
  25.   
  26. public class MainActivity extends Activity {  
  27.     private AssetManager mAssetManager;//资源管理器  
  28.     private Resources mResources;//资源  
  29.     private Theme mTheme;//主题  
  30.     private String apkFileName = "PluginSDKs.apk";  
  31.     private String dexpath = null;//apk文件地址  
  32.     private File fileRelease = null; //释放目录  
  33.     private DexClassLoader classLoader = null;  
  34.     @SuppressLint("NewApi")  
  35.     @Override  
  36.     protected void onCreate(Bundle savedInstanceState) {  
  37.         super.onCreate(savedInstanceState);  
  38.         setContentView(R.layout.activity_main);  
  39.         dexpath =  Environment.getExternalStorageDirectory() + File.separator+apkFileName;  
  40.         fileRelease = getDir("dex", 0);  
  41.           
  42.         /*初始化classloader 
  43.          * dexpath dex文件地址 
  44.          * fileRelease 文件释放地址  
  45.          *  父classLoader 
  46.          */  
  47.           
  48.         Log.d("DEMO", (getClassLoader()==ListView.class.getClassLoader())+"");  
  49.         Log.d("DEMO",ListView.class.getClassLoader()+"");  
  50.         Log.d("DEMO", Context.class.getClassLoader()+"");  
  51.         Log.d("DEMO", Context.class.getClassLoader().getSystemClassLoader()+"");  
  52.         Log.d("DEMO",Activity.class.getClassLoader()+"");  
  53.         Log.d("DEMO", (Context.class.getClassLoader().getSystemClassLoader() == ClassLoader.getSystemClassLoader())+"");  
  54.         Log.d("DEMO",ClassLoader.getSystemClassLoader()+"");  
  55.           
  56.         classLoader = new DexClassLoader(dexpath, fileRelease.getAbsolutePath(),null,getClassLoader());  
  57.           
  58.         Button btn_1 = (Button)findViewById(R.id.btn_1);  
  59.         Button btn_2 = (Button)findViewById(R.id.btn_2);  
  60.         Button btn_3 = (Button)findViewById(R.id.btn_3);  
  61.         Button btn_4 = (Button)findViewById(R.id.btn_4);  
  62.         Button btn_5 = (Button)findViewById(R.id.btn_5);  
  63.         Button btn_6 = (Button)findViewById(R.id.btn_6);  
  64.           
  65.         btn_1.setOnClickListener(new View.OnClickListener() {//普通调用  反射的方式  
  66.             @Override  
  67.             public void onClick(View arg0) {  
  68.                 Class mLoadClassBean;  
  69.                 try {  
  70.                     mLoadClassBean = classLoader.loadClass("com.pluginsdk.bean.Bean");  
  71.                     Object beanObject = mLoadClassBean.newInstance();  
  72.                     Log.d("DEMO", "ClassLoader:"+mLoadClassBean.getClassLoader());  
  73.                     Log.d("DEMO", "ClassLoader:"+mLoadClassBean.getClassLoader().getParent());  
  74.                     Method getNameMethod = mLoadClassBean.getMethod("getName");  
  75.                     getNameMethod.setAccessible(true);  
  76.                     String name = (String) getNameMethod.invoke(beanObject);  
  77.                     Toast.makeText(MainActivity.this, name, Toast.LENGTH_SHORT).show();  
  78.                 } catch (Exception e) {  
  79.                     Log.e("DEMO", "msg:"+e.getMessage());  
  80.                 }   
  81.             }  
  82.         });  
  83.         btn_2.setOnClickListener(new View.OnClickListener() {//带参数调用  
  84.             @Override  
  85.             public void onClick(View arg0) {  
  86.                 Class mLoadClassBean;  
  87.                 try {  
  88.                     mLoadClassBean = classLoader.loadClass("com.pluginsdk.bean.Bean");  
  89.                     Object beanObject = mLoadClassBean.newInstance();  
  90.                     //接口形式调用  
  91.                     Log.d("DEMO", beanObject.getClass().getClassLoader()+"");  
  92.                     Log.d("DEMO",IBean.class.getClassLoader()+"");  
  93.                     Log.d("DEMO",ClassLoader.getSystemClassLoader()+"");  
  94.                     IBean bean = (IBean)beanObject;  
  95.                     bean.setName("宿主程序设置的新名字");  
  96.                     Toast.makeText(MainActivity.this, bean.getName(), Toast.LENGTH_SHORT).show();  
  97.                 }catch (Exception e) {  
  98.                     Log.e("DEMO", "msg:"+e.getMessage());  
  99.                 }  
  100.                  
  101.             }  
  102.         });  
  103.         btn_3.setOnClickListener(new View.OnClickListener() {//带回调函数的调用  
  104.             @Override  
  105.             public void onClick(View arg0) {  
  106.                 Class mLoadClassDynamic;  
  107.                 try {  
  108.                     mLoadClassDynamic = classLoader.loadClass("com.pluginsdk.imp.Dynamic");  
  109.                      Object dynamicObject = mLoadClassDynamic.newInstance();  
  110.                       //接口形式调用  
  111.                     IDynamic dynamic = (IDynamic)dynamicObject;  
  112.                     //回调函数调用  
  113.                     YKCallBack callback = new YKCallBack() {//回调接口的定义  
  114.                         public void callback(IBean arg0) {  
  115.                             Toast.makeText(MainActivity.this, arg0.getName(), Toast.LENGTH_SHORT).show();  
  116.                         };  
  117.                     };  
  118.                     dynamic.methodWithCallBack(callback);  
  119.                 } catch (Exception e) {  
  120.                     Log.e("DEMO", "msg:"+e.getMessage());  
  121.                 }  
  122.                  
  123.             }  
  124.         });  
  125.         btn_4.setOnClickListener(new View.OnClickListener() {//带资源文件的调用  
  126.             @Override  
  127.             public void onClick(View arg0) {  
  128.                 loadResources();  
  129.                 Class mLoadClassDynamic;  
  130.                 try {  
  131.                     mLoadClassDynamic = classLoader.loadClass("com.pluginsdk.imp.Dynamic");  
  132.                     Object dynamicObject = mLoadClassDynamic.newInstance();  
  133.                     //接口形式调用  
  134.                     IDynamic dynamic = (IDynamic)dynamicObject;  
  135.                     dynamic.showPluginWindow(MainActivity.this);  
  136.                 } catch (Exception e) {  
  137.                     Log.e("DEMO", "msg:"+e.getMessage());  
  138.                 }  
  139.             }  
  140.         });  
  141.         btn_5.setOnClickListener(new View.OnClickListener() {//带资源文件的调用  
  142.             @Override  
  143.             public void onClick(View arg0) {  
  144.                 loadResources();  
  145.                 Class mLoadClassDynamic;  
  146.                 try {  
  147.                     mLoadClassDynamic = classLoader.loadClass("com.pluginsdk.imp.Dynamic");  
  148.                     Object dynamicObject = mLoadClassDynamic.newInstance();  
  149.                     //接口形式调用  
  150.                     IDynamic dynamic = (IDynamic)dynamicObject;  
  151.                     dynamic.startPluginActivity(MainActivity.this,  
  152.                             classLoader.loadClass("com.plugindemo.MainActivity"));  
  153.                 } catch (Exception e) {  
  154.                     Log.e("DEMO", "msg:"+e.getMessage());  
  155.                 }  
  156.             }  
  157.         });  
  158.         btn_6.setOnClickListener(new View.OnClickListener() {//带资源文件的调用  
  159.             @Override  
  160.             public void onClick(View arg0) {  
  161.                 loadResources();  
  162.                 Class mLoadClassDynamic;  
  163.                 try {  
  164.                     mLoadClassDynamic = classLoader.loadClass("com.pluginsdk.imp.Dynamic");  
  165.                     Object dynamicObject = mLoadClassDynamic.newInstance();  
  166.                     //接口形式调用  
  167.                     IDynamic dynamic = (IDynamic)dynamicObject;  
  168.                     String content = dynamic.getStringForResId(MainActivity.this);  
  169.                     Toast.makeText(getApplicationContext(), content+"", Toast.LENGTH_LONG).show();  
  170.                 } catch (Exception e) {  
  171.                     Log.e("DEMO", "msg:"+e.getMessage());  
  172.                 }  
  173.             }  
  174.         });  
  175.           
  176.     }  
  177.   
  178.      protected void loadResources() {  
  179.             try {  
  180.                 AssetManager assetManager = AssetManager.class.newInstance();  
  181.                 Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class);  
  182.                 addAssetPath.invoke(assetManager, dexpath);  
  183.                 mAssetManager = assetManager;  
  184.             } catch (Exception e) {  
  185.                 e.printStackTrace();  
  186.             }  
  187.             Resources superRes = super.getResources();  
  188.             superRes.getDisplayMetrics();  
  189.             superRes.getConfiguration();  
  190.             mResources = new Resources(mAssetManager, superRes.getDisplayMetrics(),superRes.getConfiguration());  
  191.             mTheme = mResources.newTheme();  
  192.             mTheme.setTo(super.getTheme());  
  193.         }  
  194.       
  195.     @Override  
  196.     public AssetManager getAssets() {  
  197.         return mAssetManager == null ? super.getAssets() : mAssetManager;  
  198.     }  
  199.   
  200.     @Override  
  201.     public Resources getResources() {  
  202.         return mResources == null ? super.getResources() : mResources;  
  203.     }  
  204.   
  205.     @Override  
  206.     public Theme getTheme() {  
  207.         return mTheme == null ? super.getTheme() : mTheme;  
  208.     }  
  209. }  

三个工程的下载地址: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的时候,首先会让宿主apk的PathClassLoader加载器去加载,这个好多人有点迷糊了,为什么会先让PathClassLoader加载器去加载呢?

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

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

 

[java] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. classLoader = new DexClassLoader(dexpath, fileRelease.getAbsolutePath(),null,getClassLoader());  

那么加载器就是一样的了(宿主apk的PathClassLoader),那么就奇怪了,都是一个为什么还有错误呢?查看系统源码可以了解:

 

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

 

[cpp] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. /* 
  2.  * Find the class corresponding to "classIdx", which maps to a class name 
  3.  * string.  It might be in the same DEX file as "referrer", in a different 
  4.  * DEX file, generated by a class loader, or generated by the VM (e.g. 
  5.  * array classes). 
  6.  * 
  7.  * Because the DexTypeId is associated with the referring class' DEX file, 
  8.  * we may have to resolve the same class more than once if it's referred 
  9.  * to from classes in multiple DEX files.  This is a necessary property for 
  10.  * DEX files associated with different class loaders. 
  11.  * 
  12.  * We cache a copy of the lookup in the DexFile's "resolved class" table, 
  13.  * so future references to "classIdx" are faster. 
  14.  * 
  15.  * Note that "referrer" may be in the process of being linked. 
  16.  * 
  17.  * Traditional VMs might do access checks here, but in Dalvik the class 
  18.  * "constant pool" is shared between all classes in the DEX file.  We rely 
  19.  * on the verifier to do the checks for us. 
  20.  * 
  21.  * Does not initialize the class. 
  22.  * 
  23.  * "fromUnverifiedConstant" should only be set if this call is the direct 
  24.  * result of executing a "const-class" or "instance-of" instruction, which 
  25.  * use class constants not resolved by the bytecode verifier. 
  26.  * 
  27.  * Returns NULL with an exception raised on failure. 
  28.  */  
  29. ClassObject* dvmResolveClass(const ClassObject* referrer, u4 classIdx,  
  30.     bool fromUnverifiedConstant)  
  31. {  
  32.     DvmDex* pDvmDex = referrer->pDvmDex;  
  33.     ClassObject* resClass;  
  34.     const char* className;  
  35.   
  36.     /* 
  37.      * Check the table first -- this gets called from the other "resolve" 
  38.      * methods. 
  39.      */  
  40.     resClass = dvmDexGetResolvedClass(pDvmDex, classIdx);  
  41.     if (resClass != NULL)  
  42.         return resClass;  
  43.   
  44.     LOGVV("--- resolving class %u (referrer=%s cl=%p)\n",  
  45.         classIdx, referrer->descriptor, referrer->classLoader);  
  46.   
  47.     /* 
  48.      * Class hasn't been loaded yet, or is in the process of being loaded 
  49.      * and initialized now.  Try to get a copy.  If we find one, put the 
  50.      * pointer in the DexTypeId.  There isn't a race condition here -- 
  51.      * 32-bit writes are guaranteed atomic on all target platforms.  Worst 
  52.      * case we have two threads storing the same value. 
  53.      * 
  54.      * If this is an array class, we'll generate it here. 
  55.      */  
  56.     className = dexStringByTypeIdx(pDvmDex->pDexFile, classIdx);  
  57.     if (className[0] != '\0' && className[1] == '\0') {  
  58.         /* primitive type */  
  59.         resClass = dvmFindPrimitiveClass(className[0]);  
  60.     } else {  
  61.         resClass = dvmFindClassNoInit(className, referrer->classLoader);  
  62.     }  
  63.   
  64.     if (resClass != NULL) {  
  65.         /* 
  66.          * If the referrer was pre-verified, the resolved class must come 
  67.          * from the same DEX or from a bootstrap class.  The pre-verifier 
  68.          * makes assumptions that could be invalidated by a wacky class 
  69.          * loader.  (See the notes at the top of oo/Class.c.) 
  70.          * 
  71.          * The verifier does *not* fail a class for using a const-class 
  72.          * or instance-of instruction referring to an unresolveable class, 
  73.          * because the result of the instruction is simply a Class object 
  74.          * or boolean -- there's no need to resolve the class object during 
  75.          * verification.  Instance field and virtual method accesses can 
  76.          * break dangerously if we get the wrong class, but const-class and 
  77.          * instance-of are only interesting at execution time.  So, if we 
  78.          * we got here as part of executing one of the "unverified class" 
  79.          * instructions, we skip the additional check. 
  80.          * 
  81.          * Ditto for class references from annotations and exception 
  82.          * handler lists. 
  83.          */  
  84.         if (!fromUnverifiedConstant &&  
  85.             IS_CLASS_FLAG_SET(referrer, CLASS_ISPREVERIFIED))  
  86.         {  
  87.             ClassObject* resClassCheck = resClass;  
  88.             if (dvmIsArrayClass(resClassCheck))  
  89.                 resClassCheck = resClassCheck->elementClass;  
  90.   
  91.             if (referrer->pDvmDex != resClassCheck->pDvmDex &&  
  92.                 resClassCheck->classLoader != NULL)  
  93.             {  
  94.                 LOGW("Class resolved by unexpected DEX:"  
  95.                      " %s(%p):%p ref [%s] %s(%p):%p\n",  
  96.                     referrer->descriptor, referrer->classLoader,  
  97.                     referrer->pDvmDex,  
  98.                     resClass->descriptor, resClassCheck->descriptor,  
  99.                     resClassCheck->classLoader, resClassCheck->pDvmDex);  
  100.                 LOGW("(%s had used a different %s during pre-verification)\n",  
  101.                     referrer->descriptor, resClass->descriptor);  
  102.                 dvmThrowException("Ljava/lang/IllegalAccessError;",  
  103.                     "Class ref in pre-verified class resolved to unexpected "  
  104.                     "implementation");  
  105.                 return NULL;  
  106.             }  
  107.         }  
  108.   
  109.         LOGVV("##### +ResolveClass(%s): referrer=%s dex=%p ldr=%p ref=%d\n",  
  110.             resClass->descriptor, referrer->descriptor, referrer->pDvmDex,  
  111.             referrer->classLoader, classIdx);  
  112.   
  113.         /* 
  114.          * Add what we found to the list so we can skip the class search 
  115.          * next time through. 
  116.          * 
  117.          * TODO: should we be doing this when fromUnverifiedConstant==true? 
  118.          * (see comments at top of oo/Class.c) 
  119.          */  
  120.         dvmDexSetResolvedClass(pDvmDex, classIdx, resClass);  
  121.     } else {  
  122.         /* not found, exception should be raised */  
  123.         LOGVV("Class not found: %s\n",  
  124.             dexStringByTypeIdx(pDvmDex->pDexFile, classIdx));  
  125.         assert(dvmCheckException(dvmThreadSelf()));  
  126.     }  
  127.   
  128.     return resClass;  
  129. }  

我们看下面的判断可以得到,就是在这里抛出的异常,代码逻辑我们就不看了,因为太多的头文件相互引用,看起来很费劲,直接看一下函数的说明:


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

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

 

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

这个问题产生的操作:

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

 

[java] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. classLoader = new DexClassLoader(dexpath, fileRelease.getAbsolutePath(),null,getClassLoader());  

 

修改成:

 

[java] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. classLoader = new DexClassLoader(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
 
 在CODE上查看代码片派生到我的代码片
  1. IBean bean = (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中的类加载器的相关只是来做铺垫的

posted @ 2016-03-02 15:51  chenxibobo  阅读(291)  评论(0编辑  收藏  举报