插件化开发实战,手游SDK使用插件化

插件化开发实战,手游SDK使用插件化

插件化的目的主要是为了APP减少体积,也可实现APP热更新,使用原理就是java反射,需要了解的知识就是APP启动流程,Resources加载流程,需要了解源码。

插件化开发离不开宿主APK,插件APK和中间依赖层的相互调用,一般实现有两种,一种是新建代理Activity,在代理Activity中重写getResource和getAsset等资源方法,一种是在宿主APKActivity中直接调用插件APK资源,同样需要重写getResource和getAsset等资源方法,这两种的区别就在于,手游SDK中界面有的是ActivityUI,有的是依附于Activity上的DialogUI或者其他UI。

一.先了解APP加载资源流程

1.当一个APP启动,先由用户在手机桌面点击Icon,手机桌面其实也是一个Activity,叫Launcher,点击Icon相当于程序中点击Button跳转新界面,而这个新界面就是我们APP的第一个启动界面MainActivity。

2.我们都知道,跳转时调用Activity类中的startActivity

3.继续调用Activity类中startActivityForResult

4.再调用Instrumentation类中execStartActivity,到此APP启动就开始和最最重要的成员相遇,也就是ActivityManagerService(AMS),ActivityThread(继承ClientTransactionHandler)。在execStartActivity先创建ActivityThread中的ApplicationThread私有内部类,来实现各种和Activity生命周期有关方法。

5.Instrumentation类中execStartActivity结尾调用AMS中的startActivity,return startActivityAsUser

6.在startActivityAsUser中又return ActivityStartController.obtainStarter,来创建ActivityStarter类,并在最后调用ActivityStarter类中的execute,继续执行ActivityStarter类startActivityMayWait或者startActivity,但是最终还是在本类中经过多次调用不同重载的startActivity后调用ActivityStarter类startActivityUnchecked

7.调用ActivityStackSupervisor类中的resumeFocusedStackTopActivityLocked

8.在return ActivityStack.resumeTopActivityUncheckedLocked

9.调用ActivityStack类中的resumeTopActivityinnerLocked,在这里处理在跳转一个新的Activity界面时,需要resume前一个Activity。在这里会提前接触到一个新的类ResumeActivityItem(继承ActivityLifecycleItem,又继承ClientTransactionItem,一个Activity生命周期管理类,全部生命周期都会有各自的对应类最后交由AMS中创建的ClientLifcycleManager中的scheduleTransaction处理)

10.在ActivityStack.resumeTopActivityUncheckedLocked最后会调用ActivityStackSupervisor类中的startSpecificActivityLocked,到了一个重点,这里会判断我们启动的这个APP在之前是否已经创建了进程,即是否已经启动,也就是判断ApplicationThread的接口类IApplicationThread是否创建

11.如果创建调用ActivityStackSupervisor类中的realStartActivityLocked,在这里我们又会接触到一个LaunchActivityItem(继承ClientTransactionItem),同时之前在经过ResumeActivityItem也说过,会将所以Activity生命周期交给AMS中创建的ClientLifcycleManager中的scheduleTransaction类处理,这里需要一个参数就是ClientTransaction,这个类中有3个方法,obtain(即创建一个ClientTransaction实例),addCallback(即将当前生命周期对用的Item实例),schedule(即执行ClientLifcycleManager中的scheduleTransaction,也就是执行IApplicationThread中的scheduleTransaction)。那么IApplicationThread中的scheduleTransaction中到底做了什么,因为这是一个接口那必定有实现也就是在ActivityThread中的ApplicationThread实现类的scheduleTransaction,最后调用ClientTransactionHandler(ActivityThread继承于此类)的scheduleTransaction,通过Handler发送一个EXECUTE_TRANSACTION消息执行ActivityThread中H对应的实现体,最后调用TransactionExecutor类中的execute执行本类中executeCallbacks和executeLifecycleState,最终执行ClientTransactionItem中的execute和postExecute,因为在之前有生命周期对应Item传入,所以就会执行LaunchActivityItem中execute和postExecute方法,在这里调用ActivityThread类中的handleLaunchActivity,在调用performLaunchActivity,在这里创建Activity实例,并执行Activity中attach方法(创建PhoneWindow实例),到此一个Activity创建完成。

12.再说如果判断没有创建IApplicationThread,即没有进程,则需要先创建进程,调用AMS中的startProcessLocked,startProcess,Process.start,ZygoteProcess,再由虚拟机执行ActivityThread中main方法创建Looper实例,ActivityThread实例,并执行attach方法,在这里会调用AMS中的attachApplication方法,按照AMS执行逻辑,调用ActivityThread中的bindApplication,通过Handler发送一个BIND_APPLICTION消息执行ActivityThread中H对应的实现体,最终调用ActivityThread中的handleBindApplication,在这里创建ContentImpl实例,ApplicationInfo实例,LoadedApk实例。同时在结尾会调用ActivityStackSupervisor类中的attachApplicationLocked,执行ActivityStackSupervisor类中的realStartActivityLocked,之前说过内部实现,也就是重复一次11。到此APP启动流程结束。

二,了解了APP启动流程,那就要知道在启动的时候什么时机处理了Resource

由一我们知道,不管是没有进程创建进程还是有进程直接创建Activity,都会在对应方法中创建ContentImpl实例,即createAppContext,createActivityContext,在这两个方法中,都会new ContextImpl,并setResources,就是ContextImpl中的mResources这个私有变量都会变,那么这就是接触插件化资源的关键所在。

三,开始搭建一个宿主APK,插件APK,中间依赖类的项目

1.中间依赖,必须存在一个Activity一个Service并继承Activity和Service还有实现对应其各自生命周期接口,插件APK中Activity和Service必须继承这里的Activity和Service,宿主APK必须使用对应各自生命周期接口,即使用反射原理在代理Activity或者Service中调用对应接口方法。

package com.leo.plugin;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.content.pm.ApplicationInfo;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;


public class PluginActivity extends Activity implements PluginAInterface {
    /**
     * Activity context -> ProxyActivity
     */
    protected Activity hostActivity;

    @Override
    public void attach(Activity proxyActivity) {
        this.hostActivity = proxyActivity;
    }

    @SuppressLint("MissingSuperCall")
    @Override
    public void onCreate(Bundle savedInstanceState) {
    }

    @SuppressLint("MissingSuperCall")
    @Override
    public void onStart() {
    }

    @SuppressLint("MissingSuperCall")
    @Override
    public void onRestart() {
    }

    @SuppressLint("MissingSuperCall")
    @Override
    public void onResume() {
    }

    @SuppressLint("MissingSuperCall")
    @Override
    public void onPause() {
    }

    @SuppressLint("MissingSuperCall")
    @Override
    public void onStop() {
    }

    @SuppressLint("MissingSuperCall")
    @Override
    public void onDestroy() {
    }

    @Override
    public void onBackPressed() {
        if (hostActivity != null) {
            hostActivity.finish();
        } else {
            finish();
        }
    }

    @SuppressLint("MissingSuperCall")
    @Override
    public void onNewIntent(Intent intent) {
    }

    //--------------------------凡事用到上下文的地方都需要重写,需要重写的方法-----------------------------//
    @Override
    public void setContentView(View view) {
        if (hostActivity != null) {
            hostActivity.setContentView(view);
        } else {
            super.setContentView(view);
        }
    }

    @Override
    public void setContentView(int layoutResID) {
        if (hostActivity != null) {
            hostActivity.setContentView(layoutResID);
        } else {
            super.setContentView(layoutResID);
        }
    }

    @Override
    public void setContentView(View view, ViewGroup.LayoutParams params) {
        if (hostActivity != null) {
            hostActivity.setContentView(view, params);
        } else {
            super.setContentView(view, params);
        }
    }

    @Override
    public void startActivity(Intent intent) {
        if (hostActivity != null) {
            //ProxyActivity --->className
            Intent m = new Intent();
            m.putExtra("className", intent.getComponent().getClassName());
            hostActivity.startActivity(m);
        } else {
            super.startActivity(intent);
        }
    }

    @Override
    public ComponentName startService(Intent service) {
        if (hostActivity != null) {
            //ProxyActivity --->serviceName
            Intent m = new Intent();
            m.putExtra("serviceName", service.getComponent().getClassName());
            return hostActivity.startService(m);
        }
        return super.startService(service);
    }

    @Override
    public boolean stopService(Intent name) {
        if (hostActivity != null) {
            Intent m = new Intent();
            m.putExtra("serviceName", name.getComponent().getClassName());
            return hostActivity.stopService(m);
        }
        return super.stopService(name);
    }

    @Override
    public boolean bindService(Intent service, ServiceConnection conn, int flags) {
        if (hostActivity != null) {
            //ProxyActivity --->serviceName
            Intent m = new Intent();
            m.putExtra("serviceName", service.getComponent().getClassName());
            return hostActivity.bindService(m, conn, flags);
        }
        return super.bindService(service, conn, flags);
    }

    @Override
    public void unbindService(ServiceConnection conn) {
        if (hostActivity != null) {
            hostActivity.unbindService(conn);
        } else {
            super.unbindService(conn);
        }
    }

    @Override
    public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
        return hostActivity != null ? hostActivity.registerReceiver(receiver, filter) : super.registerReceiver(receiver, filter);
    }

    @Override
    public void unregisterReceiver(BroadcastReceiver receiver) {
        if (hostActivity != null) {
            hostActivity.unregisterReceiver(receiver);
        } else {
            super.unregisterReceiver(receiver);
        }
    }

    @Override
    public void sendBroadcast(Intent intent) {
        if (hostActivity != null) {
            hostActivity.sendBroadcast(intent);
        } else {
            super.sendBroadcast(intent);
        }
    }

    @Override
    public  T findViewById(int id) {
        return (T) (hostActivity != null ? hostActivity.findViewById(id) : super.findViewById(id));
    }

    @Override
    public Intent getIntent() {
        return hostActivity != null ? hostActivity.getIntent() : super.getIntent();
    }

    @Override
    public ClassLoader getClassLoader() {
        return hostActivity != null ? hostActivity.getClassLoader() : super.getClassLoader();
    }

    @Override
    public LayoutInflater getLayoutInflater() {
        return hostActivity != null ? hostActivity.getLayoutInflater() : super.getLayoutInflater();
    }

    @Override
    public ApplicationInfo getApplicationInfo() {
        return hostActivity != null ? hostActivity.getApplicationInfo() : super.getApplicationInfo();
    }

    @Override
    public Window getWindow() {
        return hostActivity != null ? hostActivity.getWindow() : super.getWindow();
    }

    @Override
    public WindowManager getWindowManager() {
        return hostActivity != null ? hostActivity.getWindowManager() : super.getWindowManager();
    }

    //--------------------------凡事用到上下文的地方都需要重写,需要重写的方法-----------------------------//
}
package com.leo.plugin;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;

public interface PluginAInterface {
    /**
     * 上下文的传递 通过上下文来启动Activity
     *
     * @param proxyActivity
     */
    void attach(Activity proxyActivity);

    //---------------------- 生命周期 传递到插件中 -------------------------//

    void onCreate(Bundle savedInstanceState);
    void onStart();
    void onRestart();
    void onResume();
    void onPause();
    void onStop();
    void onDestroy();
    void onBackPressed();
    void onNewIntent(Intent intent);
}

2.插件APK,必须继承PluginActivity

package com.leo.proxyplugin;

import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;

import com.leo.plugin.PluginActivity;

public class MainActivity extends PluginActivity {

    private Button button;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main_activity);

        button = (Button) findViewById(R.id.btn);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                startActivity(new Intent(hostActivity, LoginActivity.class));
            }
        });
    }

}

注意这里的hostActivity,就是宿主APK那个代理类的对象,插件中所以使用this的地方都要换成hostActivity

3.宿主APK,必须反射,并调用PluginAInterface对应接口方法

package com.leo_sdk.platformsdk.loadClass;

import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.content.res.Resources;
import android.os.Bundle;
import android.util.Log;

import com.leo.plugin.PluginAInterface;


public class ProxyActivity extends Activity {

    /**
     * 目标插件apk的activity
     */
    PluginAInterface mTargetActivity;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //被加载插件的apk的Activity全名
        String className = getIntent().getStringExtra("className");
        Log.i("Activity全名", className);
        try {
            //这里需要用DexClassLoader,不能用Class.forName,因为目标还是java文件没有被编译成class文件
            //加载该Activity的字节码对象
            Class activityClass = getClassLoader().loadClass(className);
            //创建该Activity的实例
            Object newInstance = activityClass.newInstance();
            //程序健壮性检查
            if (newInstance instanceof PluginAInterface) {
                mTargetActivity = (PluginAInterface) newInstance;
                //将代理Activity的实例传递给三方Activity
                mTargetActivity.attach(this);
                //创建bundle用来与三方apk传输数据
                Bundle bundle = new Bundle();
                //调用三方Activity的onCreate
                mTargetActivity.onCreate(bundle);
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public void startActivity(Intent intent) {
        String className = intent.getStringExtra("className");
        Intent i = new Intent(this, ProxyActivity.class);
        i.putExtra("className", className);
        super.startActivity(i);
    }

    @Override
    public ComponentName startService(Intent service) {
        String serviceName = service.getStringExtra("serviceName");
        Intent i = new Intent(this, ProxyService.class);
        i.putExtra("serviceName", serviceName);
        return super.startService(i);
    }

    @Override
    public boolean stopService(Intent name) {
        String serviceName = name.getStringExtra("serviceName");
        Intent i = new Intent(this, ProxyService.class);
        i.putExtra("serviceName", serviceName);
        return super.stopService(i);
    }

    @Override
    public boolean bindService(Intent service, ServiceConnection conn, int flags) {
        String serviceName = service.getStringExtra("serviceName");
        Intent i = new Intent(this, ProxyService.class);
        i.putExtra("serviceName", serviceName);
        return super.bindService(i, conn, flags);
    }

    @Override
    public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
        IntentFilter newInterFilter = new IntentFilter();
        for (int i = 0; i < filter.countActions(); i++) {
            newInterFilter.addAction(filter.getAction(i));
        }
        return super.registerReceiver(receiver, newInterFilter);
    }

    @Override
    public ClassLoader getClassLoader() {
        return PluginManager.getInstance().getPluginDexClassLoader();
    }

    @Override
    public Resources getResources() {
        return PluginManager.getInstance().getPluginResources();
    }


    @Override
    protected void onStart() {
        if (mTargetActivity != null)
            mTargetActivity.onStart();
        super.onStart();
    }

    @Override
    protected void onRestart() {
        if (mTargetActivity != null)
            mTargetActivity.onRestart();
        super.onRestart();
    }

    @Override
    protected void onResume() {
        if (mTargetActivity != null)
            mTargetActivity.onResume();
        super.onResume();
    }

    @Override
    protected void onPause() {
        if (mTargetActivity != null)
            mTargetActivity.onPause();
        super.onPause();
    }

    @Override
    protected void onStop() {
        if (mTargetActivity != null)
            mTargetActivity.onStop();
        super.onStop();
    }

    @Override
    protected void onDestroy() {
        if (mTargetActivity != null)
            mTargetActivity.onDestroy();
        super.onDestroy();
    }

    @Override
    public void onBackPressed() {
        if (mTargetActivity != null) {
            mTargetActivity.onBackPressed();
        } else {
            super.onBackPressed();
        }
    }

    @Override
    protected void onNewIntent(Intent intent) {
        if (mTargetActivity != null) {
            mTargetActivity.onNewIntent(intent);
        } else {
            super.onNewIntent(intent);
        }
    }
}

注意这里的继承一个Activity,之前我们说明了一个Activity启动流程,当启动这个Activity就相当于启动插件中Activity,那么插件中的方法,我们通过反射执行,那么资源怎么办,又不在宿主APK中,一定会找不到,所以我们要找到插件APK在打包APK之后资源文件在哪里?相信大家已经知道,网上很多

/**
     * @deprecated Use {@link #setApkAssets(ApkAssets[], boolean)}
     * @hide
     */
    @Deprecated
    public int addAssetPath(String path) {
        return addAssetPathInternal(path, false /*overlay*/, false /*appAsLib*/);
    }


/**
     * Changes the asset paths in this AssetManager. This replaces the {@link #addAssetPath(String)}
     * family of methods.
     *
     * @param apkAssets The new set of paths.
     * @param invalidateCaches Whether to invalidate any caches. This should almost always be true.
     *                         Set this to false if you are appending new resources
     *                         (not new configurations).
     * @hide
     */
    public void setApkAssets(@NonNull ApkAssets[] apkAssets, boolean invalidateCaches) {
        Preconditions.checkNotNull(apkAssets, "apkAssets");

        ApkAssets[] newApkAssets = new ApkAssets[sSystemApkAssets.length + apkAssets.length];

        // Copy the system assets first.
        System.arraycopy(sSystemApkAssets, 0, newApkAssets, 0, sSystemApkAssets.length);

        // Copy the given ApkAssets if they are not already in the system list.
        int newLength = sSystemApkAssets.length;
        for (ApkAssets apkAsset : apkAssets) {
            if (!sSystemApkAssetsSet.contains(apkAsset)) {
                newApkAssets[newLength++] = apkAsset;
            }
        }

        // Truncate if necessary.
        if (newLength != newApkAssets.length) {
            newApkAssets = Arrays.copyOf(newApkAssets, newLength);
        }

        synchronized (this) {
            ensureOpenLocked();
            mApkAssets = newApkAssets;
            nativeSetApkAssets(mObject, mApkAssets, invalidateCaches);
            if (invalidateCaches) {
                // Invalidate all caches.
                invalidateCachesLocked(-1);
            }
        }
    }

private static native void nativeSetApkAssets(long ptr, @NonNull ApkAssets[] apkAssets,
            boolean invalidateCaches);

这三个方法是有关联的,也表明一个APK的资源其实就是数组,ApkAssets[],那么宿主APK资源已经在启动的时候加入了这个ApkAssets[],我们就可以在获取插件APK的资源通过addAssetPath在添加到ApkAssets[]中,问题是不是解决了

那怎么获取插件APK资源呢?

String dexPath = Utils.getFileDirPath(context, "plugin_apk", pluginApkName);
        Log.e("dexPath", dexPath);

try {
            AssetManager assets = AssetManager.class.newInstance();
            Method addAssetPath = AssetManager.class.getMethod("addAssetPath", String.class);
            addAssetPath.invoke(assets, dexPath);
            pluginResources = new Resources(assets, context.getResources().getDisplayMetrics(), context.getResources().getConfiguration());
        } catch (Exception e) {
            e.printStackTrace();
        }

获取插件APK路径,使用反射找到AssetManager中addAssetPath,将插件资源添加到ApkAsset[]

所以上面ProxyActivity中getResources(),return的便是这个pluginResource,还有一种方式是,在ProxyActivity的attachBaseContext中super之前调用,两种选一种。

public void replaceResources(Context context){
        //替换ContextImpl中的Resources
        try {
            ReflectUtil.setField(context.getClass(), "mResources", context, pluginResources);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

到这里,我们上面说的第一种方法插件化就结束了,即在宿主APK中使用跳转一个新的代理Activity,而这个Activity没有任何布局资源,当跳转新的Activity时就会重新实例ContextImpl,也就会重新赋值其中的mResources。

但是第二种呢,如果你自己尝试也不难发现,也就是在宿主APKActivity使用第一种getResources(),就是报错,原因是宿主Activity中有布局资源,调用的插件Activity也有布局资源,当宿主APK启动第一个Activity时,宿主资源已经加载到ContextImpl中的mResources,也就是getResources()首先调用,但是这个时候我们还没有执行添加插件资源方法,即为null

 

处理方法是

@Override
    public Resources getResources() {
        return PluginManager.getInstance().getPluginResources() == null ? super.getResources() :
                PluginManager.getInstance().getPluginResources();
    }

好了,到此为止,我们已经说完了插件化开发,宿主APK和插件APK的资源问题!

四.接下来说手游SDK开发一些问题

1.执行SDK即插件中的带布局资源的方法时,难免遇到带有回调接口参数的方法

public void setUserLoginListener(OnUserLoginListener listener){
        mUserLogin.setUserLoginListener(listener);
    }

public interface OnUserLoginListener {
    public void onLoginComplete(boolean success, String loginInfo);
}

因为这里面使用反射,遇到接口回调参数,就得使用接口代理类

public void setUserLoginListener(OnUserLoginListener listener) {
        if (listener != null) {
            userLoginListener = listener;
        }

        String methodName = "setUserLoginListener";
        String interName = "com.nj9you.sdk.listener.OnUserLoginListener";
        invokeMethod(methodName, interName);
    }
/**
     * 执行执行含有接口回调方法
     */
    private Object invokeMethod(String methodName, String interfaceName) {
        try {
            //加载类名
            Class clazz = getClassLoader().loadClass(CLASS_NAME);
            //执行getInstance方法获取到单例对象
            Method getInstance = clazz.getMethod("getInstance");
            Object instance = getInstance.invoke(clazz);

            //加载接口名
            Class interClass = getClassLoader().loadClass(interfaceName);
            //给插件APK中接口方法添加代理
            Object interObj = Proxy.newProxyInstance(getClassLoader(), new Class[]{interClass}, new CallbackMethodInterceptor());

            Class[] classArray = {interClass};
            Object[] objArray = {interObj};

            //执行对应方法
            Method method = clazz.getMethod(methodName, classArray);
            return method.invoke(instance, objArray);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
//接口代理
    private static class CallbackMethodInterceptor implements InvocationHandler {

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

            String methodName = method.getName();
            Log.e(TAG, "method:" + methodName);

            if (methodName.equals("onLoginComplete")) {
                boolean success = (Boolean) args[0];
                String loginInfo = (String) args[1];
                userLoginListener.onLoginResult(success, loginInfo);
            }

            return proxy;
        }
    }

同样需要在宿主这里有一个一模一样的回调接口,因为接入方肯定会写这个接口的实现方法。

2.那么遇到有实体类参数呢?

public void pay(Activity activity, PayParams params, OnPayCallback callback) {
        if (activity == null) return;

        if (callback != null) {
            payCallback = callback;
        }

        String methodName = "pay";
        String interName = "com.nj9you.sdk.listener.OnPayCallback";
        String entityName = "com.nj9you.sdk.params.PayParams";

        Class[] entityClasses = {String.class, String.class, String.class, String.class, String.class, String.class, String.class};
        Object[] entityValues = {params.getServer(), params.getProductName(), params.getExtension(), params.getProductPrice(), params.getProductOrderId(), params.getProductDesc(), params.getProductId()};

        invokeMethod(activity, methodName, interName, entityName, entityClasses, entityValues);
    }
/**
     * 执行含有普通参数,实体类参数,回调接口参数的方法
     */
    private Object invokeMethod(Activity activity, String methodName, String interfaceName, String entityName, Class[] entityClasses, Object[] entityValues) {
        try {
            //加载类名
            Class clazz = getClassLoader().loadClass(CLASS_NAME);
            //执行getInstance方法获取到单例对象
            Method getInstance = clazz.getMethod("getInstance");
            Object instance = getInstance.invoke(clazz);

            //加载接口名
            Class interClass = getClassLoader().loadClass(interfaceName);
            //给插件APK中接口方法添加代理
            Object interObj = Proxy.newProxyInstance(getClassLoader(), new Class[]{interClass}, new CallbackMethodInterceptor());

            Class[] classArray = {Activity.class, interClass};
            Object[] objArray = {activity, interObj};

            //如果方法中含有实体类参数
            if (entityName != null) {
                Class entityClass = getClassLoader().loadClass(entityName);
                // 获取构造方法,没有参数就是无参构造,如果要获取有参构造,就用clazz.getConstructor(String.class, Integer.class)
                Constructor constructor = entityClass.getConstructor(entityClasses);
                //实例化一个对象
                Object entityObj = constructor.newInstance(entityValues);

                classArray = new Class[]{Activity.class, entityClass, interClass};
                objArray = new Object[]{activity, entityObj, interObj};
            }

            //执行对应方法
            Method method = clazz.getMethod(methodName, classArray);
            return method.invoke(instance, objArray);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

同样需要在宿主这里有一个一模一样的实体类,因为接入方肯定会写使用这个实体类get,set。

3.那么遇到插件中有自定义View呢?

LayoutInflater inflater = LayoutInflater.from(context);
        // 从布局文件获取浮动窗口视图
        View rootFloatView = inflater.inflate(R.layout.jy_widget_floatview, null);

这里记住,这个自定义布局中的子布局,所有在布局设置backgroud,drawable,src等,需要插件资源的,都需要在插件代码中使用对应控件的方法set

 mIvFloatLoader = (ImageView) rootFloatView.findViewById(R.id.jy_floatview_icon_notify);
        mIvFloatLoader.setImageResource(R.drawable.jy_floatview_anim_background);

        mLlFloatMenu = (LinearLayout) rootFloatView.findViewById(R.id.jy_floatview_menu);
        mLlFloatMenu.setBackgroundResource(R.drawable.jy_floatview_menu_bg);

这是因为,这里我们传入的context是宿主的,不是插件中的context,当然获取资源也是通过宿主Activity中getResources()获取,但是通过LayoutInflater加载的布局中的控件最后是通过反射获取的,而反射使用的classloader是插件的,不是我们传入宿主的classloader(因为我们就没有传),这就使得控件中通过布局设置资源的时候找不到,因为是插件本身,但这是个没有加载的APK。

4.那么遇到插件有Service呢?

之前也提过,但是上面只有Activity,那我们说一下遇到Service怎么处理

首先中间依赖

package com.leo.plugin;

import android.annotation.SuppressLint;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.res.Resources;
import android.os.IBinder;


public class PluginService extends Service implements PluginSInterface {

    /**
     * Service context -> ProxyService
     */
    protected Service hostService;

    @Override
    public void attach(Service proxyService) {
        hostService = proxyService;
    }

    @SuppressLint("MissingSuperCall")
    @Override
    public void onCreate() {
    }

    @SuppressLint("MissingSuperCall")
    @Override
    public void onDestroy() {

    }

    //子类实现直接return,不执行这里的方法体
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    //子类实现直接return,不执行这里的方法体
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        return super.onStartCommand(intent, flags, startId);
    }

    //--------------------------凡事用到上下文的地方都需要重写,需要重写的方法-----------------------------//

    @Override
    public void sendBroadcast(Intent intent) {
        if (hostService != null) {
            hostService.sendBroadcast(intent);
        } else {
            super.sendBroadcast(intent);
        }
    }

    @Override
    public ApplicationInfo getApplicationInfo() {
        return hostService != null ? hostService.getApplicationInfo() : super.getApplicationInfo();
    }

    @Override
    public ClassLoader getClassLoader() {
        return hostService != null ? hostService.getClassLoader() : super.getClassLoader();
    }

    @Override
    public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
        return hostService != null ? hostService.registerReceiver(receiver, filter) : super.registerReceiver(receiver, filter);
    }

    //--------------------------凡事用到上下文的地方都需要重写,需要重写的方法-----------------------------//
}
package com.leo.plugin;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;

public interface PluginSInterface {

    /**
     * 上下文的传递 通过上下文来启动Service
     *
     * @param proxyService
     */
    void attach(Service proxyService);

    /**
     * 绑定服务时才会调用
     * 必须要实现的方法
     * @param intent
     * @return
     */
    IBinder onBind(Intent intent);

    /**
     * 首次创建服务时,系统将调用此方法来执行一次性设置程序(在调用 onStartCommand() 或 onBind() 之前)。
     * 如果服务已在运行,则不会调用此方法。该方法只被调用一次
     */
    void onCreate();

    /**
     * 每次通过startService()方法启动Service时都会被回调。
     * @param intent
     * @param flags
     * @param startId
     * @return
     */
    int onStartCommand(Intent intent, int flags, int startId);

    /**
     * 服务销毁时的回调
     */
    void onDestroy();
}

再是,插件

package com.nj9you.sdk.service;

import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;

import com.leo.plugin.PluginService;
import com.nj9you.sdk.framework.UserModule;
import com.nj9you.sdk.widget.FloatView;

/**
 * Desction:Float view service
 * Author:lutianjiao
 */
public class FloatViewService extends PluginService {

    private FloatView mFloatView;

    @Override
    public IBinder onBind(Intent intent) {
        return new FloatViewServiceBinder();
    }

    @Override
    public void onCreate() {
        super.onCreate();
        mFloatView = new FloatView(hostService);
    }

   
    public void showFloat() {
        if (mFloatView != null) {
            mFloatView.show();
        }
    }

    public void hideFloat() {
        if (mFloatView != null) {
            mFloatView.hide();
        }
    }

    public void destroyFloat() {
        if (mFloatView != null) {
            mFloatView.destroy();
        }
        mFloatView = null;
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        destroyFloat();
    }

    public class FloatViewServiceBinder extends Binder {
        public FloatViewService getService() {
            return FloatViewService.this;
        }
    }

}

注意这里的hostService,就是宿主APK那个代理类的对象,插件中所以使用this的地方都要换成hostService

最后,宿主

package com.moyoisdk.proxy;

import android.app.Service;
import android.content.Intent;
import android.content.res.AssetManager;
import android.content.res.Resources;
import android.os.IBinder;

import com.leo.plugin.PluginSInterface;

public class ProxyService extends Service {

    /**
     * 目标插件apk的service
     */
    PluginSInterface mTargetService;

    @Override
    public IBinder onBind(Intent intent) {
        if (mTargetService != null)
            return mTargetService.onBind(intent);
        return null;
    }

    @Override
    public void onCreate() {
        super.onCreate();

        try {
            String className = "com.nj9you.sdk.service.FloatViewService";
            //加载该Service的字节码对象
            Class serviceClass = getClassLoader().loadClass(className);
            //创建该Service的实例
            Object newInstance = serviceClass.newInstance();
            //程序健壮性检查
            if (newInstance instanceof PluginSInterface) {
                mTargetService = (PluginSInterface) newInstance;
                mTargetService.attach(this);
                mTargetService.onCreate();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        if (mTargetService != null) {
            mTargetService.onStartCommand(intent, flags, startId);
        }
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public ClassLoader getClassLoader() {
        return PluginManager.getInstance().getPluginDexClassLoader();
    }

    @Override
    public AssetManager getAssets() {
        return PluginManager.getInstance().getPluginAssetManager() == null ? super.getAssets() :
                PluginManager.getInstance().getPluginAssetManager();
    }

    @Override
    public Resources getResources() {
        return PluginManager.getInstance().getPluginResources() == null ? super.getResources() :
                PluginManager.getInstance().getPluginResources();
    }

    @Override
    public void onDestroy() {
        if (mTargetService != null)
            mTargetService.onDestroy();
        super.onDestroy();
    }

}


那么通过在Activity中使用startService或者bindService就是使用代理ProxyService,记得要在Actitity中重写startService和bindService并指向ProxyService,最后在宿主SDKAndroidManifest中注册。

5.对于微信登录和支付回调,WXEntryActivity和WXPayEntryActivity,要先在插件SDK中实现对应WXEntryActivity和WXPayEntryActivity,在宿主中写两个Activity类似于ProxyActivity这样的代理Activity,最后在宿主SDKAndroidManifest中指向


        
        
        
        
        
        
        

6.对应QQ这样的使用onActivityResult接受回调的

public void onActivityResult(int requestCode, int resultCode, Intent data) {
        try {
            String qqLogin = "com.nj9you.sdk.loginassit.QQLogin";

            Class clazz = getClassLoader().loadClass(qqLogin);
            Method getInstance = clazz.getMethod("getInstance");
            Object instance = getInstance.invoke(clazz);
            //获取QQLogin中mUiListener属性
            Object listener = ReflectUtil.getField(clazz, "mUiListener", instance);

            if (requestCode == Constants.REQUEST_LOGIN || requestCode == Constants.REQUEST_APPBAR) {
                Tencent.onActivityResultData(requestCode, resultCode, data, (IUiListener) listener);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

public class QQLogin {

 private IUiListener mUiListener;
}

这样做是因为,如果使用反射传递Intent,会提示异常,找不到方法,Intent.class是一个类似我们的实体类对象实现接口Parcelable,就像我上面提到的反射的方法如果有实体类怎么处理,我们不能这样处理Intent啊,所以就想到这种处理方法,还有QQ需要的jar不管你插件中有没有,都需要在宿主SDK中重新加入对应jar,和在AndroidManifest中注册对应Activity,所以这里的Tencent也就这么来的。

好了,这就是我两个星期开发的手游SDK插件化遇到的问题和解决方案,希望对正在开发游戏SDK使用插件化的同学提供思路!

你可能感兴趣的:(插件化)