android 动态加载之插件化开发1

背景

由于Android 应用规模越来越大,程序业务越来越复杂,一个细微的修改,就需要打包重上面,这对一个大型企业来说,无疑是不利的,对用户来说,频繁的更新也让人很厌烦,对程序员来说打包akp,在运行时,在方法数,临近阀值,会报65535的错。在这种情况下,动态加载技术就应运而生,在移动互联网技术日渐成熟,Android程序员技能越来成熟的情况下,可以预见的未来,插件化开发,将会是解决Android大型应用复杂性的一种趋势,程序员学习动态加载技术也是十分必要的。

知识准备

再学习动态加载之前,先温习一下,Android 的基本知识。在这里提几个问题:
1.Android程序安装到本地,是如何被唤起的?
2。Android编译打包都的apk是如何被linux操作系统中的Dvm程序执行的?
3.Android程序中,各种图片、布局等res资源是如何被加载到程序中的?
第二、三个问题都比较复杂在这里只简单说自己理解的过程,详细分析,已经超出了作者的知识范围。
Android程序安装到本地,是如何被唤起的
在开发工具中编写好一个Android程序的源码后,编译、灌入到手机后,在桌面上回看到一个android图标,点击就可以进入编写的程序中。其实这个生成的图标,是对应到一laucher程序,点击laucher意图查看器,会便利所有组件的意图,如果有匹配的则,启动该组件,意图在哪里配置?

 <activity
            android:name=".MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
   /******************这里***************************/
                <category android:name="android.intent.category.LAUNCHER" />
                 /******************这里***************************/
            </intent-filter>
        </activity>

我们的MainActivity就是这样被启动的。为什么要说这个?
这个
<category android:name="android.intent.category.LAUNCHER" />
*应用程序中也不是必须要写的,把Android程序编辑好,在不配置
LAUNCHER的情况下也是可以的,经过试验,发现,在清单文件里面不配置任何组件也不配置laucherye 可以被安装到手机,这是这时程序是不可见的(没有任何图标),一般这样的程序是被当做插件,安装到手机的*

apk是如何在DVM中执行的?
在java程程序中,.java文件被编译器编译成字节码的.class文件后,交给JVM执行。java中可执行文件_.jar文件,jar文件中包含了,所有的编译后的.class文件,在运行时由java的类加载器,加载到jvm然后执行。在Android中,可执行的文件是dex,虚拟机是DVM(android 5.0后是ART),过程基本相同。

图片、布局等res资源是如何被加载到程序中的?
在编辑代码的时候,需要添加各种资源,图片、布局等,这些资源根据名称生成R文件,这个R文件是和代码一起编译到dex文件中的,所有的资源存放在外部(其他应用程序是访问不到的),但是此时已经被程序中的一个容器Resources 中保存起来,通过R文件的id来访问。我们知道,其实XML,或是图片都经过Android的XML解析器,通过反射生成相对应的View实例和Drawable对象,然后在程序中调用;

动态加载原理

Dalvik虚拟机如同其他Java虚拟机一样,在运行程序时首先需要将对应的类加载到内存中。而在Java标准的虚拟机中,类加载可以从class文件中读取,也可以是其他形式的二进制流,因此,我们常常利用这一点,在程序运行时手动加载Class,从而达到代码动态加载执行的目的。Android为我们从ClassLoader派生出了两个类:DexClassLoader和PathClassLoaderder
1.DexClassLoader加载:

/**使用DexClassLoader方式加载类*/  
        //dex压缩文件的路径(可以是apk,jar,zip格式)  
        String dexPath = Environment.getExternalStorageDirectory().toString() + File.separator + "Dynamic.apk";  
        //dex解压释放后的目录  
        //String dexOutputDir = getApplicationInfo().dataDir;  
        String dexOutputDirs = Environment.getExternalStorageDirectory().toString();  
        //定义DexClassLoader  
        //第一个参数:是dex压缩文件的路径  
        //第二个参数:是dex解压缩后存放的目录  
        //第三个参数:是C/C++依赖的本地库文件目录,可以为null  
        //第四个参数:是上一级的类加载器  
        DexClassLoader cl = new DexClassLoader(dexPath,dexOutputDirs,null,getClassLoader());  
        //有了这个实例,我们就可以将该路径指定的dex文件中的类加载进来

2.PathClassLoaderder加载(本文后面将使用的是这一种方式,实现插件化)

/**使用PathClassLoader方法加载类*/  
        //创建一个意图,用来找到指定的apk:这里的"com.dynamic.impl是指定apk中在AndroidMainfest.xml文件中定义的<action name="com.dynamic.impl"/>    
        Intent intent = new Intent("com.dynamic.impl", null);    
        //获得包管理器    
        PackageManager pm = getPackageManager();    
        List<ResolveInfo> resolveinfoes =  pm.queryIntentActivities(intent, 0);    
        //获得指定的activity的信息    
        ActivityInfo actInfo = resolveinfoes.get(0).activityInfo;    
        //获得apk的目录或者jar的目录    
        String apkPath = actInfo.applicationInfo.sourceDir;    
        //native代码的目录    
        String libPath = actInfo.applicationInfo.nativeLibraryDir;    
        //创建类加载器,把dex加载到虚拟机中    
        //第一个参数:是指定apk安装的路径,这个路径要注意只能是通过actInfo.applicationInfo.sourceDir来获取  
        //第二个参数:是C/C++依赖的本地库文件目录,可以为null  
        //第三个参数:是上一级的类加载器  
        PathClassLoader pcl = new PathClassLoader(apkPath,libPath,this.getClassLoader()); 

动态加载之本地插件开发

过程是这样的:
**1.构建一个项目作为插件,在项目中定义好接口和父类(一般作为Activity/Service/ContentProvider等,作为插件中各组件的父类),接口作为实体类工具类的在宿主中调用的入口
2.将这些接口导出jar包,在宿主中调用
3.在宿主中定义代理组件,来绑定Acitivyt/Service**
基本就是这三步
本文,自己做了一个demo,来此时这种方案是否可行,以及存在的问题;
a.demo
1.构建插件
这里写图片描述
所有组件类要继承我们定义的父类来实现代理,这里就是SecondActivit要继承MybasAcvitiy,(其他类型组件与Activity类似)

public class MyBaseActivity extends Activity {
    private Activity provyActivity;

    public Activity getProvyActivity() {
        return provyActivity;
    }

    public void setProvyActivity(Activity provyActivity) {//指定代理activity
        this.provyActivity = provyActivity;
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // TODO Auto-generated method stub
         super.onCreate(savedInstanceState);

    }

    @Override
    public void setContentView(int layoutResID) {
        if (provyActivity != null && provyActivity instanceof Activity) {
            TextView  textView=new  TextView(provyActivity);
            textView.setText("TextView");
            textView.setTextSize(80);
            textView.setTextColor(Color.BLUE);
            //将插件中的布局添加到代理的Activity,注意,这里如果用资源ID来添加添加布局是无效的
            provyActivity.setContentView(textView);
        }

    }

2.定义接口导出jar包(如果只是四大组件,无需其他无继承的类,则不必);
在此项目中定义了一个IDynamicImp 类来实现,Toast,那么在宿主中,就要通过接口,将加载出来的实现类(Object对象),强转,然后调用接口;

public interface IDynamic {
    public void init(Activity activity);
    public  void  one();
    public  void  tow();
}
public class IDynamicImp implements IDynamic {
    private  Activity activity;
    public void init(Activity activity) {
        this.activity=activity;
    }

    public void one() {
        // TODO Auto-generated method stub
        Toast.makeText(activity, "oneggggggggggggggggggggg", Toast.LENGTH_SHORT).show();
    }

    public void tow() {
        // TODO Auto-generated method stub
        Toast.makeText(activity, "towggggggggggggggggggggggg", Toast.LENGTH_SHORT).show();
    }

}

3新建宿主项目
这里写图片描述

public class ProxyActivity extends Activity {....}

在ProxyActivity 中来代理,插件中SecondActivity的行为

    public void setUpOnCreateMethord() {

        Intent intent = new Intent("test.action.imp", null);
        // 获得包管理器
        PackageManager pm = getPackageManager();
        List<ResolveInfo> resolveinfoes = pm.queryIntentActivities(intent, 0);
        // 获得指定的activity的信息
        ActivityInfo actInfo = resolveinfoes.get(0).activityInfo;
        // 获得apk的目录或者jar的目录
        String apkPath = actInfo.applicationInfo.sourceDir;
        // native代码的目录
        String libPath = actInfo.applicationInfo.nativeLibraryDir;
        // 创建类加载器,把dex加载到虚拟机中
        // 第一个参数:是指定apk安装的路径,这个路径要注意只能是通过actInfo.applicationInfo.sourceDir来获取
        // 第二个参数:是C/C++依赖的本地库文件目录,可以为null
        // 第三个参数:是上一级的类加载器
        PathClassLoader pcl = new PathClassLoader(apkPath, libPath,
                this.getClassLoader());
        // 加载类
        try {
            // com.dynamic.impl.Dynamic是动态类名
            // 使用DexClassLoader加载类
            // Class libProviderClazz =
            // cl.loadClass("com.dynamic.impl.Dynamic");
            // 使用PathClassLoader加载类
            libProviderClazz = pcl.loadClass("com.example.testandroidimp.SecondActivity");
            localConstructor = libProviderClazz.getConstructor(new Class[] {}); // 获取构造方法
            instance = (Activity) localConstructor.newInstance(new Object[] {}); // 创建一个实例
        } catch (Exception e) {
            e.printStackTrace();
        }
        try {
            Method setProxy = libProviderClazz.getMethod("setProvyActivity",
                    new Class[] { Activity.class }); // 最重要的方法;需要在调用onCreate方法之前调用;设置代理Activity
            setProxy.setAccessible(true); // 设置方法访问权限
            setProxy.invoke(instance, new Object[] { ProxyActivity.this }); // 调用方法

            Method onCreate = libProviderClazz.getDeclaredMethod("onCreate",
                    new Class[] { Bundle.class }); // 通过反射,得到onCreate方法
            onCreate.setAccessible(true); // 设置方法访问权限
            onCreate.invoke(instance, new Object[] { new Bundle() }); // 调用onCreate方法,调用Activity其他方法绑定生命周期类似
        } catch (Exception E) {
            E.printStackTrace();
        }

    }

b.存在问题
1.组件生命周期的管理
本例中是通过生命周期方法的绑定,见过其他案例中,插件是使用Fragement编辑,然后由代理Acvitity来管理fragement的生命周期
2.资源文件
将所有的布局使用代码写成,图片使用网络图片;有案例中用使用下面的方式AssetManager :

protected void loadResources() {
        try {
            AssetManager assetManager = AssetManager.class.newInstance();
            Method addAssetPath = assetManager.getClass().getMethod(addAssetPath, String.class);
            addAssetPath.invoke(assetManager, mDexPath);
            mAssetManager = assetManager;
        } catch (Exception e) {
            e.printStackTrace();
        }
        Resources superRes = super.getResources();
        mResources = new Resources(mAssetManager, superRes.getDisplayMetrics(),
                superRes.getConfiguration());
        mTheme = mResources.newTheme();
        mTheme.setTo(super.getTheme());
    }

将资源文件实现可访问

插件化开发遵循的原则

插件apk的开发规范

动态加载的其他几种方案

1.如本例,是使用类加载器
2.通过sharedUserId(sharedUserId相同,则在一个Linux进程);

demo下载

下一篇博文,将继续学习插件化开发,加载插件的资源文件。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值