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

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

前言:

上一篇文章

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>

效果图如下:


目录
相关文章
|
7月前
|
Linux 测试技术 语音技术
【车载Android】模拟Android系统的高负载环境
本文介绍如何将Linux压力测试工具Stress移植到Android系统,用于模拟高负载环境下的CPU、内存、IO和磁盘压力,帮助开发者优化车载Android应用在多任务并发时的性能问题,提升系统稳定性与用户体验。
524 6
|
7月前
|
Java 数据库 Android开发
基于Android的电子记账本系统
本项目研究开发一款基于Java与Android平台的开源电子记账系统,采用SQLite数据库和Gradle工具,实现高效、安全、便捷的个人财务管理,顺应数字化转型趋势。
|
移动开发 安全 Java
Android历史版本与APK文件结构
通过以上内容,您可以全面了解Android的历史版本及其主要特性,同时掌握APK文件的结构和各部分的作用。这些知识对于理解Android应用的开发和发布过程非常重要,也有助于在实际开发中进行高效的应用管理和优化。希望这些内容对您的学习和工作有所帮助。
1521 83
|
安全 算法 小程序
【03】微信支付商户申请下户到配置完整流程-微信开放平台创建APP应用-填写上传基础资料-生成安卓证书-获取Apk签名-申请+配置完整流程-优雅草卓伊凡
【03】微信支付商户申请下户到配置完整流程-微信开放平台创建APP应用-填写上传基础资料-生成安卓证书-获取Apk签名-申请+配置完整流程-优雅草卓伊凡
857 28
【03】微信支付商户申请下户到配置完整流程-微信开放平台创建APP应用-填写上传基础资料-生成安卓证书-获取Apk签名-申请+配置完整流程-优雅草卓伊凡
|
前端开发 Java 编译器
当flutter react native 等混开框架-并且用vscode-idea等编译器无法打包apk,打包安卓不成功怎么办-直接用android studio如何打包安卓apk -重要-优雅草卓伊凡
当flutter react native 等混开框架-并且用vscode-idea等编译器无法打包apk,打包安卓不成功怎么办-直接用android studio如何打包安卓apk -重要-优雅草卓伊凡
448 36
当flutter react native 等混开框架-并且用vscode-idea等编译器无法打包apk,打包安卓不成功怎么办-直接用android studio如何打包安卓apk -重要-优雅草卓伊凡
|
12月前
|
安全 搜索推荐 Android开发
Android系统SELinux安全机制详解
如此看来,SELinux对于大家来说,就像那位不眠不休,严阵以待的港口管理员,守护我们安卓系统的平安,维护这片海港的和谐生态。SELinux就这样,默默无闻,却卫士如山,给予Android系统一份厚重的安全保障。
384 18
|
XML JavaScript Android开发
【Android】网络技术知识总结之WebView,HttpURLConnection,OKHttp,XML的pull解析方式
本文总结了Android中几种常用的网络技术,包括WebView、HttpURLConnection、OKHttp和XML的Pull解析方式。每种技术都有其独特的特点和适用场景。理解并熟练运用这些技术,可以帮助开发者构建高效、可靠的网络应用程序。通过示例代码和详细解释,本文为开发者提供了实用的参考和指导。
482 15
|
监控 Shell Linux
Android调试终极指南:ADB安装+多设备连接+ANR日志抓取全流程解析,覆盖环境变量配置/多设备调试/ANR日志分析全流程,附Win/Mac/Linux三平台解决方案
ADB(Android Debug Bridge)是安卓开发中的重要工具,用于连接电脑与安卓设备,实现文件传输、应用管理、日志抓取等功能。本文介绍了 ADB 的基本概念、安装配置及常用命令。包括:1) 基本命令如 `adb version` 和 `adb devices`;2) 权限操作如 `adb root` 和 `adb shell`;3) APK 操作如安装、卸载应用;4) 文件传输如 `adb push` 和 `adb pull`;5) 日志记录如 `adb logcat`;6) 系统信息获取如屏幕截图和录屏。通过这些功能,用户可高效调试和管理安卓设备。
|
Android开发 开发者
Android Split APK介绍
【2月更文挑战第5天】
|
XML API Android开发
android S 上 安装apk出现android.os.FileUriExposedException
android S 上 安装apk出现android.os.FileUriExposedException
565 6

热门文章

最新文章

  • 1
    为什么微信发送的APP安装不了,.apk转化为.apk.1
    1180
  • 2
    【03】微信支付商户申请下户到配置完整流程-微信开放平台创建APP应用-填写上传基础资料-生成安卓证书-获取Apk签名-申请+配置完整流程-优雅草卓伊凡
    857
  • 3
    Android历史版本与APK文件结构
    1521
  • 4
    Cocos2d-x 游戏开发-打包apk被默认自带了很多不必要的权限导致apk被报毒,如何在Cocos 2d-x中强制去掉不必要的权限-优雅草卓伊凡
    291
  • 5
    当flutter react native 等混开框架-并且用vscode-idea等编译器无法打包apk,打包安卓不成功怎么办-直接用android studio如何打包安卓apk -重要-优雅草卓伊凡
    448
  • 6
    【03】完整flutter的APP打包流程-以apk设置图标-包名-签名-APP名-打包流程为例—-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈 章节内容【03】
    1350
  • 7
    【02】写一个注册页面以及配置打包选项打包安卓apk测试—开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
    500
  • 8
    apk安装包EXE,mis程序打包后报毒的正确处理方式-千万不要再人傻钱多被骗-真正的合法途径的处理方式才是正确的-apk安装包EXE,mis程序如何处理-优雅草央千澈
    470
  • 9
    使用Kivy创建“Hello World”应用并打包成APK
    2315
  • 10
    使用keytool查看Android APK签名
    2422
  • 推荐镜像

    更多