前些天发现了一个蛮有意思的人工智能学习网站,8个字形容一下"通俗易懂,风趣幽默",感觉非常有意思,忍不住分享一下给大家。
前言:
1、什么是插件化?
能运行的宿主APP去加载没有下载的APK文件,并使用APK文件里面的功能,这就叫插件化。
2、插件化的使用场景?
很多大厂APP内会有很多功能模块,但是包体积却很小,那么就用到了插件化技术,点击某个模块后,从服务器获取对应的APK文件,并使用其内部的功能。
实现后的效果图如下:
接下来手写实现占位式插件化框架之Activity之间的通信
根据上图首先定义一个项目叫PluginProject,之后再新建一个Android Library库名为:stander,然后再定义一个插件包名为:plugin_package
项目目录如下:
一、首先在stander库中,定义一个接口名为ActivityInterface,ServiceInterface,ReceiverInterface三个接口
1.1、ActivityInterface接口
bash
/**
* @Author: ly
* @Date: 2023/7/14
* @Description: 定义的Activity标准接口,需要什么方法可以再加
*/
public interface ActivityInterface {
/**
* 把宿主(app)的环境给插件
*
* @param appActivity 宿主的Activity
*/
void insertAppContext(Activity appActivity);
void onCreate(Bundle savedInstanceState);
void onStart();
void onResume();
void onPause();
void onStop();
void onDestroy();
}
1.2 ServiceInteface接口
bash
/**
* @Author: ly
* @Date: 2023/7/15
* @Description: 宿主与插件间进行Service通信,标准接口
*/
public interface ServiceInterface {
/**
* 把宿主(app)的环境给插件
*
* @param service 宿主的Service
*/
void insertAppContext(Service service);
void onCreate();
int onStartCommand(Intent intent, int flags, int startId);
void onDestroy();
}
1.3、ReceiverInterface接口
bash
/**
* @Author: ly
* @Date: 2023/7/15
* @Description: 宿主与插件间进行广播通信标准接口
*/
public interface ReceiverInterface {
void onReceive(Context context, Intent intent);
}
二、在宿主APP中,定义插件管理类PluginManager
bash
/**
* @Author: ly
* @Date: 2023/7/14
* @Description: 插件管理类,获取插件中的资源Resources和类加载器DexClassLoader
*/
public class PluginManager {
private static final String TAG = PluginManager.class.getSimpleName();
private static PluginManager pluginManager;
private Context context;
//Activity class
private DexClassLoader dexClassLoader;
private Resources resources;
private PluginManager(Context context) {
this.context = context;
}
public static PluginManager getInstance(Context context) {
if (pluginManager == null) {
synchronized (PluginManager.class) {
if (pluginManager == null) {
pluginManager = new PluginManager(context);
}
return pluginManager;
}
}
return pluginManager;
}
/**
* 加载插件(2.1 Activity class, 2.2 layout)
*/
public void loadPlugin() {
try {
//getExternalFilesDir:表示应用程序的私有目录
File privateDir = context.getExternalFilesDir(null);
//路径: /storage/emulated/0/Android/data/com.example.pluginproject/files
Log.i(TAG, "privateDir: " + privateDir.getAbsolutePath());
File file = new File(privateDir.getAbsolutePath() + File.separator + "p.apk");
if (!file.exists()) {
Log.d(TAG, "插件包,不存在");
return;
}
String pluginPath = file.getAbsolutePath();
//下面是加载插件里面的class
//dexClassLoader 需要一个缓存目录 /data/data/当前应用的包名/pDir
File fileDir = context.getDir("pDir", Context.MODE_PRIVATE);
//fileDir.getAbsolutePath(): /data/user/0/com.example.pluginproject/app_pDir
Log.d(TAG, "fileDir: " + fileDir.getAbsolutePath());
//pluginPath:插件文件的路径,表示插件APK文件的位置。
//fileDir.getAbsolutePath():表示应用程序的私有目录路径,作为DexClassLoader的第二个参数传递,用于指定Dex文件的输出目录。
//null:表示没有指定库(Native Library)的路径,如果插件中有依赖的库文件,可以传入库目录的路径。
//context.getClassLoader():获取应用程序的类加载器作为DexClassLoader的父类加载器。
dexClassLoader = new DexClassLoader(pluginPath, fileDir.getAbsolutePath(), null, context.getClassLoader());
//下面是加载插件里面的layout文件
//加载资源
AssetManager assetManager = AssetManager.class.newInstance();
//我们执行此方法,为了把插件包的路径添加进去
// public int addAssetPath(String path)
Method method = assetManager.getClass().getMethod("addAssetPath", String.class);//类类型Class
method.invoke(assetManager, pluginPath);//插件包的路径,pluginPath
Resources r = context.getResources();//宿主的资源配置信息
//特殊的resource,加载插件里面的资源的resource
this.resources = new Resources(assetManager, r.getDisplayMetrics(), r.getConfiguration());//参数二和参数三,配置信息
} catch (Exception e) {
e.printStackTrace();
}
}
public ClassLoader getClassLoader() {
return dexClassLoader;
}
public Resources getResources() {
return resources;
}
}
2.1然后在MainActivity定义两个按钮,分别为加载插件,和启动插件里面的Activity
bash
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED
|| ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE}, 0);
}
}
@Override
protected void onStart() {
super.onStart();
}
@Override
protected void onResume() {
super.onResume();
}
@Override
protected void onPause() {
super.onPause();
}
@Override
protected void onStop() {
super.onStop();
}
@Override
protected void onDestroy() {
super.onDestroy();
}
/**
* 加载插件
*
* @param view
*/
public void loadPlugin(View view) {
PluginManager.getInstance(this).loadPlugin();
}
/**
* 启动插件里面的Activity
*
* @param view
*/
public void startPluginActivity(View view) {
File privateDir = getExternalFilesDir(null);
File file = new File(privateDir.getAbsolutePath() + File.separator + "p.apk");
String path = file.getAbsolutePath();
File file1 = new File(path);
if (!file1.exists() || file1.isFile()) {
Log.i("TAG", "插件包路径无效");
}
Log.i("TAG", "path: " + path);
//获取插件包里面的Activity
PackageManager packageManager = getPackageManager();
PackageInfo packageInfo = packageManager.getPackageArchiveInfo(path, PackageManager.GET_ACTIVITIES);
ActivityInfo activityInfo = packageInfo.activities[1];
//占位 代理Activity
Intent intent = new Intent(this, ProxyActivity.class);
// intent.putExtra("className", "com.example.plugin_package.PluginActivity");
intent.putExtra("className", activityInfo.name);
startActivity(intent);
}
}
2.2 写代理类ProxyActivity,用代理类的上下文环境,实现插件包页面正常加载
bash
/**
* @Author: ly
* @Date: 2023/7/14
* @Description: 代理的Activity,代理/占位 插件里面的Activity
*/
public class ProxyActivity extends Activity {
private static final String TAG = "ProxyActivity";
@Override
public Resources getResources() {
return PluginManager.getInstance(this).getResources();
}
@Override
public ClassLoader getClassLoader() {
return PluginManager.getInstance(this).getClassLoader();
}
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//真正的加载,插件里面的Activity
String className = getIntent().getStringExtra("className");
Log.i(TAG, "className: " + className);
try {
Class<?> pluginActivityClass = getClassLoader().loadClass(className);
//实例化插件包里面的Activity
Constructor<?> constructor = pluginActivityClass.getConstructor(new Class[]{});
Object pluginActivity = constructor.newInstance(new Object[]{});
ActivityInterface activityInterface = (ActivityInterface) pluginActivity;
//注入
activityInterface.insertAppContext(this);
Bundle bundle = new Bundle();
bundle.putString("appName", "我是宿主传递过来的信息");
//执行插件里面的onCreate()方法
activityInterface.onCreate(bundle);
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void startActivity(Intent intent) {
String className = intent.getStringExtra("className");
Intent proxyIntent = new Intent(this, ProxyActivity.class);
proxyIntent.putExtra("className", className);//包名TestActivity
//要给TestActivity进栈
super.startActivity(proxyIntent);
}
@Override
public ComponentName startService(Intent service) {
String className = service.getStringExtra("className");
Intent intent = new Intent(this, ProxyService.class);
intent.putExtra("className", className);//ProxyService全类名
return super.startService(intent);
}
@Override
public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter intentFilter) {
//MyReceiver全类名
String pluginReceiverName = receiver.getClass().getName();
//在宿主app注册广播
return super.registerReceiver(new ProxyReceiver(pluginReceiverName), intentFilter);
}
@Override
public void sendBroadcast(Intent intent) {
super.sendBroadcast(intent);//发送广播到ProxyReceiver
}
}
2.3、ProxyService类
bash
/**
* @Author: ly
* @Date: 2023/7/15
* @Description: 代理Service类,代理/占位插件中的Service
*/
public class ProxyService extends Service {
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
super.onCreate();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
String className = intent.getStringExtra("className");
//com.example.plugin_package.TestService
try {
Class<?> testServiceClass = PluginManager.getInstance(this).getClassLoader().loadClass(className);
Object testService = testServiceClass.newInstance();
ServiceInterface serviceInterface = (ServiceInterface) testService;
serviceInterface.insertAppContext(this);
serviceInterface.onStartCommand(intent, flags, startId);
} catch (Exception e) {
e.printStackTrace();
}
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
super.onDestroy();
}
}
ProxyService需要在AndroidManifest.xml中注册
bash
<service android:name=".ProxyService" />
2.4、ProxyReceiver类
bash
/**
* @Author: ly
* @Date: 2023/7/15
* @Description: 能接收的广播接收者, 代理/占位,插件里面的BroadcastReceiver
*/
public class ProxyReceiver extends BroadcastReceiver {
/**
* 插件里面的MyReceiver全类名
*/
private String pluginReceiverName;
public ProxyReceiver(String pluginReceiverName) {
this.pluginReceiverName = pluginReceiverName;
}
@Override
public void onReceive(Context context, Intent intent) {
//加载插件里面的MyReceiver
try {
Class myReceiverClass = PluginManager.getInstance(context).getClassLoader().loadClass(pluginReceiverName);
//实例化Class
Object myReceiver = myReceiverClass.newInstance();
ReceiverInterface receiverInterface = (ReceiverInterface) myReceiver;
receiverInterface.onReceive(context, intent);
} catch (Exception e) {
e.printStackTrace();
}
}
}
三、插件包plugin_package中,首先实现BaseActivity类
bash
/**
* @Author: ly
* @Date: 2023/7/14
* @Description: 插件包中Activity基础类, 拿到宿主的上下文环境
*/
public class BaseActivity extends Activity implements ActivityInterface {
private static final String TAG = "BaseActivity";
/**
* 宿主的环境
*/
public Activity appActivity;
@Override
public void insertAppContext(Activity appActivity) {
this.appActivity = appActivity;
}
@SuppressLint("MissingSuperCall")
@Override
public void onCreate(Bundle savedInstanceState) {
String appName = savedInstanceState.getString("appName");
Log.i(TAG, "appName: " + appName);
}
@SuppressLint("MissingSuperCall")
@Override
public void onStart() {
}
@SuppressLint("MissingSuperCall")
@Override
public void onResume() {
}
@SuppressLint("MissingSuperCall")
@Override
public void onPause() {
}
@SuppressLint("MissingSuperCall")
@Override
public void onStop() {
}
@SuppressLint("MissingSuperCall")
@Override
public void onDestroy() {
}
public void setContentView(int resId) {
appActivity.setContentView(resId);
}
public View findViewById(int id) {
return appActivity.findViewById(id);
}
/**
* 启动插件包内的第二个Activity:TestActivity
*
* @param intent 意图数据
*/
public void startActivity(Intent intent) {
Intent newIntent = new Intent();
newIntent.putExtra("className", intent.getComponent().getClassName());
appActivity.startActivity(newIntent);
}
public ComponentName startService(Intent serviceIntent) {
Intent newIntent = new Intent();
//serviceIntent.getComponent().getClassName() 这里拿到的是TestService的全类名
newIntent.putExtra("className", serviceIntent.getComponent().getClassName());
return appActivity.startService(newIntent);
}
/**
* 注册广播
*
* @param receiver
* @param intentFilter
* @return
*/
public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter intentFilter) {
return appActivity.registerReceiver(receiver, intentFilter);
}
/**
* 发送广播
*
* @param intent
*/
public void sendBroadcast(Intent intent) {
appActivity.sendBroadcast(intent);
}
}
3.2 插件包中首页PluginActivity,代码如下
bash
/**
* 首先加载该页面PluginActivity
*/
public class PluginActivity extends BaseActivity {
private static final String TAG = "PluginActivity";
private static final String ACTION = "com.example.plugin_package.ACTION";
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
String appName = savedInstanceState.getString("appName");
Log.i(TAG, "appName: " + appName);
setContentView(R.layout.activity_plugin);
Toast.makeText(appActivity, "我是插件", Toast.LENGTH_SHORT).show();
//点击按钮跳转到TestActivity
findViewById(R.id.btn_start_activity).setOnClickListener(v -> {
startActivity(new Intent(appActivity, TestActivity.class));
});
//点击按钮跳转到TestService
findViewById(R.id.btn_start_service).setOnClickListener(v -> {
startService(new Intent(appActivity, TestService.class));
});
//插件内部注册插件的广播接收者
findViewById(R.id.btn_register_receiver).setOnClickListener(v -> {
IntentFilter filter = new IntentFilter();
filter.addAction(ACTION);
registerReceiver(new MyReceiver(), filter);
});
//插件内部发送插件的广播接收者
findViewById(R.id.btn_send_receiver).setOnClickListener(v -> {
Intent intent = new Intent();
intent.setAction(ACTION);
sendBroadcast(intent);
});
}
}
3.3 点击PluginActivity中的按钮,可以跳转到TestActivity,代码如下:
bash
public class TestActivity extends BaseActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
}
}
3.4、BaseService类
bash
/**
* @Author: ly
* @Date: 2023/7/15
* @Description: 基础Service继承标准库中ServiceInterface接口
*/
public class BaseService extends Service implements ServiceInterface {
private Service appService;
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void insertAppContext(Service appService) {
this.appService = appService;
}
@Override
public void onCreate() {
}
@SuppressLint("WrongConstant")
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return 0;
}
@Override
public void onDestroy() {
}
}
3.5、TestService类
bash
/**
* @Author: ly
* @Date: 2023/7/15
* @Description: 插件中的Service
*/
public class TestService extends BaseService {
private static final String TAG = "TestService";
@Override
public void onCreate() {
super.onCreate();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
//开启子线程
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
} finally {
Log.i(TAG, "插件里面的服务正在执行中!");
}
}
}
}).start();
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
super.onDestroy();
}
}
3.6、插件中的广播接收者MyReceiver
bash
/**
* @Author: ly
* @Date: 2023/7/15
* @Description: 插件中的广播接收者
*/
public class MyReceiver extends BroadcastReceiver implements ReceiverInterface {
@Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(context, "我是插件里面的广播接收者,我收到了广播!", Toast.LENGTH_SHORT).show();
}
}
一、这个是宿主APP启动插件Activity流程:
二、这个是插件中启动Activity的流程如下图:
三、下面是插件中启动Service的流程图如下:
四、插件中启动BroadcastReceiver,这是在插件中动态注册广播接收者,并在插件内部发送广播。
五、宿主app中获取插件包中静态注册的广播接收者StaticeReceiver,并在宿主app中发送静态广播请看这篇文章
Android手写占位式插件化框架之apk解析原理系统源码分析
六、编写代码后,将plugin_package包手动放到宿主app的私有目录下,便可以正常运行,在公司项目中会将插件包放到服务器用户使用某个功能模块的时候,会下载到本地。
七、具体问题思考
1、为什么在插件中不能使用this?
因为插件是没有在手机上安装的,是无法拥有组件环境的。
2、为什么要有代理的Activity?
由于插件中的Activity并不是一个能够运行的组件,所以需要代理的Activity去代替插件中的Activity(例如Activity进出栈)
3、这种插件化,在写插件开发的时候,有什么要注意的事项?
所有关于操作组件环境的地方,都必须使用宿主的环境。