zoukankan      html  css  js  c++  java
  • Android插件化开发---执行未安装apk中的Service

    欢迎各位增加我的Android开发群[257053751] 

    假设你还不知道什么叫插件化开发。那么你应该先读一读之前写的这篇博客: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对象从哪里来?非常easy,通过类载入器载入一个

    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
  • 相关阅读:
    C语言函数qsort的使用方法
    成绩打分
    distance.c
    留学生题目
    6大排序算法比较
    小游戏得分[石头剪刀布]
    二叉排序树算法
    头文件相关
    小型考试系统
    小题目【链表1】
  • 原文地址:https://www.cnblogs.com/blfshiye/p/5083214.html
Copyright © 2011-2022 走看看