前些天发现了一个蛮有意思的人工智能学习网站,8个字形容一下"通俗易懂,风趣幽默",感觉非常有意思,忍不住分享一下给大家。
👉点击跳转到教程
前言:
上一篇文章
Android手写占位式插件化框架之Activity通信、Service通信和BroadcastReceiver通信
问题引出,在宿主app中获取插件包中静态注册的广播接收者StaticeReceiver,这个时候就需要apk解析原理系统源码分析,分析后进行再来操作。
apk解析原理系统源码分析笔记如下:
1.静态注册的广播是什么时候注册的?
手机开机的时候去,所有的APP再次进行安装一遍,安装后系统会去解析AndroidManifest.xml文件
解析静态广播后就会自动注册。
2.我们去分析安装
会在data/app/下放置目录 这是系统安装时做的事情
data/data/包名/ 应用所属目录
data/dalvik-cache 虚拟机去加载执行指令
3.该分析哪个目录?
data/app 放置目录
手机开机安装APP的时候,安装之后,马上就会全盘扫描 data/app 放置目录
解析出APP apk文件里面所有的组件,包括权限 系统会解析AndroidManifest.xml文件
Android系统会在安装过后,会马上扫描此目录data/app 放置目录 -->解析apk文件里面的配置信息AndroidManifest.xml
如果里面有静态配置的广播,就会要去注册广播。
分析系统源码,是如何进行解析apk
PackageManagerService
目标:看系统是如何去解析APK文件里面的组件信息的。
系统是在安装的时候才会去扫描APK
分析PackageManagerService 是由谁启动的
手机开机的时候
Linux内核驱动-->init进程-->zygote进程 孵化SystemServer进程-->
把Android所有的服务启动一下(包括PackageManagerService启动)
PackageManagerService启动
PMS如何去处理data/app/目录,如何解析APK
/** 存储已安装应用程序的目录 */
private static final File sAppInstallDir =
new File(Environment.getDataDirectory(), "app");
sAppInstallDir:/data/app/ 目录
sAppInstallDir,如何去解析APK文件的
scanDirTracedLI(sAppInstallDir, 0, scanFlags | SCAN_REQUIRE_KNOWN, 0,
packageParser, executorService);
这个方法就是扫描/data/app/目录下的apk文件-->解析AndroidManifest.xml里面的所有信息
扫描APK文件,解析APK,scanDirTracedLI-->
parsePackage:解析apk文件里面的所有信息
Package-->apk里面的AndroidManifest配置信息(所有的)
拿到了Package,就能拿到静态的广播信息
最终的目标:
<!--静态注册的广播-->
<receiver android:name=".StaticReceiver">
<intent-filter>
<action android:name="plugin.static_receiver" />
</intent-filter>
</receiver>
分析完apk解析原理后,然后通过反射技术进行获取对应的信息。
一、在宿主APP中的PluginManager类中,增加一个方法parserApkAction(),通过反射源码,来解析apk文件里的所有信息。
/**
* @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;
}
/**
* 反射系统源码,来解析apk文件里的所有信息
*/
public void parserApkAction() {
//1.执行此方法public Package parsePackage(File packageFile, int flags),就是为了拿到Package
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;
}
//实例化PackageParser对象
Class<?> packageParserClass = Class.forName("android.content.pm.PackageParser");
Object packageParser = packageParserClass.newInstance();
Method parsePackageMethod = packageParserClass.getMethod("parsePackage", File.class, int.class);
Object mPackage = parsePackageMethod.invoke(packageParser, file, PackageManager.GET_ACTIVITIES);//执行方法
//继续分析Package
//得到receivers
Field receiversFiled = mPackage.getClass().getDeclaredField("receivers");
Object receivers = receiversFiled.get(mPackage);
ArrayList arrayList = (ArrayList) receivers;
//此Activity不是组件的Activity,是PackageParser类的内部类
for (Object mActivity : arrayList) {//mActivity 对应 <receiver android:name=".StaticReceiver">
//获取<intent-filter> intents == 对应很多intent-filter
//通过反射拿到intents
Class<?> componentClass = Class.forName("android.content.pm.PackageParser$Component");
Field intentsField = componentClass.getDeclaredField("intents");
ArrayList<IntentFilter> intents = (ArrayList) intentsField.get(mActivity);
Class<?> packageUserStateClass = Class.forName("android.content.pm.PackageUserState");
Class<?> userHandleClass = Class.forName("android.os.UserHandle");
Method getCallingUserIdMethod = userHandleClass.getMethod("getCallingUserId");
int userId = (int) getCallingUserIdMethod.invoke(null);
//拿到android:name=".StaticReceiver"
//ActivityInfo.name --> android:name=".StaticReceiver"
//分析源码如何拿到ActivityInfo
//执行此方法generateActivityInfo,就能拿到ActivityInfo
//public static final ActivityInfo generateActivityInfo(Activity a, int flags,
// PackageUserState state, int userId)
Method generateActivityInfoMethod = packageParserClass.getMethod("generateActivityInfo", mActivity.getClass(), int.class,
packageUserStateClass, int.class);
//执行此方法,拿到ActivityInfo
ActivityInfo activityInfo = (ActivityInfo) generateActivityInfoMethod.invoke(null, mActivity, 0, packageUserStateClass.newInstance(), userId);
String receiverClassName = activityInfo.name;//com.example.plugin_package.StaticReceiver
BroadcastReceiver broadcastReceiver = (BroadcastReceiver) getClassLoader().loadClass(receiverClassName).newInstance();
for (IntentFilter intentFilter : intents) {
//注册广播
context.registerReceiver(broadcastReceiver, intentFilter);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
1.2 MainActivity中增加两个方法分别为:loadStaticReceiver,sendStaticReceiver
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);
}
/**
* 注册插件里面配置的静态广播
*
* @param view
*/
public void loadStaticReceiver(View view) {
PluginManager.getInstance(this).parserApkAction();
}
/**
* 发送给静态广播接收者
*
* @param view
*/
public void sendStaticReceiver(View view) {
Intent intent = new Intent();
intent.setAction("plugin.static_receiver");
sendBroadcast(intent);
}
}
二、在插件包声明类StaticReceiver
/**
* @Author: ly
* @Date: 2023/7/15
* @Description: 插件包中的静态广播
*/
public class StaticReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(context, "我是静态注册的广播,我收到广播了!", Toast.LENGTH_SHORT).show();
}
}
2.1 在AndroidManifest.xml文件中进行注册
<!--静态注册的广播-->
<receiver android:name=".StaticReceiver">
<intent-filter>
<action android:name="plugin.static_receiver" />
</intent-filter>
</receiver>
效果图如下: