Android PMS(PackageManagerService) 原理

文章目录

  • 什么是 PMS
  • AndroidManifest.xml 的作用
  • PMS 的 apk 解析流程
    • PMS 的启动过程
    • PMS 解析 apk 流程
    • PackageParser 类源码解析
    • 小结
  • 知道 PMS 解析过程有什么作用?

什么是 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 通信获取

Android PMS(PackageManagerService) 原理_第1张图片

知道 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 变量,将我们解析的数据添加进去,这样就实现了动态加载。

你可能感兴趣的:(原理,android)