Android PMS(PackageManagerService) 原理

什么是 PMS

PMS(PackageManagerService)是 Android 提供的包管理系统服务,它用来管理所有的包信息,包括应用安装、卸载、更新以及解析 AndroidManifest.xml。通常情况下我们不会把 PMS 单独的拆分出来讲解,因为 PMS 最主要的是提供给 AMS(ActivityManagerService)服务。

你是否有考虑过为什么我们手机开启启动时会很慢?这是因为 在手机启动时 PMS 会在这段时间处理 apk 解析,至少有 70% 的启动时间耗费在 PMS 解析上,所以这也是为什么手机开机启动比较慢的原因之一

从解析的角度上,可以理解为 PMS 保存了后续提供给 AMS 所需要的数据,它是具有保存应用数据的缓存

AndroidManifest.xml 的作用

当手机开机的时候,系统启动 PMS 后会去扫描两个目录,分别是存放用户安装的 apk 的目录 /data/app 以及系统安装的 apk 的目录 /system/app。

刚才有提到,PMS 是为了给 AMS 服务的,那 PMS 需要提供哪些数据呢?为什么需要 AndroidManifest.xml?

我们都知道 AndroidManifest.xml 定义了apk 中所有的四大组件、权限等等信息,它是一个定义文件。PMS 对 apk 的解析最主要的就是去扫描到 /data/app 和 /system/app 目录下的 apk 文件,找到 apk 包中的 AndroidManifest.xml,然后解析 AndroidManifest.xml 的信息保存到系统内存中,这样 AMS 在需要应用数据时,就能找到 PMS 快速的从内存中拿到相关信息

如果没有 AndroidManifest.xml,PMS 的解析就是要保存每个 apk 中所有的类文件信息,这个数据量是庞大的,而且解析也会很慢,手机启动速度更慢。

PMS 的 apk 解析流程

PMS 的启动过程

在 Android 系统所有的核心服务都会经过 SystemServer 启动,PMS 也不例外,SystemServer 会在手机开机时启动运行。关于 SystemServer 是如何启动的可以查看文章 Zygote

SystemServer.java

public static void main(String[] args) {
	new SystemServer().run();
}

private void run() {
	...
	try {
		...
		startBootstrapServices();
		startCoreServices();
		startOtherServices();
		...
	}
	...
}

private void startBootstrapServices() {
	...
	// 启动 AMS 
    mActivityManagerService = mSystemServiceManager.startService(
            ActivityManagerService.Lifecycle.class).getService();
    mActivityManagerService.setSystemServiceManager(mSystemServiceManager);
    mActivityManagerService.setInstaller(installer);
    ...
    // 启动 PMS
    mPackageManagerService = PackageManagerService.main(mSystemContext, installer,
            mFactoryTestMode != FactoryTest.FACTORY_TEST_OFF, mOnlyCore);
    mFirstBoot = mPackageManagerService.isFirstBoot();
    mPackageManager = mSystemContext.getPackageManager();   
    ... 
}

PackageManagerService.java

public static PackageManagerService main(Context context, Installer installer,
        boolean factoryTest, boolean onlyCore) {
    // Self-check for initial settings.
    PackageManagerServiceCompilerMapping.checkProperties();

	// 创建自己的实例
    PackageManagerService m = new PackageManagerService(context, installer,
            factoryTest, onlyCore);
    m.enableSystemUserPackages();
    // 将 PMS 添加到 ServiceManager,AMS 找 PMS 拿数据时就是通过 ServiceManager 找到 PMS
    ServiceManager.addService("package", m); 
    final PackageManagerNative pmn = m.new PackageManagerNative();
    ServiceManager.addService("package_native", pmn);
    return m;
}

当 SystemServer 被 Zygote 启动调用了 main() 方法时,执行了 SystemServer 的 run() 方法启动一些核心服务,例如先启动了 AMS 后再启动了 PMS,将 AMS 和 PMS 添加到 ServiceManager,由 ServiceManager 管理这些服务。

ServiceManager 只提供了 addService() 和 getService() 方法,当 app 进程需要获取到对应的系统服务,都会通过 ServiceManager 拿到相应服务的 Binder 代理,使用 Binder 通信获取数据:

例如在 ApplicationActivity 等地方调用 getPackageManager() 时:

ContextWrapper.java

@Override
public PackageManager getPackageManager() {
	// mBase 是 ContextImpl
    return mBase.getPackageManager();
}

ContextImpl.java

@Override
public PackageManager getPackageManager() {
    if (mPackageManager != null) {
        return mPackageManager;
    }

    IPackageManager pm = ActivityThread.getPackageManager();
    if (pm != null) {
        // Doesn't matter if we make more than one instance.
        return (mPackageManager = new ApplicationPackageManager(this, pm));
    }

    return null;
}

ActivityThread.java

public static IPackageManager getPackageManager() {
    if (sPackageManager != null) {
        //Slog.v("PackageManager", "returning cur default = " + sPackageManager);
        return sPackageManager;
    }
    // 通过 ServiceManager 拿到 PMS
    IBinder b = ServiceManager.getService("package");
    //Slog.v("PackageManager", "default service binder = " + b);
    sPackageManager = IPackageManager.Stub.asInterface(b); // binder 通信
    //Slog.v("PackageManager", "default service = " + sPackageManager);
    return sPackageManager;
}

PMS 解析 apk 流程

PMS 的处理流程简单理解就是手机开机时会去扫描两个目录 /data/app 和 /system/app,去解析这两个目录的 apk 文件的 AndroidManifest.xml 生成应用的摘要信息保存为 Java Bean 到内存

PackageManagerService.java

// data/app 目录
private static final File sAppInstallDir =
        new File(Environment.getDataDirectory(), "app");

public PackageManagerService(Context context, Installer installer,
        boolean factoryTest, boolean onlyCore) {
	...
	// /system/app 目录
	final File systemAppDir = new File(Environment.getRootDirectory(), "app");
	// 扫描 /system/app 目录下的 apk 文件
    scanDirTracedLI(systemAppDir,
            mDefParseFlags
            | PackageParser.PARSE_IS_SYSTEM_DIR,
            scanFlags
            | SCAN_AS_SYSTEM,
            0);	
	...
	// 扫描 /data/app 目录下的 apk 文件
	scanDirTracedLI(sAppInstallDir, 0, scanFlags | SCAN_REQUIRE_KNOWN, 0);
	...
}

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) {
	final File[] files = scanDir.listFiles();
	...
    try (ParallelPackageParser parallelPackageParser = new ParallelPackageParser(
             mSeparateProcesses, mOnlyCore, mMetrics, mCacheDir,
             mParallelPackageParserCallback)) {
         // Submit files for parsing in parallel
         int fileCount = 0;
         for (File file : files) {
         	 // 判断是否是 .apk 后缀的文件
             final boolean isPackage = (isApkFile(file) || file.isDirectory())
                     && !PackageInstallerService.isStageName(file.getName());
             if (!isPackage) {
                 // Ignore entries which are not packages
                 continue;
             }
             // 添加到子线程交给 PackageParser 解析 apk 文件
             parallelPackageParser.submit(file, parseFlags);
             fileCount++;
         }
         ...
     }
}

ParallelPackageParser.java

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; // 传入待解析的 apk 文件
            // 交给 packageParser 解析 apk
            pr.pkg = parsePackage(pp, scanFile, parseFlags);
        } catch (Throwable e) {
            pr.throwable = e;
        } finally {
            Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
        }
        ...
    });
}

protected PackageParser.Package parsePackage(PackageParser packageParser, File scanFile,
        int parseFlags) throws PackageParser.PackageParserException {
    return packageParser.parsePackage(scanFile, parseFlags, true /* useCaches */);
}

PackageParser.java

public static final String APK_FILE_EXTENSION = ".apk";

public static final boolean isApkFile(File file) {
	return isApkPath(file.getName());
}

public static boolean isApkPath(String path) {
	return path.endsWith(APK_FILE_EXTENSION);
}

从源码可以看到,PMS 其实就是去扫描 /data/app/ 和 /system/app/ 两个目录下的 apk,判断目录下的文件是否是 apk 也只是简单的判断文件后缀是否是 .apk。然后通过 PackageParser 开始解析 apk。

需要注意的是,在不同的系统源码版本解析的方式也不相同,在 6.0、7.0、8.0 版本启动解析的方式还是直接解析的,但在 10.0 版本开始使用线程池放到子线程去解析,加快了手机启动速度。

PackageParser 类源码解析

根据上面的分析,apk 的解析最终是交给 PackageParser,继续查看是如何解析的:

PackageParser.java

public Package parsePackage(File packageFile, int flags, boolean useCaches)
        throws PackageParserException {
     // 如果有缓存,直接返回解析后的信息
     Package parsed = useCaches ? getCachedResult(packageFile, flags) : null;
     if (parsed != null) {
         return parsed;
     }        
     ...
	 // apk 文件不是目录,所以会走的 parseMonolithicPackage()
     if (packageFile.isDirectory()) {
         parsed = parseClusterPackage(packageFile, flags);
     } else {
         parsed = parseMonolithicPackage(packageFile, flags);
     }
     ...
}

public Package parseMonolithicPackage(File apkFile, int flags) throws PackageParserException {
	...
    final SplitAssetLoader assetLoader = new DefaultSplitAssetLoader(lite, flags);
    try {
    	// 解析 apk
        final Package pkg = parseBaseApk(apkFile, assetLoader.getBaseAssetManager(), flags);
        pkg.setCodePath(apkFile.getCanonicalPath());
        pkg.setUse32bitAbi(lite.use32bitAbi);
        return pkg;
    } catch (IOException e) {
        throw new PackageParserException(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,
                "Failed to get path: " + apkFile, e);
    } finally {
        IoUtils.closeQuietly(assetLoader);
    }
}

private Package parseBaseApk(File apkFile, AssetManager assets, int flags)
        throws PackageParserException {
    final String apkPath = apkFile.getAbsolutePath();    
	...
	// 开始 dom 解析 AndroidManifest.xml
	XmlResourceParser parser = null;
    try {
        final int cookie = assets.findCookieForPath(apkPath);
        ...
        // ANDROID_MANIFEST_FILENAME 就是 AndroidManifest.xml
        parser = assets.openXmlResourceParser(cookie, ANDROID_MANIFEST_FILENAME);
        ...
        final Package pkg = parseBaseApk(apkPath, res, parser, flags, outError);
        ...
        return pkg;

    } catch (PackageParserException e) {
        throw e;
    } catch (Exception e) {
        throw new PackageParserException(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,
                "Failed to read manifest from " + apkPath, e);
    } finally {
        IoUtils.closeQuietly(parser);
    }
}

private Package parseBaseApk(String apkPath, Resources res, XmlResourceParser parser, int flags,
        String[] outError) throws XmlPullParserException, IOException {
     final String splitName;
     final String pkgName;

     try {
         Pair<String, String> packageSplit = parsePackageSplitNames(parser, parser);
         pkgName = packageSplit.first; // 拿到包名
         splitName = packageSplit.second;

         ...
     } 
     ...
     // 后续的流程就是将 xml 解析的信息如权限、四大组件等信息存到 Package
     final Package pkg = new Package(pkgName);
	 ...
     return parseBaseApkCommon(pkg, null, res, parser, flags, outError);
}

public final static class Package implements Parcelable {
	// 包名
	public String packageName;
	...
	// 申请的权限
    public final ArrayList<Permission> permissions = new ArrayList<Permission>(0);
    public final ArrayList<PermissionGroup> permissionGroups = new ArrayList<PermissionGroup>(0);
    // 四大组件
    public final ArrayList<Activity> activities = new ArrayList<Activity>(0);
    public final ArrayList<Activity> receivers = new ArrayList<Activity>(0);
    public final ArrayList<Provider> providers = new ArrayList<Provider>(0);
    public final ArrayList<Service> services = new ArrayList<Service>(0);
    ...
}

上面的源码其实很好理解,就是根据传过来的 apk 文件路径先拿到 AndroidManifest.xml,然后开始进行 dom 解析 xml 文件,将不同的标签数据信息存放在 Package 类的不同字段,例如 权限信息、四大组件信息等,将它们都解析好存放到内存中,方便后续 AMS 找到 PMS 拿数据。

在 9.0 版本开始解析结果默认会开启缓存,如果有缓存则直接返回解析后的结果信息,否则就解析每个 apk 文件的 AndroidManifest.xml:

ParallelPackageParser.java

protected PackageParser.Package parsePackage(PackageParser packageParser, File scanFile,
        int parseFlags) throws PackageParser.PackageParserException {
    // 开启缓存
    return packageParser.parsePackage(scanFile, parseFlags, true /* useCaches */);
}

PackageParser.java

public Package parsePackage(File packageFile, int flags, boolean useCaches)
        throws PackageParserException {
    // 如果有缓存,直接返回
    Package parsed = useCaches ? getCachedResult(packageFile, flags) : null;
    if (parsed != null) {
        return parsed;
    }
    ...
}

以上就是 PMS 的 apk 解析流程,简单说就是提前将 AMS 要用的数据信息先解析存到内存,方便能快速定位到 Activity 等信息。

当我们在应用商店下载安装应用或使用 adb install 时也是走的上述的解析过程。

小结

再简单总结下 PMS 的 apk 解析流程:

  • 手机系统启动,Zygote 启动 SystemServer,SystemServer 启动 AMS、PMS,并注册到 ServiceManager

  • PMS 扫描 /data/app/ 和 /system/app/ 目录下的所有 apk 文件,获取每个 apk 文件的 AndroidManifest.xml 文件,并进行 dom 解析

  • 解析 AndroidManifest.xml 将权限、四大组件等数据信息转换为 Java Bean 记录到内存中

  • 当 AMS 需要获取 apk 数据信息时,通过 ServiceManager 获取到 PMS 的 Binder 代理通过 Binder 通信获取

在这里插入图片描述

知道 PMS 解析过程有什么作用?

了解了 PMS 解析 apk 的流程,我们可以根据原理 hook 实现动态装载的功能,使用 PackageParser 将网络下载的一个 apk 文件自己手动解析,然后通过反射添加到 PMS 的内存,实现动态装载功能。

下面的 demo 实现了一个简单的动态加载功能,将一个外部 apk 文件的广播添加到 PMS 的缓存中。

首先准备需要动态添加的广播,该广播放在外部 apk 文件 hook-debug.apk:

public class HookReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        Log.i("HookReceiver", "hook receiver receive message");
        // 给宿主广播发消息
        Intent sendIntent = new Intent();
        sendIntent.setAction("com.example.demo.main");
        context.sendBroadcast(sendIntent);
    }
}

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.hook">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.Demo" >
        <!-- 清单文件也需要添加广播注册,PackageParser 动态加载时需要使用 -->
        <receiver android:name=".HookReceiver"
            android:exported="true">
            <intent-filter>
                <action android:name="com.example.demo.hook" />
            </intent-filter>
        </receiver>
    </application>
</manifest>

将外部 apk 打包出来后,为了方便演示,demo 是将 apk 导入到 cache 目录。接下来是动态解析:

public class HookPackageParser {

    public void parse(Context context, File apkFile) throws Exception {
        Class<?> packageParserClazz = Class.forName("android.content.pm.PackageParser");
        Method parsePackageMethod = packageParserClazz.getDeclaredMethod("parsePackage", File.class, int.class);
        parsePackageMethod.setAccessible(true);
        Object packageParserObj = packageParserClazz.newInstance();

        // 调用 PackageParser.parsePackage() 获取到解析后的 Package
        Object packageObj = parsePackageMethod.invoke(packageParserObj, apkFile, PackageManager.GET_RECEIVERS);

        // 获取 receivers 成员变量
        Field receiversField = packageObj.getClass().getDeclaredField("receivers");
        List receivers = (List) receiversField.get(packageObj);

        DexClassLoader dexClassLoader = new DexClassLoader(
                apkFile.getAbsolutePath(),
                context.getDir("plugin", Context.MODE_PRIVATE).getAbsolutePath(),
                null,
                context.getClassLoader());

        Class<?> componentClazz = Class.forName("android.content.pm.PackageParser$Component");
        Field intentsField = componentClazz.getDeclaredField("intents");
        for (Object receiverObj : receivers) {
            String name = (String) receiverObj.getClass().getField("className").get(receiverObj);
            try {
                BroadcastReceiver hookReceiver = (BroadcastReceiver) dexClassLoader.loadClass(name).newInstance();
                List<? extends IntentFilter> filters = (List<? extends IntentFilter>) intentsField.get(receiverObj);
                for (IntentFilter filter : filters) {
                    context.registerReceiver(hookReceiver, filter);
                }
            } catch (Exception e) {
                // ignore
            }
        }
    }
}

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        checkPermission(this);

		// 注册一个用于接收 hook 广播发送的消息验证是否动态装载了外部 apk 的广播
        IntentFilter filter = new IntentFilter();
        filter.addAction("com.example.demo.main");
        registerReceiver(new MainReceiver(), filter);
    }

    private boolean checkPermission(Activity activity) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && activity.checkSelfPermission(
                Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
            activity.requestPermissions(new String[]{
                    Manifest.permission.READ_EXTERNAL_STORAGE,
                    Manifest.permission.WRITE_EXTERNAL_STORAGE
            }, 1);

        }
        return false;
    }

    // 先 hook 外部 apk 的 HookReceiver
    public void hookReceiver(View view) {
        HookPackageParser packageParser = new HookPackageParser();
        File directory = getCacheDir();
        String path = directory.getAbsolutePath() + "/hook-debug.apk";
        File file = new File(path);
        if (!file.exists()) {
            throw new RuntimeException("hook apk no exist");
        }

        try {
            packageParser.parse(this, file);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // hook 后尝试发送广播看是否生效
    public void sendBroadcast(View view) {
        Intent intent = new Intent();
        intent.setAction("com.example.demo.hook");
        sendBroadcast(intent);
    }

    private static class MainReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            Log.i("MainReceiver", "receive hook receiver message");
        }
    }
}

PackageParser 需要通过反射获取,再反射调用它的 parsePackage() 传入 apk 路径完成解析获取到 Package 对象,再反射 PMS 的 activities、providers、receivers、services 变量,将我们解析的数据添加进去,这样就实现了动态加载。

  • 5
    点赞
  • 45
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值