Android手写占位式插件化框架之apk解析原理系统源码分析

前些天发现了一个蛮有意思的人工智能学习网站,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
如果里面有静态配置的广播,就会要去注册广播。分析系统源码,是如何进行解析apkPackageManagerService目标:看系统是如何去解析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 classprivate 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/filesLog.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/当前应用的包名/pDirFile fileDir = context.getDir("pDir", Context.MODE_PRIVATE);//fileDir.getAbsolutePath(): /data/user/0/com.example.pluginproject/app_pDirLog.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);//类类型Classmethod.invoke(assetManager, pluginPath);//插件包的路径,pluginPathResources r = context.getResources();//宿主的资源配置信息//特殊的resource,加载插件里面的资源的resourcethis.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),就是为了拿到Packagetry {//getExternalFilesDir:表示应用程序的私有目录File privateDir = context.getExternalFilesDir(null);//路径: /storage/emulated/0/Android/data/com.example.pluginproject/filesLog.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//得到receiversField 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//通过反射拿到intentsClass<?> 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);//执行此方法,拿到ActivityInfoActivityInfo activityInfo = (ActivityInfo) generateActivityInfoMethod.invoke(null, mActivity, 0, packageUserStateClass.newInstance(), userId);String receiverClassName = activityInfo.name;//com.example.plugin_package.StaticReceiverBroadcastReceiver 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 {@Overrideprotected 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);}}@Overrideprotected void onStart() {super.onStart();}@Overrideprotected void onResume() {super.onResume();}@Overrideprotected void onPause() {super.onPause();}@Overrideprotected void onStop() {super.onStop();}@Overrideprotected 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);//获取插件包里面的ActivityPackageManager packageManager = getPackageManager();PackageInfo packageInfo = packageManager.getPackageArchiveInfo(path, PackageManager.GET_ACTIVITIES);ActivityInfo activityInfo = packageInfo.activities[1];//占位 代理ActivityIntent 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 {@Overridepublic 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>

效果图如下:

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/28995.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

硬件设计 之 M.2接口常用知识

M.2接口 也被称为NGFF&#xff08;Next Generation Form Factor&#xff09;&#xff0c;是一种用于固态硬盘&#xff08;SSD&#xff09;和无线网络适配器等设备的高速接口标准。它是一种小型、高密度、高速度的接口&#xff0c;可用于替代传统的SATA接口和PCI Express Mini卡…

浪涌保护器行业应用防雷选型方案

当今社会中&#xff0c;电气设备的使用范围越来越广泛&#xff0c;也越来越普及&#xff0c;而与之相关的浪涌保护器就显得尤为重要。在这个领域&#xff0c;有一种高品质的浪涌保护器 —— 地凯防雷SPD浪涌保护器&#xff0c;它可以为各种设备提供强大的保护&#xff0c;并在各…

抖音小程序开发常见问题

抖音小程序 问题1 抖音小程序调试预留白屏 解决 &#xff0c;连接wifi出现无法打开&#xff0c;用手机流量可以正常访问 抖音小程序 web-view 上传后白屏 抖音小程序使用 webview 白屏&#xff0c;使用web-vew打开h5页面白屏 解决&#xff1a;配置web-view域名 服务器域名配…

【美团面试】软件测试面试题

一、设计登录界面测试用例 功能测试(Function test) 0. 什么都不输入&#xff0c;点击提交按钮&#xff0c;看提示信息。&#xff08;非空检查&#xff09; 1.输入正确的用户名和密码&#xff0c;点击提交按钮&#xff0c;验证是否能正确登录。&#xff08;正常输入&#xff0…

银行金融风险管理面试问题汇总(附答案)

最近有些学员在咨询换工作的事&#xff0c;包括一些金融上市公司的高管。我收集了一些金融风险管理面试问题相关资料&#xff0c;希望能帮助大家。记得收藏此文章&#xff0c;以防之后找不到文章。 风险经理识别和分析潜在的公司风险&#xff0c;并找到减少或避免风险的方法。…

SCT52A40,对标UCC27200、UCC27201半桥驱动IGBT/MOSFET栅极驱动器

特点&#xff1a; • 8-24V宽供电电压 • 驱动高侧和低侧N通道MOSFET • 4A峰值输出源电流和汇电流 • 升压电源电压范围可达120V • 集成阴极负载二极管 • TTL兼容输入&#xff0c;-10V输入 • 45ns传输延迟 • 1000pF负载下7ns上升和4.5ns下降时间 • 2ns延迟匹配时间 • 静…

简单工厂模式详解

文章目录 前言一、简单工厂模式定义二、举个例子三、简单工厂模式的缺点总结 前言 本篇我们了解一下简单工厂模式&#xff0c;它是设计模式的雏形&#xff0c;是学习设计模式的开端&#xff0c;我会结合案例说明它的设计思路。 一、简单工厂模式定义 简单工厂模式并不是GoF23…

督查督办系统通过哪些功能点提高效率

督查督办管理系统&#xff0c;主要实现对督办工作的分解、下派、办理及执行过程的监管&#xff0c;防止督办任务责任不明确、工作积压、工作耽误等问题&#xff0c;提高企业单位或政府部门的执行效率。那督查督办系统主要是通过哪些功能点来提升效率的呢&#xff1f;下面我们用…

论文解读: 2023-Lost in the Middle: How Language Models Use Long Contexts

大模型使用的关键在于Prompt&#xff0c;然而大模型存在幻觉现象&#xff0c;如何减少这种现象的发生成为迫切解决的问题。外部知识库LLM的方法可以缓解大模型幻觉&#xff0c;但是如何撰写Prompt才能发挥LLM的性能。下面介绍这篇论文说明上下文信息出现在Prompt什么位置使模型…

【iOS】编译与链接过程

前言 计算机语言分为&#xff1a;机器语言、汇编语言和高级语言。 高级语言又能分为&#xff1a;编辑语言、解释语言。 解释语言 解释语言编写的程序在每次运行时都需要通过解释器对程序进行动态解释和执行&#xff0c;即解释一条代码&#xff0c;执行一条代码。 优点&…

高薪Offer收割机之redis集群

单节点的redis并发能力是有限的&#xff0c;如果需要进一步提高redis的并发能力&#xff0c;就需要搭建集群。 Redis中的集群分为三种&#xff1a; 主从复制&#xff0c;哨兵模式&#xff0c;分片集群 先来看一下主从复制&#xff1a; 在主从集群中一个主节点可以有多个从节…

智安网络|移动安全的转型:零信任如何重新定义格局

数字化转型和远程/移动办公的常态化已经成为许多企业的现实。这一转变为企业带来了许多便利&#xff0c;但同时也引入了前所未有的风险&#xff0c;涉及员工的隐私、个人身份和特权访问凭证。尤其是在经济衰退和疫情的持续影响下&#xff0c;许多企业不得不在提高生产力的同时面…