欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2022-03-28 17:43:20
插件化开发实战,手游SDK使用插件化插件化的目的主要是为了APP减少体积,也可实现APP热更新,使用原理就是java反射,需要了解的知识就是APP启动流程,Resources加载流程,需要了解源码。插件化开发离不开宿主APK,插件APK和中间依赖层的相互调用,一般实现有两种,一种是新建代理Activity,在代理Activity中重写getResource和getAsset等资源方法,一种是在宿主APKActivity中直接调用插件APK资源,同样需要重写getResource和getAsset等资...

插件化开发实战,手游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 extends View> 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中指向

<!--微信相关开始-->
        <activity
            android:name="com.moyoisdk.wxapi.WXEntryActivity"
            android:exported="true"
            android:screenOrientation="behind"
            android:launchMode="singleTop"
            android:theme="@android:style/Theme.Translucent.NoTitleBar" />
        <activity
            android:name="com.moyoisdk.wxapi.WXPayEntryActivity"
            android:exported="true"
            android:screenOrientation="behind"
            android:launchMode="singleTop"
            android:theme="@android:style/Theme.Translucent.NoTitleBar" />
        <!--修改为游戏包名.wxapi.WXEntryActivity-->
        <activity-alias
            android:name="${applicationId}.wxapi.WXEntryActivity"
            android:exported="true"
            android:targetActivity="com.moyoisdk.wxapi.WXEntryActivity" />
        <!--修改为游戏包名.wxapi.WXPayEntryActivity-->
        <activity-alias
            android:name="${applicationId}.wxapi.WXPayEntryActivity"
            android:exported="true"
            android:targetActivity="com.moyoisdk.wxapi.WXPayEntryActivity" />
        <!--微信相关结束-->

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使用插件化的同学提供思路!

本文地址:https://blog.csdn.net/baidu_28842815/article/details/108117859

相关标签: 插件化