Android插件化开发---运行未安装apk中的Service

如果你还不知道什么叫插件化开发,那么你应该先读一读之前写的这篇博客:Android插件化开发,初入殿堂

上一篇博客主要从整体角度分析了一下Android插件化开发的几个难点与动态加载没有被安装的apk中的Activity和资源的方法。其实一般的插件开发主要也就是加载个Activity,读取一些资源图片之类的。但是总有遇到特殊情况的时候,比如加载Service。

要动态加载Service,有两种思路:一是通过NDK的形式,将Service通过C++运行起来(这种方法我没有尝试,只听群里的朋友说实现过);另一种就是我使用的,具体思路和上一篇中提到加载Activity的方法一样,使用托管所的形式,由于上一篇博客没有讲清楚,这里就详细讲一下通过托管所实现加载插件中Service的方法。

以下几点是每一个Android开发组肯定都知到的: 一个apk如果没有被安装的话是没有办法直接运行的。一个JAVA类的class文件是可以通过classload类加载器读取的。一个apk实际上就是一个压缩包,其中包含了一个.dex文件就是我们的代码文件。那么,接下来基本思路我们就可以明确了:apk没办法直接运行,apk中有代码文件,代码文件可以被classload读取。

在Android中有两种classload,分别是DexClassLoader、PathClassLoader。后者只能加载/data/app目录下的apk也就是apk必须要安装才能被加载,这不是我们想要的,所以我们使用前者:DexClassLoader。

public class CJClassLoader extends DexClassLoader {
    //创建一个插件加载器集合,对固定的dex使用固定的加载器可以防止多个加载器同时加载一个dex造成的错误。
    private static final HashMap<String, CJClassLoader> pluginLoader = new HashMap<String, CJClassLoader>();

    protected CJClassLoader(String dexPath, String optimizedDirectory,
            String libraryPath, ClassLoader parent) {
        super(dexPath, optimizedDirectory, libraryPath, parent);
    }

    /**
     * 返回dexPath对应的加载器
     */
    public static CJClassLoader getClassLoader(String dexPath, Context cxt,
            ClassLoader parent) {
        CJClassLoader cjLoader = pluginLoader.get(dexPath);
        if (cjLoader == null) {
            // 获取到app的启动路径
            final String dexOutputPath = cxt
                    .getDir("dex", Context.MODE_PRIVATE).getAbsolutePath();
            cjLoader = new CJClassLoader(dexPath, dexOutputPath, null, parent);
            pluginLoader.put(dexPath, cjLoader);
        }
        return cjLoader;
    }
}

以上只是一个开始,接着我们需要考虑一个问题,一个Service是有oncreate->onstart->ondestroy生命周期以及一些回调方法的,这些回调方法在我们正常使用的时候是由父类们(包括has...a...关系)或者说是SDK管理的,那么当我们通过类加载器加载的时候,它是没有能够管理的父类的,也就是说我们需要自己模拟SDK去管理插件Service的回调函数。那么这个去管理插件Service的类,就是之前提到的托管所。

这里是我将Service中的回调方法抽出来写成的一个接口

public interface I_CJService {
    IBinder onBind(Intent intent);

    void onCreate();

    int onStartCommand(Intent intent, int flags, int startId);

    void onDestroy();

    void onConfigurationChanged(Configuration newConfig);

    void onLowMemory();

    void onTrimMemory(int level);

    boolean onUnbind(Intent intent);

    void onRebind(Intent intent);

    void onTaskRemoved(Intent rootIntent);
}

.

//一个托管所类
class CJProxyService extends Service{
    //采用包含关系
    protected I_CJService mPluginService; // 插件Service对象
}

这里采用包含关系而不是采用继承(或者说实现一个接口)的方式,是由于我们需要重写Service中的方法,而这些被重写的方法都需要用到接口对象相应的接口方法。

public class CJProxyService extends Service{    
    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        mPluginService.onConfigurationChanged(newConfig);
        super.onConfigurationChanged(newConfig);
    }

    @Override
    public void onLowMemory() {
        mPluginService.onLowMemory();
        super.onLowMemory();
    }

    @Override
    @SuppressLint("NewApi")
    public void onTrimMemory(int level) {
        mPluginService.onTrimMemory(level);
        super.onTrimMemory(level);
    }

    @Override
    public boolean onUnbind(Intent intent) {
        mPluginService.onUnbind(intent);
        return super.onUnbind(intent);
    }

    @Override
    public void onRebind(Intent intent) {
        mPluginService.onRebind(intent);
        super.onRebind(intent);
    }
}

看到这里大家应该也就明白了,托管所实际上就是一个普通的Service类,但是这个托管所是正常运行的,是由SDK管理回调函数的,我们通过这个Service的回调函数去调用插件Service中相应的回调方法,就间接的管理了插件Service的生命周期(此处可以类比Activity与Fragment的关系)

到这里为止,我们已经可以成功调起一个插件Service了,接下来的问题就是这个I_CJSrvice对象从哪里来?很简单,通过类加载器加载一个

    private void init(Intent itFromApp) {

        Object instance = null;
        try {
            Class<?> serviceClass;
            if (CJConfig.DEF_STR.equals(mDexPath)) {
                serviceClass = super.getClassLoader().loadClass(mClass);
            } else {
                serviceClass = this.getClassLoader().loadClass(mClass);
            }
            Constructor<?> serviceConstructor = serviceClass
                    .getConstructor(new Class[] {});
            instance = serviceConstructor.newInstance(new Object[] {});
        } catch (Exception e) {
        }
        setRemoteService(instance);
        mPluginService.setProxy(this, mDexPath);
    }

    /**
     * 保留一份插件Service对象
     */
    protected void setRemoteService(Object service) {
        if (service instanceof I_CJService) {
            mPluginService = (I_CJService) service;
        } else {
            throw new ClassCastException(
                    "plugin service must implements I_CJService");
        }
    }

这样就可以拿到一个I_CJSrvice对象mPluginService了,如果到此为止,还是会有问题,因为此时mPluginService中例如onStart方法还对应的是那个插件中的onStart也就是父类的onStart(这里比较绕,我不知道该如何描述),而之前我们又说过,通过反射加载的类是没有父类的,那么如果此时强制调用那个反射对象的@Override方法是会报空指针的,因为找不到父类。那么解决的办法就是再去插件Service中重写每个@Override的方法。

//.......篇幅有限,部分截取
public abstract class CJService extends Service implements I_CJService {
    /**
     * that指针指向的是当前插件的Context(由于是插件化开发,this指针绝对不能使用)
     */
    protected Service that; // 替代this指针

    @Override
    public IBinder onBind(Intent intent) {
        if (mFrom == CJConfig.FROM_PLUGIN) {
            return null;
        } else {
            return that.onBind(intent);
        }
    }
}

通过代可以看到:我们使用了一个that对象来替代原本的this对象,然后我们只需要通过在托管所中将这个that对象赋值为托管所的this对象,也就是插件中的所有that.xxx都相当于调用的是托管所的this.xxx,那么动态替换的目的就达到了,这样我们也就成功的加载了一个未被安装的插件apk中的Service。

有关本类中的代码,以及完整的Demo,你可以关注:Android插件式开发框架 CJFrameForAndroid

时间: 2024-08-02 07:01:28

Android插件化开发---运行未安装apk中的Service的相关文章

Android插件化开发,初入殿堂

好久没有写博客了,这次准备写写我这几天的研究成果--Android插件化开发框架CJFrameForAndroid. 好久没有写博客了,这次准备写写我这几天的研究成果--Android插件化开发框架CJFrameForAndroid. 背景交代 首先,你需要知道什么是插件化开发.就拿最常见的QQ来说,在第三个界面动态那里有个管理,点开后可以选择很多的增植功能,这里腾讯只放了一些网页应用,那么如果未来想加入一个打飞机游戏,要怎么做?让用户重新安装吗,这就是插件化开发所解决的问题. 用一句话来概括插

详解Android插件化开发-资源访问

动态加载技术(也叫插件化技术),当项目越来越庞大的时候,我们通过插件化开发不仅可以减轻应用的内存和CPU占用,还可以实现热插拔,即在不发布新版本的情况下更新某些模块. 通常我们把安卓资源文件制作成插件的形式,无外乎有一下几种: zip.jar.dex.APK(未安装APK.安装APK) 对于用户来讲未安装的APK才是用户所需要的,不安装.不重启,无声无息的加载资源文件,这正是我们开发者追求的结果. 但是,开发中宿主程序调起未安装的插件apk,一个很大的问题就是资源如何访问,这些资源文件的ID都映

Android 插件化开发-主题皮肤更换

参考 http://www.2cto.com/kf/201501/366859.html 本项目是以插件化开发思想进行的,主要工作和代码如下 资源文件,这里以color资源为例 1.首先我们需要准备一个皮肤包,这个皮肤包里面不会包含任何Activity,里面只有资源文件,这里我为了简单,仅仅加入一个color.xml(其实就相当于Android系统中的framework_res.apk) <!--?xml version="1.0" encoding="utf-8&qu

Android插件化开发之解决Atlas组件在宿主的注册问题

OpenAtlas有一个问题,就是四大组件必须在Manifest文件中进行注册,那么就必然带来一个问题,插件中的组件都要重复在宿主中注册.像Service,ContentProvider等组件目前没有什么好的解决方法,只能在宿主中注册.但是像Activity,显然是有解决方法的,就是使用Fragment代替Activity,Activity只是作为一个放Fragment的容器,那么不仅在插件中不用再清单文件中注册,就连宿主的注册问题也一并解决了.那么,解决方案呢,没错,就是之前写的一篇博文And

Android 插件化开发(三):资源插件化

在前面的文章中我们成功的加载了外部的Dex(Apk)并执行了插件的Bean代码.这时我们会想,能不能加载并运行插件Apk的Activity.答案当然是能,否则后续我们的研究就没意义了,但是想实现Activity的插件化运行,我们必须要解决一个问题——如何使用插件中的资源. 本文我们就讲一下插件的资源加载机制,并讲述一下如何实现资源的插件化. 一.资源的加载机制 Android的资源文件分为两类: 第一类是res目录下存放的可编辑的资源文件,这类文件在编译时系统会自动在R文件中生成资源文件的16进

Android插件化开发-hook动态代理

首先,我们阐述为什么android需要插件化: 1:由于业务的增长,app的方法数逐渐达到65535(有人说用于检索方法数的列表大小使用short存储的,其实我看了源码之后并没有发现相关信息,并对此说法产生了怀疑,不过最后找到的结果就是,65535这个限制可能是由于dalvik的bytecode大小限制的,具体的可以查看官方文档). 2:一个模块的变化都要整体编译一次app,维护成本太大了,用插件开发app会好很多 对于以上问题解决方案不少,著名的有h5,hybird,不过这些都没有native

android插件化开发——加载广播

阅读本文前,先阅读前面几篇: http://blog.csdn.net/u013022222/article/details/51171720 引言 在android开发过程中,我们不可避免的会使用广播,比如,侦听开机,侦听短信. 而对于广播,我想很多人都知道他有两种类型,动态广播,通过代码在runtime进行register, 像这样: IntentFilter intentFilter = new IntentFilter("com.chan.plugin.receiver");

Android插件化探索(三)免安装运行Activity(上)

[Android插件化探索(一)类加载器DexClassLoader] [Android插件化探索(二)资源加载] 前情提要 在上一篇中有一个细节没有提到,那就是getResourcesForApplication和AssetManager的区别. getResourcesForApplication getResourcesForApplication(String packageName),很显然需要传入一个包名,换言之,这个插件必须已经被安装在系统内,然后才能通过包名来获取资源.你可能会想

Android插件化(三)加载插件apk中的Resource资源

Android加载插件apk中的Resource资源 简介 如何加载未安装apk中的资源文件呢?我们从android.content.res.AssetManager.java的源码中发现,它有一个私有方法addAssetPath,只需要将apk的路径作为参数传入,我们就可以获得对应的AssetsManager对象,然后我们就可以使用AssetsManager对象,创建一个Resources对象,然后就可以从Resource对象中访问apk中的资源了.总结如下: 1.新建一个AssetManag