android 插件化开发(个人理解入门)

来源:互联网 发布:山东大学网络教育登录 编辑:程序博客网 时间:2024/06/15 02:03

个人感觉android 插件化是未来发展的一个需要,用户为什么必须按照开发人员的来使用,用户也可以根据自己的喜好来决定是否需要这个功能,想不想要这个功能,插件化就是一种可以根据用户的来定制的一种快速开发的方式。

首先插件化个人理解有这几个好处,第一个是可以减小app的体积,第二个方便app的更新,第三个是用户定制化app。

我是个人研究,目前处于入门,插件化的基础是classloader,俗称类的加载器,通过类的加载器来调用外部的应用资源,其次是反射,用的很多的反射注解调用外部应用于内部结合。

以下是我依照别人的例子来实现的方法:

1:建造一个主程序,通过主程序来下载各种插件,将这个主程序命名为mainapp吧

2:建一个插件程序,这个插件命名为Plugin,将注释1和2注释掉,这样这个程序就无法进行编译,但是可以生成apk

  1. <application  
  2.     android:allowBackup="true"  
  3.     android:icon="@drawable/ic_launcher"  
  4.     android:label="@string/app_name"  
  5.     android:theme="@style/AppTheme" >  
  6.     <activity  
  7.         android:name="com.example.testplugin.MainActivity"  
  8.         android:label="@string/app_name" >  
  9.         <intent-filter>  
  10.            // <action android:name="android.intent.action.MAIN" />   //注释1  
  11.            // <category android:name="android.intent.category.LAUNCHER" /> //注释2  
  12.         </intent-filter>  
  13.     </activity>  
  14. </application>  
3:想要让主程序找到这个插件程序的话需要在插件中加上

  1. <application  
  2.     android:allowBackup="true"  
  3.     android:icon="@drawable/ic_launcher"  
  4.     android:label="@string/app_name"  
  5.     android:theme="@style/AppTheme" >  
  6.     <activity  
  7.         android:name="com.example.testplugin.MainActivity"  
  8.         android:label="@string/app_name" >  
  9.         <intent-filter>  //加上这个最主要的才能主程序找到
  10.             <action android:name="com.example.testplugin.client" />  
  11.         </intent-filter>  
  12.     </activity>  
  13. </application>  
4:住程序搜索到插件程序。在主程序中此时就可以搜索到符合条件的插件了。

  1. Intent intent = new Intent("com.example.testplugin.client",null);  
  2. PackageManager pm = context.getPackageManager();  
  3. List<ResolveInfo> plugins = pm.queryIntentActivities(intent, 0);for(int i = 0;i < plugins.size();i++)  
  4. {  
  5.      ResolveInfo rinfo = plugins.get(i);  
  6.      ActivityInfo ainfo = rinfo.activityInfo;  
  7. }  
5:主程序获取到插件程序的名称版本信息。Plugin其实也是一个app程序,我们可以给每个Plugin定义一个名称,版本号以便在主程序中可以更好的显示区别及升级插件程序。

在testPlugin程序的string.xml文件里添加如下:

[java] view plain copy
  1. <string name="version">1</string>  
  2. <string name="name">Plugin</string>  

6在主程序里就可以获取到插件程序里的这些信息了。

[java] view plain copy
  1. Resources res = pm.getResourcesForApplication(ainfo.packageName);  //包名可以通过步骤4中ResolveInfo里面ActivityInfo成员变量获取到  
  2. int id = 0;  
  3. id = res.getIdentifier("name""string", ainfo.packageName); //获取到testPlugin里面定义的name  
  4. name = res.getString(id);  
  5. id = res.getIdentifier("version""string", ainfo.packageName);//获取到testPlugin里面定义的version  
  6. version = res.getString(id);  

7:通信:通过类加载器并反射机制实现。类加载器(ClassLoader)可以动态装载Class文件,标准的Java SDK中有一个ClassLoader类,它可以装载想要的Class文件。在我们以前的开发中,需要使用哪个类,直接import进来就可以了,使用import引用类文件有两个特点:

1:必须存在本地,当程序运行时需要改类时,内部类装载器会自动装载该类,这对程序员来说是透明的,程序员感知不到该过程。

2:编译时必须在现场,否则编译过程会因找不到引用文件而不能正常编译。

但是在有些情况下所需要的类不能满足以上条件。就如Host程序里可能需要调用Plugin程序里面某个类的一个函数的功能。此时这个过程是在运行是动态调用的。对Android程序而言,虽然本质是Java开发,并使用标准的Java编译器编译出Class文件,但最终的APK文件中包含的确是dex类型的文件。dex文件是将所需要的所有Class文件重新打包,打包的规则不是简单的压缩,而是完全对Class文件内部的各种函数表,变量表等进行优化,并产生一个新的文件,这就是dex文件。由于dex文件是一种经过优化的Class文件,因此要加载这样特殊的Class文件需要特殊的类加载器,这就是DexClassLoader。

testPlugin程序里添加一个PluginClass.java的类文件:

[java] view plain copy
  1. package com.example.testplugin;  
  2. import com.example.testhost.Comm;  
  3. public class PluginClass{  
  4.     @Override  
  5.     public int function1(int a, int b) {  
  6.         // TODO Auto-generated method stub  
  7.         return a+b;  
  8.     }  
  9. }  

在主程序里通过如下方式可以调用到PluginClass类里面的function1函数完成计算。

[java] view plain copy
  1. public void classLoader() {  
  2.     Intent intent = new Intent("com.example.testplugin.client",null);  
  3.   
  4.     PackageManager pm = getPackageManager();  
  5.     final List<ResolveInfo> plugins = pm.queryIntentActivities(intent, 0);  
  6.     ResolveInfo rinfo = plugins.get(0);  
  7.     ActivityInfo ainfo = rinfo.activityInfo;      
  8.   
  9.     String div = System.getProperty("path.separator"); // 分隔符  
  10.     String packagename = ainfo.packageName;  
  11.     String dexPath = ainfo.applicationInfo.sourceDir; // 目标类所在apk或者jar文件的路径  
  12.     String dexOutputDir = getApplicationInfo().dataDir; // 指定解压出的dex文件存放路径  
  13.     String libPath = ainfo.applicationInfo.nativeLibraryDir; // 目标类所使用的C/C++类库存放路径  
  14.     DexClassLoader cl = new DexClassLoader(dexPath, dexOutputDir, libPath,this.getClass().getClassLoader());  
  15.     try {  
  16.         Class<?> clazz = cl.loadClass(packagename + ".PluginClass"); // Class是ClassLoader所能识别的类,此处只是装载了PluginClass的程序代码  
  17.         Object obj = clazz.newInstance(); // 此处才真正返回了PluginClass对象,  
  18.         // 尽管返回了PluginClass对象,但是本地并没有任何PluginClass类的定义,所以只能通过反射机制调用PluginClass类里面的方法。  
  19.         Class[] params = new Class[2];  
  20.         params[0] = Integer.TYPE;  
  21.         params[1] = Integer.TYPE;  
  22.                 Method action = clazz.getMethod("function1", params); // 此处可以返回该类中的任何方法  
  23.         Integer ret = (Integer) action.invoke(obj, 12); // 第一个参数为执行目标函数的对象  
  24.         Toast.makeText(this"" + ret, Toast.LENGTH_SHORT).show();  
  25.     }...  
  26. }  
在上面方式中,通过ClassLoader装载的类有点繁琐,获取了到了PluginClass对象还需要反射方式去调用类里面的函数,那么能不能直接通过对象.函数的方式去调用PluginClass里面的函数呢?
在testHost程序中定义一个接口Comm.java文件:

[java] view plain copy
  1. package com.example.testhost;  
  2.   
  3. public interface Comm {  
  4.     public int function1(int a,int b);  
  5. }  
重写testPlugin里面的PluginClass.java的类文件,我们看到PluginClass继承了Comm接口,但是在testPlugin里面不能直接把testHost程序里的Comm.java文件拷贝过来,否则会抛出异常,需要将testHost里面Comm.java导出成jar包,然后再添加到testPlugin程序里面:
[java] view plain copy
  1. package com.example.testplugin;  
  2. import com.example.testhost.Comm;  
  3. public class PluginClass implements Comm{  
  4.     @Override  
  5.     public int function1(int a, int b) {  
  6.         // TODO Auto-generated method stub  
  7.         return a+b;  
  8.     }  
  9. }  
重写主程序里面的classLoader函数:

[java] view plain copy
  1. public void classLoader() {  
  2.     Intent intent = new Intent("com.example.testplugin.client",null);  
  3.   
  4.     PackageManager pm = getPackageManager();  
  5.     final List<ResolveInfo> plugins = pm.queryIntentActivities(intent, 0);  
  6.     ResolveInfo rinfo = plugins.get(0);  
  7.     ActivityInfo ainfo = rinfo.activityInfo;      
  8.   
  9.     String div = System.getProperty("path.separator"); // 分隔符  
  10.     String packagename = ainfo.packageName;  
  11.     String dexPath = ainfo.applicationInfo.sourceDir; // 目标类所在apk或者jar文件的路径  
  12.     String dexOutputDir = getApplicationInfo().dataDir; // 指定解压出的dex文件存放路径  
  13.     String libPath = ainfo.applicationInfo.nativeLibraryDir; // 目标类所使用的C/C++类库存放路径  
  14.     DexClassLoader cl = new DexClassLoader(dexPath, dexOutputDir, libPath,this.getClass().getClassLoader());  
  15.     try {  
  16.                Class<?> clazz = cl.loadClass(packagename + ".PluginClass");  
  17.                Comm com = (Comm) clazz.newInstance();  
  18.                Integer ret = com.function1(1,2);  
  19.                Toast.makeText(this"" + ret, Toast.LENGTH_SHORT).show();  
  20.     }...  
  21. }  
好了,通过上述方式就可以在主程序和插件程序里面相互调用对方的类功能了。


通信:广播方式。广播方式也比较简单

代码下载请看博客http://download.csdn.net/download/qq1060844713/9940216