mirror of
https://github.com/feicong/rom-course.git
synced 2025-08-29 18:54:56 +00:00
第六章完成
This commit is contained in:
parent
4dd4af3b0a
commit
d2527fec13
@ -765,19 +765,704 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
cn.mik.myservicedemo I/MainActivity: msg: hello mikrom service
|
cn.mik.myservicedemo I/MainActivity: msg: hello mikrom service
|
||||||
```
|
```
|
||||||
|
|
||||||
## 6.5 修改APP默认权限
|
## 6.5 APP权限
|
||||||
|
|
||||||
|
Android中的权限是指应用程序访问设备功能和用户数据所需的授权。在Android系统中,所有的应用程序都必须声明其需要的权限,以便在安装时就向用户展示,并且在运行时需要获取相应的授权才能使用。
|
||||||
|
|
||||||
|
这一节将介绍APP的权限,以及在源码中是如何加载`AndroidManifest.xml`文件获取到权限,并进行控制的,最后尝试在加载流程中进行修改,让APP默认具有一些权限,无需APP进行申请。
|
||||||
|
|
||||||
### 6.5.1 APP权限介绍
|
### 6.5.1 APP权限介绍
|
||||||
|
|
||||||
|
Android系统将权限分为普通权限和危险权限两类,其中危险权限需要用户明确授权才能使用,而普通权限则不需要。普通权限通常不涉及到用户隐私和设备安全问题,例如访问网络、读取手机状态等。而危险权限则可能会涉及到用户隐私和设备安全问题,例如读取联系人信息、访问摄像头等。
|
||||||
|
在AndroidManifest.xml文件中声明权限,可以使用`<uses-permission>`标签来声明需要的权限,例如:
|
||||||
|
|
||||||
|
```
|
||||||
|
<manifest package="com.example.app">
|
||||||
|
<uses-permission android:name="android.permission.READ_CONTACTS" />
|
||||||
|
<uses-permission android:name="android.permission.CAMERA" />
|
||||||
|
...
|
||||||
|
</manifest>
|
||||||
|
```
|
||||||
|
|
||||||
### 6.5.2 APP权限的源码跟踪
|
Android中的常见权限列表如下。
|
||||||
|
|
||||||
|
1. 日历权限:`android.permission.READ_CALENDAR、android.permission.WRITE_CALENDAR`
|
||||||
|
2. 相机权限:`android.permission.CAMERA`
|
||||||
|
3. 联系人权限:`android.permission.READ_CONTACTS、android.permission.WRITE_CONTACTS、android.permission.GET_ACCOUNTS`
|
||||||
|
4. 定位权限:`android.permission.ACCESS_FINE_LOCATION、android.permission.ACCESS_COARSE_LOCATION`
|
||||||
|
5. 麦克风权限:`android.permission.RECORD_AUDIO`
|
||||||
|
6. 手机状态和电话权限:`android.permission.READ_PHONE_STATE、android.permission.CALL_PHONE、android.permission.READ_CALL_LOG、android.permission.WRITE_CALL_LOG、android.permission.ADD_VOICEMAIL、android.permission.USE_SIP、android.permission.PROCESS_OUTGOING_CALLS`
|
||||||
|
7. 传感器权限:`android.permission.BODY_SENSORS`
|
||||||
|
8. 短信权限:`android.permission.READ_SMS、android.permission.RECEIVE_SMS、android.permission.SEND_SMS、android.permission.RECEIVE_WAP_PUSH、android.permission.RECEIVE_MMS`
|
||||||
|
9. 存储权限:`android.permission.READ_EXTERNAL_STORAGE、android.permission.WRITE_EXTERNAL_STORAGE`
|
||||||
|
10. 联网权限:`android.permission.INTERNET`
|
||||||
|
|
||||||
|
androidManifest.xml文件是Android应用程序的清单文件,它在应用程序安装和运行过程中都会被解析。Android系统启动时也会解析每个已安装应用程序的清单文件(即androidManifest.xml),以了解应用程序所需的权限、组件等信息,并将这些信息记录在系统中。而这项解析工作是由`PackageManagerService`系统服务来完成的。因此开始分析的入手点可以从该系统服务的启动开始。
|
||||||
|
|
||||||
### 6.5.3 AOSP10下的默认权限修改
|
### 6.5.2 权限解析源码跟踪
|
||||||
|
|
||||||
|
`PackageManagerService`系统服务的启动是再`SystemServer`进程中,在`SystemServer.java`中搜索就能该进程启动的入口,相关代码如下。
|
||||||
|
|
||||||
|
```java
|
||||||
|
private void startBootstrapServices() {
|
||||||
|
...
|
||||||
|
t.traceBegin("StartPackageManagerService");
|
||||||
|
try {
|
||||||
|
Watchdog.getInstance().pauseWatchingCurrentThread("packagemanagermain");
|
||||||
|
mPackageManagerService = PackageManagerService.main(mSystemContext, installer,
|
||||||
|
domainVerificationService, mFactoryTestMode != FactoryTest.FACTORY_TEST_OFF,
|
||||||
|
mOnlyCore);
|
||||||
|
} finally {
|
||||||
|
Watchdog.getInstance().resumeWatchingCurrentThread("packagemanagermain");
|
||||||
|
}
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## 6.6 进程注入器
|
继续跟进看该服务的main函数
|
||||||
|
|
||||||
|
```java
|
||||||
|
public static PackageManagerService main(Context context, Installer installer,
|
||||||
|
@NonNull DomainVerificationService domainVerificationService, boolean factoryTest,
|
||||||
|
boolean onlyCore) {
|
||||||
|
...
|
||||||
|
PackageManagerService m = new PackageManagerService(injector, onlyCore, factoryTest,
|
||||||
|
Build.FINGERPRINT, Build.IS_ENG, Build.IS_USERDEBUG, Build.VERSION.SDK_INT,
|
||||||
|
Build.VERSION.INCREMENTAL);
|
||||||
|
...
|
||||||
|
ServiceManager.addService("package", m);
|
||||||
|
final PackageManagerNative pmn = m.new PackageManagerNative();
|
||||||
|
ServiceManager.addService("package_native", pmn);
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
然后这里调用了`PackageManagerService`的构造函数,继续查看构造函数代码。
|
||||||
|
|
||||||
|
```java
|
||||||
|
public PackageManagerService(Injector injector, boolean onlyCore, boolean factoryTest,
|
||||||
|
final String buildFingerprint, final boolean isEngBuild,
|
||||||
|
final boolean isUserDebugBuild, final int sdkVersion, final String incrementalVersion) {
|
||||||
|
...
|
||||||
|
synchronized (mInstallLock) {
|
||||||
|
// writer
|
||||||
|
synchronized (mLock) {
|
||||||
|
...
|
||||||
|
// 遍历系统应用程序目录列表
|
||||||
|
for (int i = mDirsToScanAsSystem.size() - 1; i >= 0; i--) {
|
||||||
|
final ScanPartition partition = mDirsToScanAsSystem.get(i);
|
||||||
|
if (partition.getOverlayFolder() == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
scanDirTracedLI(partition.getOverlayFolder(), systemParseFlags,
|
||||||
|
systemScanFlags | partition.scanFlag, 0,
|
||||||
|
packageParser, executorService);
|
||||||
|
}
|
||||||
|
|
||||||
|
scanDirTracedLI(frameworkDir, systemParseFlags,
|
||||||
|
systemScanFlags | SCAN_NO_DEX | SCAN_AS_PRIVILEGED, 0,
|
||||||
|
packageParser, executorService);
|
||||||
|
...
|
||||||
|
|
||||||
|
} // synchronized (mLock)
|
||||||
|
} // synchronized (mInstallLock)
|
||||||
|
// CHECKSTYLE:ON IndentationCheck
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`mDirsToScanAsSystem`是`PackageManagerService`类中的一个成员变量,用于存储系统应用程序目录列表。
|
||||||
|
|
||||||
|
系统应用程序存储在多个目录中,例如`/system/app、/system/priv-app`等。当系统启动时,`PackageManagerService`类会扫描这些目录以查找系统应用程序,并将其添加到应用程序列表中。这个列表中的每个元素都是一个File对象,表示一个系统应用程序目录。
|
||||||
|
|
||||||
|
当系统启动时,`PackageManagerService`会遍历`mDirsToScanAsSystem`列表并扫描其中的所有目录以查找系统应用程序。如果发现新的应用程序,则将其添加到应用程序列表中;如果发现已删除或升级的应用程序,则将其添加到`possiblyDeletedUpdatedSystemApps`列表中进行后续处理。下面看看`scanDirTracedLI`方法的实现。
|
||||||
|
|
||||||
|
```java
|
||||||
|
private void scanDirTracedLI(File scanDir, final int parseFlags, int scanFlags,
|
||||||
|
long currentTime, PackageParser2 packageParser, ExecutorService executorService) {
|
||||||
|
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "scanDir [" + scanDir.getAbsolutePath() + "]");
|
||||||
|
try {
|
||||||
|
scanDirLI(scanDir, parseFlags, scanFlags, currentTime, packageParser, executorService);
|
||||||
|
} finally {
|
||||||
|
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void scanDirLI(File scanDir, int parseFlags, int scanFlags, long currentTime,
|
||||||
|
PackageParser2 packageParser, ExecutorService executorService) {
|
||||||
|
final File[] files = scanDir.listFiles();
|
||||||
|
if (ArrayUtils.isEmpty(files)) {
|
||||||
|
Log.d(TAG, "No files in app dir " + scanDir);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (DEBUG_PACKAGE_SCANNING) {
|
||||||
|
Log.d(TAG, "Scanning app dir " + scanDir + " scanFlags=" + scanFlags
|
||||||
|
+ " flags=0x" + Integer.toHexString(parseFlags));
|
||||||
|
}
|
||||||
|
|
||||||
|
ParallelPackageParser parallelPackageParser =
|
||||||
|
new ParallelPackageParser(packageParser, executorService);
|
||||||
|
|
||||||
|
// Submit files for parsing in parallel
|
||||||
|
int fileCount = 0;
|
||||||
|
// 遍历所有文件
|
||||||
|
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()方法异步地将其提交给PackageParser类来解析
|
||||||
|
parallelPackageParser.submit(file, parseFlags);
|
||||||
|
fileCount++;
|
||||||
|
}
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
以上代码可以看到`scanDirLI`方法主要是遍历所有文件筛选是Apk文件,或者是一个目录,`isStageName`方法是判断当前文件是否为分阶段安装的数据,`parallelPackageParser.submit()`方法异步地将其提交给`PackageParser`类来解析。跟踪查看`submit`。
|
||||||
|
|
||||||
|
```java
|
||||||
|
public void submit(File scanFile, int parseFlags) {
|
||||||
|
mExecutorService.submit(() -> {
|
||||||
|
ParseResult pr = new ParseResult();
|
||||||
|
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parallel parsePackage [" + scanFile + "]");
|
||||||
|
try {
|
||||||
|
pr.scanFile = scanFile;
|
||||||
|
// 解析应用程序包
|
||||||
|
pr.parsedPackage = parsePackage(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();
|
||||||
|
// Propagate result to callers of take().
|
||||||
|
// This is helpful to prevent main thread from getting stuck waiting on
|
||||||
|
// ParallelPackageParser to finish in case of interruption
|
||||||
|
mInterruptedInThread = Thread.currentThread().getName();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
继续跟踪`parsePackage`是如何解析的
|
||||||
|
|
||||||
|
```java
|
||||||
|
protected ParsedPackage parsePackage(File scanFile, int parseFlags)
|
||||||
|
throws PackageParser.PackageParserException {
|
||||||
|
return mPackageParser.parsePackage(scanFile, parseFlags, true);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
这里需要留意`mPackageParser`的类型是`PackageParser2`,而在AOSP10中,它的类型是`PackageParser`。继续查看`parsePackage`方法的实现。
|
||||||
|
|
||||||
|
```java
|
||||||
|
public ParsedPackage parsePackage(File packageFile, int flags, boolean useCaches)
|
||||||
|
throws PackageParserException {
|
||||||
|
// 尝试从缓存中找解析结果
|
||||||
|
if (useCaches && mCacher != null) {
|
||||||
|
ParsedPackage parsed = mCacher.getCachedResult(packageFile, flags);
|
||||||
|
if (parsed != null) {
|
||||||
|
return parsed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
long parseTime = LOG_PARSE_TIMINGS ? SystemClock.uptimeMillis() : 0;
|
||||||
|
ParseInput input = mSharedResult.get().reset();
|
||||||
|
// 解析
|
||||||
|
ParseResult<ParsingPackage> result = parsingUtils.parsePackage(input, packageFile, flags);
|
||||||
|
if (result.isError()) {
|
||||||
|
throw new PackageParserException(result.getErrorCode(), result.getErrorMessage(),
|
||||||
|
result.getException());
|
||||||
|
}
|
||||||
|
...
|
||||||
|
return parsed;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
继续跟踪`parsingUtils.parsePackage`的实现。
|
||||||
|
|
||||||
|
```java
|
||||||
|
public ParseResult<ParsingPackage> parsePackage(ParseInput input, File packageFile,
|
||||||
|
int flags)
|
||||||
|
throws PackageParserException {
|
||||||
|
if (packageFile.isDirectory()) {
|
||||||
|
return parseClusterPackage(input, packageFile, flags);
|
||||||
|
} else {
|
||||||
|
return parseMonolithicPackage(input, packageFile, flags);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
如果是一个目录,则说明这是一个集群版本`(cluster package)`的应用程序包,可能由多个应用程序组成。在这种情况下,它调用`parseClusterPackage`方法对应用程序包进行解析,并返回解析结果。
|
||||||
|
|
||||||
|
`parseClusterPackage`方法会遍历该目录下的所有文件,解析其中的每个应用程序,并将它们打包成一个`PackageParser.Package`集合返回。每个`PackageParser.Package`对象表示单独的一个应用程序。
|
||||||
|
|
||||||
|
如果`packageFile`不是一个目录,则说明这是一个单体版本`(monolithic package)`的应用程序包,只包含一个应用程序。在这种情况下,它调用`parseMonolithicPackage`方法对应用程序包进行解析,并返回解析结果。
|
||||||
|
|
||||||
|
`parseMonolithicPackage`方法会读取应用程序包的内容,并解析其中的`AndroidManifest.xml`文件和资源文件等信息,然后创建一个`PackageParser.Package`对象来表示整个应用程序,并返回该对象作为解析结果。
|
||||||
|
|
||||||
|
所以我们跟踪一条路线即可,接下来查看`parseMonolithicPackage`的实现代码。
|
||||||
|
|
||||||
|
```java
|
||||||
|
private ParseResult<ParsingPackage> parseMonolithicPackage(ParseInput input, File apkFile,
|
||||||
|
int flags) throws PackageParserException {
|
||||||
|
...
|
||||||
|
try {
|
||||||
|
// 解析应用程序
|
||||||
|
final ParseResult<ParsingPackage> result = parseBaseApk(input,
|
||||||
|
apkFile,
|
||||||
|
apkFile.getCanonicalPath(),
|
||||||
|
assetLoader, flags);
|
||||||
|
if (result.isError()) {
|
||||||
|
return input.error(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
return input.success(result.getResult()
|
||||||
|
.setUse32BitAbi(lite.isUse32bitAbi()));
|
||||||
|
} catch (IOException e) {
|
||||||
|
return input.error(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,
|
||||||
|
"Failed to get path: " + apkFile, e);
|
||||||
|
} finally {
|
||||||
|
IoUtils.closeQuietly(assetLoader);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
继续查看`parseBaseApk`的实现代码。
|
||||||
|
|
||||||
|
```java
|
||||||
|
private ParseResult<ParsingPackage> parseBaseApk(ParseInput input, File apkFile,
|
||||||
|
String codePath, SplitAssetLoader assetLoader, int flags)
|
||||||
|
throws PackageParserException {
|
||||||
|
final String apkPath = apkFile.getAbsolutePath();
|
||||||
|
...
|
||||||
|
// 读取AndroidMannifest.xml文件
|
||||||
|
try (XmlResourceParser parser = assets.openXmlResourceParser(cookie,
|
||||||
|
ANDROID_MANIFEST_FILENAME)) {
|
||||||
|
final Resources res = new Resources(assets, mDisplayMetrics, null);
|
||||||
|
// 调用另一个重载进行解析
|
||||||
|
ParseResult<ParsingPackage> result = parseBaseApk(input, apkPath, codePath, res,
|
||||||
|
parser, flags);
|
||||||
|
...
|
||||||
|
return input.success(pkg);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return input.error(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,
|
||||||
|
"Failed to read manifest from " + apkPath, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
在这里看到读取`AndroidMannifest.xml`配置文件了,随后调用另一个重载进行解析。代码如下。
|
||||||
|
|
||||||
|
```java
|
||||||
|
private ParseResult<ParsingPackage> parseBaseApk(ParseInput input, String apkPath,
|
||||||
|
String codePath, Resources res, XmlResourceParser parser, int flags)
|
||||||
|
throws XmlPullParserException, IOException {
|
||||||
|
...
|
||||||
|
final TypedArray manifestArray = res.obtainAttributes(parser, R.styleable.AndroidManifest);
|
||||||
|
try {
|
||||||
|
final boolean isCoreApp =
|
||||||
|
parser.getAttributeBooleanValue(null, "coreApp", false);
|
||||||
|
final ParsingPackage pkg = mCallback.startParsingPackage(
|
||||||
|
pkgName, apkPath, codePath, manifestArray, isCoreApp);
|
||||||
|
// 解析Apk文件中xml的各种标记
|
||||||
|
final ParseResult<ParsingPackage> result =
|
||||||
|
parseBaseApkTags(input, pkg, manifestArray, res, parser, flags);
|
||||||
|
if (result.isError()) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
return input.success(pkg);
|
||||||
|
} finally {
|
||||||
|
manifestArray.recycle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
继续查看`parseBaseApkTags`的实现代码。
|
||||||
|
|
||||||
|
```java
|
||||||
|
private ParseResult<ParsingPackage> parseBaseApkTags(ParseInput input, ParsingPackage pkg,
|
||||||
|
TypedArray sa, Resources res, XmlResourceParser parser, int flags)
|
||||||
|
throws XmlPullParserException, IOException {
|
||||||
|
...
|
||||||
|
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
|
||||||
|
&& (type != XmlPullParser.END_TAG
|
||||||
|
|| parser.getDepth() > depth)) {
|
||||||
|
...
|
||||||
|
// <application> has special logic, so it's handled outside the general method
|
||||||
|
if (TAG_APPLICATION.equals(tagName)) {
|
||||||
|
if (foundApp) {
|
||||||
|
...
|
||||||
|
} else {
|
||||||
|
foundApp = true;
|
||||||
|
result = parseBaseApplication(input, pkg, res, parser, flags);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result = parseBaseApkTag(tagName, input, pkg, res, parser, flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.isError()) {
|
||||||
|
return input.error(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
...
|
||||||
|
return input.success(pkg);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
检查`tagName`是否为`<application>`标记。如果是`<application>`标记,则表示当前正在解析应用程序包的主要组件,在该标记中会定义应用程序的所有组件、权限等信息。如果没有发现`<application>`标记,则继续递归调用处理其他标记。所以接下来查看`parseBaseApplication`方法的实现。
|
||||||
|
|
||||||
|
```java
|
||||||
|
private ParseResult<ParsingPackage> parseBaseApplication(ParseInput input,
|
||||||
|
ParsingPackage pkg, Resources res, XmlResourceParser parser, int flags)
|
||||||
|
throws XmlPullParserException, IOException {
|
||||||
|
final String pkgName = pkg.getPackageName();
|
||||||
|
int targetSdk = pkg.getTargetSdkVersion();
|
||||||
|
|
||||||
|
TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestApplication);
|
||||||
|
try {
|
||||||
|
...
|
||||||
|
// 解析应用程序包中基本APK文件的标志
|
||||||
|
parseBaseAppBasicFlags(pkg, sa);
|
||||||
|
...
|
||||||
|
// 根据xml配置,对pkg的值做相应的修改
|
||||||
|
if (sa.getBoolean(R.styleable.AndroidManifestApplication_persistent, false)) {
|
||||||
|
// Check if persistence is based on a feature being present
|
||||||
|
final String requiredFeature = sa.getNonResourceString(R.styleable
|
||||||
|
.AndroidManifestApplication_persistentWhenFeatureAvailable);
|
||||||
|
pkg.setPersistent(requiredFeature == null || mCallback.hasFeature(requiredFeature));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sa.hasValueOrEmpty(R.styleable.AndroidManifestApplication_resizeableActivity)) {
|
||||||
|
pkg.setResizeableActivity(sa.getBoolean(
|
||||||
|
R.styleable.AndroidManifestApplication_resizeableActivity, true));
|
||||||
|
} else {
|
||||||
|
pkg.setResizeableActivityViaSdkVersion(
|
||||||
|
targetSdk >= Build.VERSION_CODES.N);
|
||||||
|
}
|
||||||
|
...
|
||||||
|
|
||||||
|
} finally {
|
||||||
|
sa.recycle();
|
||||||
|
}
|
||||||
|
...
|
||||||
|
// 根据xml中的tag进行对应的处理
|
||||||
|
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
|
||||||
|
&& (type != XmlPullParser.END_TAG
|
||||||
|
|| parser.getDepth() > depth)) {
|
||||||
|
if (type != XmlPullParser.START_TAG) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
final ParseResult result;
|
||||||
|
String tagName = parser.getName();
|
||||||
|
boolean isActivity = false;
|
||||||
|
switch (tagName) {
|
||||||
|
case "activity":
|
||||||
|
isActivity = true;
|
||||||
|
// fall-through
|
||||||
|
case "receiver":
|
||||||
|
ParseResult<ParsedActivity> activityResult =
|
||||||
|
ParsedActivityUtils.parseActivityOrReceiver(mSeparateProcesses, pkg,
|
||||||
|
res, parser, flags, sUseRoundIcon, input);
|
||||||
|
|
||||||
|
if (activityResult.isSuccess()) {
|
||||||
|
ParsedActivity activity = activityResult.getResult();
|
||||||
|
if (isActivity) {
|
||||||
|
hasActivityOrder |= (activity.getOrder() != 0);
|
||||||
|
pkg.addActivity(activity);
|
||||||
|
} else {
|
||||||
|
hasReceiverOrder |= (activity.getOrder() != 0);
|
||||||
|
pkg.addReceiver(activity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result = activityResult;
|
||||||
|
break;
|
||||||
|
case "service":
|
||||||
|
ParseResult<ParsedService> serviceResult =
|
||||||
|
ParsedServiceUtils.parseService(mSeparateProcesses, pkg, res, parser,
|
||||||
|
flags, sUseRoundIcon, input);
|
||||||
|
if (serviceResult.isSuccess()) {
|
||||||
|
ParsedService service = serviceResult.getResult();
|
||||||
|
hasServiceOrder |= (service.getOrder() != 0);
|
||||||
|
pkg.addService(service);
|
||||||
|
}
|
||||||
|
|
||||||
|
result = serviceResult;
|
||||||
|
break;
|
||||||
|
case "provider":
|
||||||
|
ParseResult<ParsedProvider> providerResult =
|
||||||
|
ParsedProviderUtils.parseProvider(mSeparateProcesses, pkg, res, parser,
|
||||||
|
flags, sUseRoundIcon, input);
|
||||||
|
if (providerResult.isSuccess()) {
|
||||||
|
pkg.addProvider(providerResult.getResult());
|
||||||
|
}
|
||||||
|
|
||||||
|
result = providerResult;
|
||||||
|
break;
|
||||||
|
case "activity-alias":
|
||||||
|
activityResult = ParsedActivityUtils.parseActivityAlias(pkg, res,
|
||||||
|
parser, sUseRoundIcon, input);
|
||||||
|
if (activityResult.isSuccess()) {
|
||||||
|
ParsedActivity activity = activityResult.getResult();
|
||||||
|
hasActivityOrder |= (activity.getOrder() != 0);
|
||||||
|
pkg.addActivity(activity);
|
||||||
|
}
|
||||||
|
|
||||||
|
result = activityResult;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
result = parseBaseAppChildTag(input, tagName, pkg, res, parser, flags);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.isError()) {
|
||||||
|
return input.error(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
...
|
||||||
|
return input.success(pkg);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
基本大多数的解析都在这里实现了,最后看看基本APK标志是如何解析处理的。`parseBaseAppBasicFlags`的实现如下。
|
||||||
|
|
||||||
|
```java
|
||||||
|
private void parseBaseAppBasicFlags(ParsingPackage pkg, TypedArray sa) {
|
||||||
|
int targetSdk = pkg.getTargetSdkVersion();
|
||||||
|
//@formatter:off
|
||||||
|
// CHECKSTYLE:off
|
||||||
|
pkg
|
||||||
|
// Default true
|
||||||
|
.setAllowBackup(bool(true, R.styleable.AndroidManifestApplication_allowBackup, sa))
|
||||||
|
.setAllowClearUserData(bool(true, R.styleable.AndroidManifestApplication_allowClearUserData, sa))
|
||||||
|
.setAllowClearUserDataOnFailedRestore(bool(true, R.styleable.AndroidManifestApplication_allowClearUserDataOnFailedRestore, sa))
|
||||||
|
.setAllowNativeHeapPointerTagging(bool(true, R.styleable.AndroidManifestApplication_allowNativeHeapPointerTagging, sa))
|
||||||
|
.setEnabled(bool(true, R.styleable.AndroidManifestApplication_enabled, sa))
|
||||||
|
.setExtractNativeLibs(bool(true, R.styleable.AndroidManifestApplication_extractNativeLibs, sa))
|
||||||
|
.setHasCode(bool(true, R.styleable.AndroidManifestApplication_hasCode, sa))
|
||||||
|
// Default false
|
||||||
|
.setAllowTaskReparenting(bool(false, R.styleable.AndroidManifestApplication_allowTaskReparenting, sa))
|
||||||
|
.setCantSaveState(bool(false, R.styleable.AndroidManifestApplication_cantSaveState, sa))
|
||||||
|
.setCrossProfile(bool(false, R.styleable.AndroidManifestApplication_crossProfile, sa))
|
||||||
|
.setDebuggable(bool(false, R.styleable.AndroidManifestApplication_debuggable, sa))
|
||||||
|
.setDefaultToDeviceProtectedStorage(bool(false, R.styleable.AndroidManifestApplication_defaultToDeviceProtectedStorage, sa))
|
||||||
|
.setDirectBootAware(bool(false, R.styleable.AndroidManifestApplication_directBootAware, sa))
|
||||||
|
.setForceQueryable(bool(false, R.styleable.AndroidManifestApplication_forceQueryable, sa))
|
||||||
|
.setGame(bool(false, R.styleable.AndroidManifestApplication_isGame, sa))
|
||||||
|
.setHasFragileUserData(bool(false, R.styleable.AndroidManifestApplication_hasFragileUserData, sa))
|
||||||
|
.setLargeHeap(bool(false, R.styleable.AndroidManifestApplication_largeHeap, sa))
|
||||||
|
.setMultiArch(bool(false, R.styleable.AndroidManifestApplication_multiArch, sa))
|
||||||
|
.setPreserveLegacyExternalStorage(bool(false, R.styleable.AndroidManifestApplication_preserveLegacyExternalStorage, sa))
|
||||||
|
.setRequiredForAllUsers(bool(false, R.styleable.AndroidManifestApplication_requiredForAllUsers, sa))
|
||||||
|
.setSupportsRtl(bool(false, R.styleable.AndroidManifestApplication_supportsRtl, sa))
|
||||||
|
.setTestOnly(bool(false, R.styleable.AndroidManifestApplication_testOnly, sa))
|
||||||
|
.setUseEmbeddedDex(bool(false, R.styleable.AndroidManifestApplication_useEmbeddedDex, sa))
|
||||||
|
.setUsesNonSdkApi(bool(false, R.styleable.AndroidManifestApplication_usesNonSdkApi, sa))
|
||||||
|
.setVmSafeMode(bool(false, R.styleable.AndroidManifestApplication_vmSafeMode, sa))
|
||||||
|
.setAutoRevokePermissions(anInt(R.styleable.AndroidManifestApplication_autoRevokePermissions, sa))
|
||||||
|
.setAttributionsAreUserVisible(bool(false, R.styleable.AndroidManifestApplication_attributionsAreUserVisible, sa))
|
||||||
|
// targetSdkVersion gated
|
||||||
|
.setAllowAudioPlaybackCapture(bool(targetSdk >= Build.VERSION_CODES.Q, R.styleable.AndroidManifestApplication_allowAudioPlaybackCapture, sa))
|
||||||
|
.setBaseHardwareAccelerated(bool(targetSdk >= Build.VERSION_CODES.ICE_CREAM_SANDWICH, R.styleable.AndroidManifestApplication_hardwareAccelerated, sa))
|
||||||
|
.setRequestLegacyExternalStorage(bool(targetSdk < Build.VERSION_CODES.Q, R.styleable.AndroidManifestApplication_requestLegacyExternalStorage, sa))
|
||||||
|
.setUsesCleartextTraffic(bool(targetSdk < Build.VERSION_CODES.P, R.styleable.AndroidManifestApplication_usesCleartextTraffic, sa))
|
||||||
|
// Ints Default 0
|
||||||
|
.setUiOptions(anInt(R.styleable.AndroidManifestApplication_uiOptions, sa))
|
||||||
|
// Ints
|
||||||
|
.setCategory(anInt(ApplicationInfo.CATEGORY_UNDEFINED, R.styleable.AndroidManifestApplication_appCategory, sa))
|
||||||
|
// Floats Default 0f
|
||||||
|
.setMaxAspectRatio(aFloat(R.styleable.AndroidManifestApplication_maxAspectRatio, sa))
|
||||||
|
.setMinAspectRatio(aFloat(R.styleable.AndroidManifestApplication_minAspectRatio, sa))
|
||||||
|
// Resource ID
|
||||||
|
.setBanner(resId(R.styleable.AndroidManifestApplication_banner, sa))
|
||||||
|
.setDescriptionRes(resId(R.styleable.AndroidManifestApplication_description, sa))
|
||||||
|
.setIconRes(resId(R.styleable.AndroidManifestApplication_icon, sa))
|
||||||
|
.setLogo(resId(R.styleable.AndroidManifestApplication_logo, sa))
|
||||||
|
.setNetworkSecurityConfigRes(resId(R.styleable.AndroidManifestApplication_networkSecurityConfig, sa))
|
||||||
|
.setRoundIconRes(resId(R.styleable.AndroidManifestApplication_roundIcon, sa))
|
||||||
|
.setTheme(resId(R.styleable.AndroidManifestApplication_theme, sa))
|
||||||
|
.setDataExtractionRules(
|
||||||
|
resId(R.styleable.AndroidManifestApplication_dataExtractionRules, sa))
|
||||||
|
// Strings
|
||||||
|
.setClassLoaderName(string(R.styleable.AndroidManifestApplication_classLoader, sa))
|
||||||
|
.setRequiredAccountType(string(R.styleable.AndroidManifestApplication_requiredAccountType, sa))
|
||||||
|
.setRestrictedAccountType(string(R.styleable.AndroidManifestApplication_restrictedAccountType, sa))
|
||||||
|
.setZygotePreloadName(string(R.styleable.AndroidManifestApplication_zygotePreloadName, sa))
|
||||||
|
// Non-Config String
|
||||||
|
.setPermission(nonConfigString(0, R.styleable.AndroidManifestApplication_permission, sa));
|
||||||
|
// CHECKSTYLE:on
|
||||||
|
//@formatter:on
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
相信你坚持跟踪到这里后,对于权限处理已经豁然开朗了,实际上总结就是,读取并解析xml文件,然后根据xml中配置的节点进行相应的处理,最终这些处理都是将值对应的设置给了`ParsingPackage`类型的对象`pkg`中。最终外层就通过拿到pkg对象,知道应该如何控制它的权限了。
|
||||||
|
|
||||||
|
### 6.5.3 修改APP默认权限
|
||||||
|
|
||||||
|
经过对源码的阅读,熟悉了APK对xml文件的解析流程后,想要为APP添加一个默认的权限就非常简单了。下面将为ROM添加一个联网权限:android.permission.INTERNET作为例子。只需要在`parseBaseApplication`函数中为`pkg`对象添加权限即可。
|
||||||
|
|
||||||
|
```java
|
||||||
|
private ParseResult<ParsingPackage> parseBaseApplication(ParseInput input,
|
||||||
|
ParsingPackage pkg, Resources res, XmlResourceParser parser, int flags)
|
||||||
|
throws XmlPullParserException, IOException {
|
||||||
|
...
|
||||||
|
// add 添加联网权限
|
||||||
|
List<String> requestedPermissions = pkg.getRequestedPermissions();
|
||||||
|
String addPermissionName = "android.permission.INTERNET";
|
||||||
|
if (!requestedPermissions.contains(addPermissionName)){
|
||||||
|
|
||||||
|
pkg.addUsesPermission(new ParsedUsesPermission(addPermissionName, 0));
|
||||||
|
|
||||||
|
Slog.w("mikrom","parseBaseApplication add android.permission.INTERNET " );
|
||||||
|
}
|
||||||
|
// add end
|
||||||
|
boolean hasActivityOrder = false;
|
||||||
|
boolean hasReceiverOrder = false;
|
||||||
|
boolean hasServiceOrder = false;
|
||||||
|
final int depth = parser.getDepth();
|
||||||
|
int type;
|
||||||
|
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
|
||||||
|
&& (type != XmlPullParser.END_TAG
|
||||||
|
|| parser.getDepth() > depth)) {
|
||||||
|
...
|
||||||
|
}
|
||||||
|
...
|
||||||
|
return input.success(pkg);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
理解源码中的实现原理后,有各种方式都能完成修改APP权限,由此可见,阅读跟踪源码观察实现原理是非常重要的手段。
|
||||||
|
|
||||||
|
## 6.6 进程注入
|
||||||
|
|
||||||
|
在上一小节中,通过对加载解析xml文件的流程进行分析,最终找到了合适的时机对默认权限进行修改,而在第三章的学习中,详细介绍了一个APP运行起来的流程,当对源码的运行流程有了足够的了解后,同样可以在其中找到合适的时机对普通用户的APP进行一些定制化的处理,例如对该进程进行注入,这一小节将介绍如何为用户进程注入jar包。
|
||||||
|
|
||||||
|
### 6.6.1 注入时机的选择
|
||||||
|
|
||||||
|
`ActivityThread`负责管理应用程序的主线程以及所有活动`Activity`的生命周期。通过`MessageQueue`和`Handler`机制与其他线程进行通信,处理来自系统和应用程序的各种消息。
|
||||||
|
|
||||||
|
在应用程序启动时,`ActivityThread`会被创建并开始运行,它会负责创建应用程序的主线程,并调用`Application`对象的`onCreate`方法初始化应用程序。同时,`ActivityThread`还会负责加载和启动应用程序中的第一个`Activity`,即启动界面`Splash Screen`或者主界面`Main Activity`,并处理`Activity`的生命周期事件,如`onCreate()、onResume()、onPause()`等。
|
||||||
|
|
||||||
|
所以可以在`ActivityThread`的调用中寻找合适的时机,那么什么叫合适的时机呢,可以将注入的需求进行整理,然后所有符合条件的调用时机都可以算作合适的时机。
|
||||||
|
|
||||||
|
注入时机尽量在一个仅调用一次的函数中,避免多次注入出现不可预料的异常情况。
|
||||||
|
|
||||||
|
注入时机分为早期和晚期,早期表示在一个调用链尽量靠前时机,这时进程的业务代码还没开始执行,就完成注入代码了,但是过早的时机会导致有些需要用到的数据还未准备就绪,例如`Application`未完成创建。如果你注入的代码无需涉及这些数据,那么可以选择尽量早的时机。例如在Zygote进程孵化的时机也是可以的。
|
||||||
|
|
||||||
|
第三章中介绍到的`handleBindApplication`就是比较合适的注入时机,主线程中通过调用这个方法来绑定应用程序,在该方法中创建了`Application`对象,并且调用了`attachBaseContext`方法和`onCreate`方法进行初始化。可以选择在创建`Application`对象后,就注入自己的jar包和so动态库。
|
||||||
|
|
||||||
|
### 6.6.2 注入jar包
|
||||||
|
|
||||||
|
在`handleBindApplication`方法中加一段注入jar包的方式和正常开发的APP中注入jar包并没有什么区别。在这个时机中是调用的onCreate方法,所以完成可以想象成是在onCreate中写一段注入代码。而onCreate中注入jar包在第五章,内置jar包中有详细介绍过。下面贴上当时的注入代码。
|
||||||
|
|
||||||
|
```java
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setContentView(R.layout.activity_main);
|
||||||
|
// 使用PathClassLoader加载jar文件
|
||||||
|
String jarPath = "/system/framework/kjar.jar";
|
||||||
|
ClassLoader systemClassLoader=ClassLoader.getSystemClassLoader();
|
||||||
|
String javaPath= System.getProperty("java.library.path");
|
||||||
|
PathClassLoader pathClassLoader=new PathClassLoader(jarPath,javaPath,systemClassLoader);
|
||||||
|
Class<?> clazz1 = null;
|
||||||
|
try {
|
||||||
|
// 通过反射调用函数
|
||||||
|
clazz1 = pathClassLoader.loadClass("cn.mik.myjar.MyCommon");
|
||||||
|
Method method = clazz1.getDeclaredMethod("getMyJarVer");
|
||||||
|
Object result = method.invoke(null);
|
||||||
|
Log.i("MainActivity","getMyJarVer:"+result);
|
||||||
|
|
||||||
|
} catch (ClassNotFoundException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (InvocationTargetException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (NoSuchMethodException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (IllegalAccessException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
唯一的区别仅仅在于,需要将注入的代码封装成一个方法,然后在`handleBindApplication`方法中,`Application`函数执行后进行调用。下面简单调整测试使用的jar包添加一个测试方法`injectJar`,代码如下。
|
||||||
|
|
||||||
|
```java
|
||||||
|
public class MyCommon {
|
||||||
|
public static String getMyJarVer(){
|
||||||
|
return "v1.0";
|
||||||
|
}
|
||||||
|
public static int add(int a,int b){
|
||||||
|
return a+b;
|
||||||
|
}
|
||||||
|
public static void injectJar(Application app){
|
||||||
|
Toast.makeText(app, "Hello, inject jar!", Toast.LENGTH_SHORT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
重新将测试的jar包编译后,解压并使用dx将classes.dex文件转换为jar包后内置到系统中。
|
||||||
|
|
||||||
|
```
|
||||||
|
unzip app-debug.apk -d app-debug
|
||||||
|
|
||||||
|
dx --dex --min-sdk-version=26 --output=./kjar.jar ./app-debug/classes.dex
|
||||||
|
|
||||||
|
cp ./kjar.jar ~/android_src/aosp12/framewoorks/native/myjar/
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
最后添加注入代码如下。
|
||||||
|
|
||||||
|
```java
|
||||||
|
private void InjectJar(Application app){
|
||||||
|
String jarPath = "/system/framework/kjar.jar";
|
||||||
|
ClassLoader systemClassLoader=ClassLoader.getSystemClassLoader();
|
||||||
|
String javaPath= System.getProperty("java.library.path");
|
||||||
|
PathClassLoader pathClassLoader=new PathClassLoader(jarPath,javaPath,systemClassLoader);
|
||||||
|
Class<?> clazz1 = null;
|
||||||
|
try {
|
||||||
|
// 通过反射调用函数
|
||||||
|
clazz1 = pathClassLoader.loadClass("cn.mik.myjar.MyCommon");
|
||||||
|
Method method = clazz1.getDeclaredMethod("injectJar",Application.class);
|
||||||
|
Object result = method.invoke(null);
|
||||||
|
|
||||||
|
} catch (ClassNotFoundException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (InvocationTargetException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (NoSuchMethodException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (IllegalAccessException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleBindApplication(AppBindData data) {
|
||||||
|
...
|
||||||
|
app = data.info.makeApplication(data.restrictedBackupMode, null);
|
||||||
|
// Propagate autofill compat state
|
||||||
|
app.setAutofillOptions(data.autofillOptions);
|
||||||
|
// Propagate Content Capture options
|
||||||
|
app.setContentCaptureOptions(data.contentCaptureOptions);
|
||||||
|
sendMessage(H.SET_CONTENT_CAPTURE_OPTIONS_CALLBACK, data.appInfo.packageName);
|
||||||
|
mInitialApplication = app;
|
||||||
|
// 注入
|
||||||
|
InjectJar(app)
|
||||||
|
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
准备就绪,编译并刷入手机中,安装任意app后,打开时都会弹出消息提示框。
|
||||||
|
|
||||||
|
注入so动态库同样和内置so的步骤没有任何区别,更简单的办法是通过在jar包中直接加载动态库即可,无需另外添加代码。
|
||||||
|
Loading…
x
Reference in New Issue
Block a user