插件化开发实战,手游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 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