jQuery鼠标指针特效

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 //是否为launcher
  • android.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                       
posted @   僵小七  阅读(48)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· 实操Deepseek接入个人知识库
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· 【.NET】调用本地 Deepseek 模型
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库
历史上的今天:
2021-07-26 自定义控件-绘制文字-drawText
点击右上角即可分享
微信分享提示