Android 系统适配无源码app
Android系统,无源码apk分辨率适配
demo1:某个应用的字体大小和UI显示,不适配当前设备的屏幕dpi
先来看一个问题:app启动,onResume中是否可以测量宽高?
如果是activity 启动后第一次进入onResume 生命周期,那么获取到的View的宽高是错误的;
如果是从其他activity回到当前activity而执行的onResume方法,那么就能够获取到View的宽高
为什么?
先找到系统调用 onResume 的地方,read f*** source code
./frameworks/base/core/java/android/app/ActivityThread.java
handleResumeActivity() {
//...
r = performResumeActivity(token, clearHide, reason);//调用 onResume()
//...
wm.addView(decor, l);// WindowManager添加Decor(decor是DecorView)
//...
}
可以看到View的添加是要在执行完onResume()之后的,所以我们调整某个应用的字体大小和UI显示最好是在它之前
frameworks/base/core/java/android/app/Activity.java
protected void onCreate(@Nullable Bundle savedInstanceState) {
...
mRestoredFromBundle = savedInstanceState != null;
mCalled = true;
//add text
ActivityManager am = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
List<ActivityManager.RunningTaskInfo> runningTasks = am.getRunningTasks(1);
if (runningTasks != null && !runningTasks.isEmpty()) {
ComponentName topActivity = runningTasks.get(0).topActivity;
String packageName = topActivity.getPackageName();
if ("com.android.deskclock".equals(packageName)) {
Resources resources = getResources();
if (resources != null) {
Configuration configuration = resources.getConfiguration();
//适配字体
if(configuration != null){
if(configuration.fontScale != 1.5f){
configuration.fontScale = 1.5f;
resources.updateConfiguration(configuration,resources.getDisplayMetrics());
}
}
if (configuration != null) {
DisplayMetrics displayMetrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
int densityDpi = displayMetrics.densityDpi;
// 适配 dpi
if (densityDpi != 240) {
configuration.densityDpi = 240;
resources.updateConfiguration(configuration, resources.getDisplayMetrics());
}
}
}
}
}
//add text
}
Android 11 系统修改第三方应用的DPI
onResume中是否可以测量宽高
无源码app修改在Launcher3上显示的app图标
1.系统层修改,可以让系统内所有显示该app图标的地方都改了
/frameworks/base/core/java/android/content/pm/parsing/ParsingPackageUtils.java
parseBaseApk(){
...
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);
final ParseResult<ParsingPackage> result =
parseBaseApkTags(input, pkg, manifestArray, res, parser, flags);
if (result.isError()) {
return result;
}
return input.success(pkg);
} finally {
manifestArray.recycle();
}
...
}
-> parseBaseApkTags() -> parseBaseApplication() -> parseBaseAppBasicFlags(){
...
// Resource ID
.setBanner(resId(R.styleable.AndroidManifestApplication_banner, sa))
.setDescriptionRes(resId(R.styleable.AndroidManifestApplication_description, sa))
.setIconRes(resId(R.styleable.AndroidManifestApplication_icon, sa))// app应用图标,熟悉的AndroidManifest
.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))
// Strings
.setClassLoaderName(string(R.styleable.AndroidManifestApplication_classLoader, sa))
...
.setPermission(nonConfigString(0, R.styleable.AndroidManifestApplication_permission, sa));
//因为它是链式调用,不方便在上面添加拦截函数,最后替换
//add text
String packageName = pkg.getPackageName();
if(packageName != null && packageName.equals("com.android.settings")){
pkg.setIconRes(com.android.internal.R.drawable.perm_group_camera);//ic_battery
}
//add text
}
./frameworks/base/core/res/res/values/symbols.xml:: <java-symbol type="drawable" name="perm_group_camera" />
Android 11.0 PackageManagerService(二)APK扫描过程
2.Launcher3层面修改,在Launcher3层面修改界面上面显示的icon被修改了,setting和系统安装应用界面等还是原来的图
packages/apps/Launcher3/src/com/android/launcher3/BubbleTextView.java
@UiThread
protected void applyIconAndLabel(ItemInfoWithIcon info) {
boolean useTheme = mDisplay == DISPLAY_WORKSPACE || mDisplay == DISPLAY_FOLDER
|| mDisplay == DISPLAY_TASKBAR;
int flags = useTheme ? FLAG_THEMED : 0;
if (mHideBadge) {
flags |= FLAG_NO_BADGE;
}
FastBitmapDrawable iconDrawable = info.newIcon(getContext(), flags);
mDotParams.appColor = iconDrawable.getIconColor();
mDotParams.dotColor = getContext().getResources()
.getColor(android.R.color.system_accent3_200, getContext().getTheme());
//add text
String pkg = info.getIntent().getComponent().getPackageName();
String cls = info.getIntent().getComponent().getClassName();
if("com.android.text".equals(pkg)){
Bitmap bitmap = BitmapFactory.decodeResource(getResources(),R.drawable.BS_player);
setIcon(new FastBitmapDrawable(bitmap));
}else{
setIcon(iconDrawable);
}
//add text
applyLabel(info);
}
Android系统开发
Android第三方无源码应用图标icon定制
无源码app,没有申请对应权限,系统增加授予相关权限
遇到无源码apk,需要某个权限,但是apk内部没有申请,只能让系统适配授予某个权限给apk.
通过PMS(PackageManagerService)解析apk文件,安装apk的过程中来操作.
apk权限的申请通常都在AndroidManifest.xml文件中.
apk解析是PackageParser负责,Activity,Service等组件也是它负责.PackageParser相当于一个解释器.
frameworks/base/core/java/android/content/pm/PackageParser.java
public Package parsePackage(File packageFile, int flags, boolean useCaches)
throws PackageParserException {
if (packageFile.isDirectory()) {
return parseClusterPackage(packageFile, flags);
} else {
return parseMonolithicPackage(packageFile, flags);
}
}
private Package parseBaseApk(String apkPath, Resources res, XmlResourceParser parser, int flags,
String[] outError) throws XmlPullParserException, IOException {
final String splitName;
final String pkgName;
...
pkg.mCompileSdkVersion = sa.getInteger(
com.android.internal.R.styleable.AndroidManifest_compileSdkVersion, 0);
pkg.applicationInfo.compileSdkVersion = pkg.mCompileSdkVersion;
pkg.mCompileSdkVersionCodename = sa.getNonConfigurationString(
com.android.internal.R.styleable.AndroidManifest_compileSdkVersionCodename, 0);
if (pkg.mCompileSdkVersionCodename != null) {
pkg.mCompileSdkVersionCodename = pkg.mCompileSdkVersionCodename.intern();
}
pkg.applicationInfo.compileSdkVersionCodename = pkg.mCompileSdkVersionCodename;
sa.recycle();
return parseBaseApkCommon(pkg, null, res, parser, flags, outError);
}
/* @param pkg The package which to populate
* @param acceptedTags Which tags to handle, null to handle all
* @param res Resources against which to resolve values
* @param parser Parser of the manifest
* @param flags Flags about how to parse
* @param outError Human readable error if parsing fails
* @return The package if parsing succeeded or null.
*/
private Package parseBaseApkCommon(Package pkg, Set<String> acceptedTags, Resources res,
XmlResourceParser parser, int flags, String[] outError) throws XmlPullParserException,
IOException {
...
//关于权限申请的解析
} else if (tagName.equals(TAG_PERMISSION_GROUP)) {
if (!parsePermissionGroup(pkg, flags, res, parser, outError)) {
return null;
}
} else if (tagName.equals(TAG_PERMISSION)) {
if (!parsePermission(pkg, res, parser, outError)) {
return null;
}
} else if (tagName.equals(TAG_PERMISSION_TREE)) {
if (!parsePermissionTree(pkg, res, parser, outError)) {
return null;
}
} else if (tagName.equals(TAG_USES_PERMISSION)) {
if (!parseUsesPermission(pkg, res, parser)) {
return null;
}
} else if (tagName.equals(TAG_USES_PERMISSION_SDK_M)
|| tagName.equals(TAG_USES_PERMISSION_SDK_23)) {
if (!parseUsesPermission(pkg, res, parser)) {
return null;
}
...
}
private boolean parseUsesPermission(Package pkg, Resources res, XmlResourceParser parser)
throws XmlPullParserException, IOException {
...
//add text
android.util.Log.d("tag","name: "+name);
android.util.Log.d("tag",name.intern());
pkg.requestedPermissions.add("android.permission.ACCESS_FINE_LOCATION".intern());
pkg.requestedPermissions.add("android.permission.SYSTEM_ALERT_WINDOW".intern());
//add text
int index = pkg.requestedPermissions.indexOf(name);
if (index == -1) {
pkg.requestedPermissions.add(name.intern());
} else {
Slog.w(TAG, "Ignoring duplicate uses-permissions/uses-permissions-sdk-m: "
+ name + " in package: " + pkg.packageName + " at: "
+ parser.getPositionDescription());
}
return true;
}
无源码应用添加Launcher属性
应用程序的行为通常受到其清单文件(AndroidManifest.xml
)中声明的组件和属性的控制.
对于无源码的app,无法直接修改其清单文件,但可以通过修改系统源码来实现特定功能.
app 的Launcher属性 ,由两个关键的 Intent
类别决定:
android.intent.category.HOME
//是否为launcherandroid.intent.category.DEFAULT
android.intent.category.LAUNCHER
//是否有图标
还可以通过设置 IntentFilter
的优先级(Priority
)来确保应用程序的启动行为符合预期
ParsedActivityUtils
是一个负责解析应用程序组件的工具类,这里对特定的应用组件(Activity)进行拦截,并动态为其添加 Launcher
属性.
(Android T)
frameworks/base/services/core/java/com/android/server/pm/pkg/component/ParsedActivityUtils.java
@NonNull
private static ParseResult<ParsedActivity> parseActivityOrAlias(ParsedActivityImpl activity,
ParsingPackage pkg, String tag, XmlResourceParser parser, Resources resources,
TypedArray array, boolean isReceiver, boolean isAlias, boolean visibleToEphemeral,
ParseInput input, int parentActivityNameAttr, int permissionAttr,
int exportedAttr) throws IOException, XmlPullParserException {
...
ParsedIntentInfoImpl intentInfo = intentResult.getResult();
if (intentInfo != null) {
IntentFilter intentFilter = intentInfo.getIntentFilter();
//add text start
+ if("com.xx.xxx".equals(activity.getName())){
+ intentFilter.addCategory("android.intent.category.HOME");
+ intentFilter.addCategory("android.intent.category.DEFAULT");
+ intentFilter.setPriority(1000);//设置Priority为1000,确保该启动项的优先级较高
+ }
//add text end
activity.setOrder(Math.max(intentFilter.getOrder(), activity.getOrder()));
activity.addIntent(intentInfo);
...
}
(Android R)
+++ b/frameworks/base/core/java/android/content/pm/parsing/component/ParsedActivityUtils.java
@@ -333,6 +333,14 @@ public class ParsedActivityUtils {
ParsedIntentInfo intent = intentResult.getResult();
if (intent != null) {
activity.order = Math.max(intent.getOrder(), activity.order);
+ //add text start
+ if(activity.getName().contains("com.example.text_app")){
+ intent.addCategory("android.intent.category.HOME");
+ intent.addCategory("android.intent.category.DEFAULT");
+ intent.setPriority(1000);
+ }
+ //add text end
activity.addIntent(intent);
if (PackageParser.LOG_UNSAFE_BROADCASTS && isReceiver
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· 实操Deepseek接入个人知识库
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· 【.NET】调用本地 Deepseek 模型
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库
2021-07-26 自定义控件-绘制文字-drawText