39

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

 4 years ago
source link: https://www.tuicool.com/articles/fuMvIrr
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

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

接着前几篇文章来:由于插件中的广播是在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);
    }

效果:

mERRVzy.gif


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK