占位式插件化之加载静态广播

接着前几篇文章来:由于插件中的广播是在manifest中配置的,所以就不能使用上一篇中的方法来注册广播了,首先我们需要了解一下APK的解析原理

第一步我们要知道静态广播是什么时候注册的?

在手机开机的时候,系统谁扫描所有的app,在重新安装一遍,这也是为啥手机开机会这么慢,这时候系统会去解析AndroidManifest文件,解析的过程中遇到静态广播后就会自动注册

第二步我们来看一下应用的安装目录

主要有三个目录

/data/app

该文件夹存放着系统中安装的第三方应用的 apk 文件,当我们调试一个app的时候,可以看到控制台输出的内容,有一项是
uploading ……就是上传我们的apk到这个文件夹,上传成功之后才开始安装。Android 中应用的安装就是将应用的安装包原封不动地拷贝到 /data/app 目录下,每个应用安装包本质上就是一个 zip 格式的压缩文件。为了提升应用的启动效率,Android 会将解压出来的 dex 格式的应用代码文件解析提取后,缓存在 /data/dalvik-cache 目录下。

/data/data

该文件夹存放存储包私有数据,对于设备中每一个安装的 App,系统都会在内部存储空间的 data/data 目录下以应用包名为名字自动创建与之对应的文件夹。
用户卸载 App 时,系统自动删除 data/data 目录下对应包名的文件夹及其内容。

data/dalvik-cache

虚拟机去加载执行指令

通过上面的解释,可以知道,我们应该分析data/app这个目录,手机开机的时候就会扫描这个目录,来解析apk中的配置信息。

然后就开始看看系统是怎么来解析apk文件的,系统中的包的解析都是通过PackageManagerService这个类来解析的,当系统启动的时候,首先启动Linux内核驱动,然后启动init进程,然后启动zygote孵化进程,在然后启动SystemServer进程,然后启动PackageManagerService。

所以我们去PackageManagerService这个类中看看系统是怎么解析data/app中的 apk文件的

下面的源码是基于Android9.0的,每个版本的源码可能不一样

Ok 现在来到PackageManagerService这个类中

我们从文件目录入手,可以看到它有几个静态的成员变量

     /** Directory where installed applications are stored */
    private static final File sAppInstallDir =
            new File(Environment.getDataDirectory(), "app");
    /** Directory where installed application's 32-bit native libraries are copied. */
    private static final File sAppLib32InstallDir =
            new File(Environment.getDataDirectory(), "app-lib");
    /** Directory where code and non-resource assets of forward-locked applications are stored */
    private static final File sDrmAppPrivateInstallDir =
            new File(Environment.getDataDirectory(), "app-private"); 

其中第一个sAppInstallDir就是我们要找的安装目录data/app,所以从这里入手,看看系统是怎么解析apk文件的。

全局搜索sAppInstallDir就可以找到scanDirTracedLI这个方法,从名字也能看出,它就是扫描该目录。内部也会解析manifest文件,所以从这里开始分析,我们跟随扫描的方法一步一步的往下看。

  private void scanDirTracedLI(File scanDir, final int parseFlags, int scanFlags, long currentTime) {
        Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "scanDir [" + scanDir.getAbsolutePath() + "]");
        try {
            scanDirLI(scanDir, parseFlags, scanFlags, currentTime);
        } finally {
            Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
        }
    }
 private void scanDirLI(File scanDir, int parseFlags, int scanFlags, long currentTime) {
     ...
         for (File file : files) {
                final boolean isPackage = (isApkFile(file) || file.isDirectory())
                        && !PackageInstallerService.isStageName(file.getName());
                if (!isPackage) {
                    // Ignore entries which are not packages
                    continue;
                }
                //开启线程池来解析
                parallelPackageParser.submit(file, parseFlags);
                fileCount++;
            }
     ...
 }
 //mService是线程池
 public void submit(File scanFile, int parseFlags) {
        mService.submit(() -> {
            ParseResult pr = new ParseResult();
            Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parallel parsePackage [" + scanFile + "]");
            try {
                PackageParser pp = new PackageParser();
                pp.setSeparateProcesses(mSeparateProcesses);
                pp.setOnlyCoreApps(mOnlyCore);
                pp.setDisplayMetrics(mMetrics);
                pp.setCacheDir(mCacheDir);
                pp.setCallback(mPackageParserCallback);
                pr.scanFile = scanFile;
                //解析包
                pr.pkg = parsePackage(pp, scanFile, parseFlags);
            } catch (Throwable e) {
                pr.throwable = e;
            } finally {
                Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
            }
            try {
                mQueue.put(pr);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                mInterruptedInThread = Thread.currentThread().getName();
            }
        });
    }
  @VisibleForTesting
    protected PackageParser.Package parsePackage(PackageParser packageParser, File scanFile,
            int parseFlags) throws PackageParser.PackageParserException {
        return packageParser.parsePackage(scanFile, parseFlags, true /* useCaches */);
    }

这一路跟随,最后到了PackageParser这个类中的parsePackage方法。

//packageFile文件包的路径
 public Package parsePackage(File packageFile, int flags) throws PackageParserException {
        return parsePackage(packageFile, flags, false /* useCaches */);
    }
public Package parsePackage(File packageFile, int flags, boolean useCaches)
            throws PackageParserException {
        Package parsed = useCaches ? getCachedResult(packageFile, flags) : null;
        if (parsed != null) {
            return parsed;
        }

        long parseTime = LOG_PARSE_TIMINGS ? SystemClock.uptimeMillis() : 0;
        if (packageFile.isDirectory()) {
            parsed = parseClusterPackage(packageFile, flags);
        } else {
            parsed = parseMonolithicPackage(packageFile, flags);
        }
         ...
        return parsed;
    }

到这里我们就知道系统是通过parsePackage这个方法来解析apk文件的,那么我们是不是可以反射得到这个方法来解析我们自己的apk包呢?当然可以啦,最后我们只要拿到Package这个对象就行了,这个Package对象是parsePackage的内部类,它里面包含了AndroidManifest中的所有信息包含Permission,Activity,Service,Provider,广播等等。能拿到静态广播的信息就可以给它注册了。

所以现在接着前两篇文章来,在PluginManager类中添加一个解析apk获取广播并注册的方法

/**
     * 反射系统源码来解析自己的apk 注册广播
     */
    public void parseApkGetReceiver(){
        try {
            File file = new File(Environment.getExternalStorageDirectory()+File.separator+"p.apk");
            if(!file.exists()){
                Log.i(TAG,"插件包不存在");
            }
            //执行系统 PackageParser中的parsePackage方法来解析
            Class<?> packageParserClass = Class.forName("android.content.pm.PackageParser");
            Object packageParser = packageParserClass.newInstance();

            Method parsePackage = packageParserClass.getMethod("parsePackage",File.class,int.class);
            //mPackage就是PackageParser中的Package类
            Object mPackage = parsePackage.invoke(packageParser, file, PackageManager.GET_ACTIVITIES);
            //分析 mPackage拿到里面的广播的集合
            Field receiversField = mPackage.getClass().getDeclaredField("receivers");
            //本质是ArrayList集合  public final ArrayList<Activity> receivers = new ArrayList<Activity>(0);
            Object receivers = receiversField.get(mPackage);

            ArrayList list = (ArrayList) receivers;
            //集合内部的元素activity是PackageParse的内部类 是一个广播的封装类
            for (Object activity : list) {
                 //拿到intentfilter
                Class<?> componentClass = Class.forName("android.content.pm.PackageParser$Component");
                Field intentsFile = componentClass.getDeclaredField("intents");
                ArrayList<IntentFilter> intents = (ArrayList) intentsFile.get(activity);

                Class<?> packageUserState = Class.forName("android.content.pm.PackageUserState");
                Class<?> userHandle = Class.forName("android.os.UserHandle");
                int  userId = (int) userHandle.getDeclaredMethod("getCallingUserId").invoke(null);

                //拿到广播的全类名
                Method generateActivityInfoMethod = packageParserClass.
                        getDeclaredMethod("generateActivityInfo", activity.getClass(),
                                int.class,packageUserState,int.class);
                generateActivityInfoMethod.setAccessible(true);
                ActivityInfo activityInfo = (ActivityInfo) generateActivityInfoMethod.invoke(null, activity, 0, packageUserState.newInstance(),
                        userId);
                //插件包中的广播的全类名
                String receiverClassName = activityInfo.name;
                Class<?> receiverClass = getClassLoader().loadClass(receiverClassName);
                BroadcastReceiver  broadcastReceiver = (BroadcastReceiver) receiverClass.newInstance();
                for (IntentFilter intentFilter : intents) {
                    //注册广播
                    mContext.registerReceiver(broadcastReceiver,intentFilter);
                }
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }

写了这么多反射的代码,其实我们主要的目的只有两个
第一个拿到intentFilter,第二个拿到broadcastReceiver, 最后调用mContext.registerReceiver方法注册广播。其余代码都是为了找到这两个参数来服务的。

在插件包中定义一个广播并注册到manifest中

public class PluginStaticReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {

        Toast.makeText(context, "我是静态注册的广播,我收到广播啦", Toast.LENGTH_SHORT).show();

    }
}
 <receiver android:name=".PluginStaticReceiver">

            <intent-filter>

                <action android:name="plugin.package.PluginStaticReceiver" />

            </intent-filter>

 </receiver>

最后在MainActivity中加两个按钮,加载注册广播并发送广播

public void loadStaticReceiver(View view) {
        PluginManager.getInstance(this).parseApkGetReceiver();
    }

    public void sendStaticReceiver(View view) {
        Intent intent = new Intent();
        intent.setAction("plugin.package.PluginStaticReceiver");
        sendBroadcast(intent);
    }

效果:
在这里插入图片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章