解决Xposed拦截并修改:获取手机已安装应用的App包名和App标签问题

您所在的位置:网站首页 读取已安装应用信息是什么 解决Xposed拦截并修改:获取手机已安装应用的App包名和App标签问题

解决Xposed拦截并修改:获取手机已安装应用的App包名和App标签问题

2024-07-13 22:32| 来源: 网络整理| 查看: 265

首先,我们先明白一个问题,如何去遍历获取手机已安装应用的App相应的信息。

大多数情况下,我们使用PackageManager类提供的getInstalledPackages()接口来获取手机已安装应用信息。

例如博主这里的代码为:

PackageManager packageManager=getPackageManager(); List packageInfos=packageManager.getInstalledPackages(PackageManager.GET_UNINSTALLED_PACKAGES);

getInstalledPackages()源码分析:

/** * Return a List of all packages that are installed on the device. * * @param flags Additional option flags to modify the data returned. * @return A List of PackageInfo objects, one for each installed package, * containing information about the package. In the unlikely case * there are no installed packages, an empty list is returned. If * flag {@code MATCH_UNINSTALLED_PACKAGES} is set, the package * information is retrieved from the list of uninstalled * applications (which includes installed applications as well as * applications with data directory i.e. applications which had been * deleted with {@code DONT_DELETE_DATA} flag set). */ public abstract List getInstalledPackages(@PackageInfoFlags int flags);

首先我们明确一下,PackageManager是一个抽象类,它内部的getInstalledPackages接口也是一个抽象方法,返回的是一个PackageInfo类的List集合,PackageInfo类即为App信息的实体类。

下面我们开始探讨如何去拦截getInstalledPackages()接口。

我们可以看到getInstalledPackages()是一个抽象方法,所以肯定不能直接去Hook它,只能去Hook它的实现方法,即找到PackageManager抽象类的具体实现类。我们继续往下查找代码,看看是谁实现了PackageManager抽象类。

首当其冲很容易就会找到MockPackageManager类,我们发现它确实继承了PackageManager抽象类,但是它并不是真正的实现类。看一下它内部实现的getInstalledPackages接口:

@Override public List getInstalledPackages(int flags) { throw new UnsupportedOperationException(); }

仅仅是抛出了一个UnsupportedOperationException异常!

我们找到这个类的具体介绍:

* * A mock {@link android.content.pm.PackageManager} class. All methods are non-functional and throw * {@link java.lang.UnsupportedOperationException}. Override it to provide the operations that you * need. * * @deprecated Use a mocking framework like Mockito. * New tests should be written using the * Android Testing Support Library. */

意思为:MockPackageManager类是PackageManager的模拟类,重写它以提供您所需要的操作。所以它并不是真正的实现类。

那么真正实现PackageManager抽象类的到底是谁??

答案是:ApplicationPackageManager类。

ApplicationPackageManager类中具体实现了PackageManager抽象类的一系列接口,getInstalledPackages接口也是在ApplicationPackageManager类中实现的。

这个时候你可能要抓狂了,因为你发现在编辑器中写代码根本找不到这个ApplicationPackageManager类!

博主一开始也是对这个非常抓狂,一直找ApplicationPackageManager类的路径位置,终于让我给找到了,它位于AndroidSDK安装路径下。(ps:找到真的很不容易,平时开发很少关注SDK这块---TAT----)

具体博主的路径为:

 找到了就很好办了,打开看看它的源码实现:

/* * Copyright (C) 2010 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.app; import android.annotation.DrawableRes; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.StringRes; import android.annotation.XmlRes; import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.IntentSender; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.ChangedPackages; import android.content.pm.ComponentInfo; import android.content.pm.FeatureInfo; import android.content.pm.IOnPermissionsChangeListener; import android.content.pm.IPackageDataObserver; import android.content.pm.IPackageDeleteObserver; import android.content.pm.IPackageInstallObserver; import android.content.pm.IPackageManager; import android.content.pm.IPackageMoveObserver; import android.content.pm.IPackageStatsObserver; import android.content.pm.InstantAppInfo; import android.content.pm.InstrumentationInfo; import android.content.pm.IntentFilterVerificationInfo; import android.content.pm.KeySet; import android.content.pm.PackageInfo; import android.content.pm.PackageInstaller; import android.content.pm.PackageItemInfo; import android.content.pm.PackageManager; import android.content.pm.ParceledListSlice; import android.content.pm.PermissionGroupInfo; import android.content.pm.PermissionInfo; import android.content.pm.ProviderInfo; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.content.pm.SharedLibraryInfo; import android.content.pm.VerifierDeviceIdentity; import android.content.pm.VersionedPackage; import android.content.res.Resources; import android.content.res.XmlResourceParser; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Rect; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.LayerDrawable; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.os.Process; import android.os.RemoteException; import android.os.SystemProperties; import android.os.UserHandle; import android.os.UserManager; import android.os.storage.StorageManager; import android.os.storage.VolumeInfo; import android.provider.Settings; import android.system.ErrnoException; import android.system.Os; import android.system.OsConstants; import android.system.StructStat; import android.util.ArrayMap; import android.util.IconDrawableFactory; import android.util.LauncherIcons; import android.util.Log; import android.view.Display; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.SomeArgs; import com.android.internal.util.Preconditions; import com.android.internal.util.UserIcons; import dalvik.system.VMRuntime; import libcore.util.EmptyArray; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Objects; /** @hide */ public class ApplicationPackageManager extends PackageManager { private static final String TAG = "ApplicationPackageManager"; private final static boolean DEBUG_ICONS = false; private static final int DEFAULT_EPHEMERAL_COOKIE_MAX_SIZE_BYTES = 16384; // 16KB // Default flags to use with PackageManager when no flags are given. private final static int sDefaultFlags = PackageManager.GET_SHARED_LIBRARY_FILES; private final Object mLock = new Object(); @GuardedBy("mLock") private UserManager mUserManager; @GuardedBy("mLock") private PackageInstaller mInstaller; @GuardedBy("mDelegates") private final ArrayList mDelegates = new ArrayList(); @GuardedBy("mLock") private String mPermissionsControllerPackageName; ....... //代码过长,有两千多行,这里贴出具体实现代码 @SuppressWarnings("unchecked") @Override public List getInstalledPackages(int flags) { return getInstalledPackagesAsUser(flags, mContext.getUserId()); } /** @hide */ @Override @SuppressWarnings("unchecked") public List getInstalledPackagesAsUser(int flags, int userId) { try { ParceledListSlice parceledList = mPM.getInstalledPackages(flags, userId); if (parceledList == null) { return Collections.emptyList(); } return parceledList.getList(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } ........ }

从源码中我们可以很清楚的看到,首先我们可以看到ApplicationPackageManager类确实是继承了PackageManager抽象类,其中关键的getInstalledPackages接口实现可以看到,原来它是返回了getInstalledPackagesAsUser()方法调用,getInstalledPackagesAsUser()才是具体的实现,接着看getInstalledPackagesAsUser()方法,关键代码为:

ParceledListSlice parceledList = mPM.getInstalledPackages(flags, userId);

原来真正调用的是 IPackageManager类的getInstalledPackages()方法,参数flags即为我们在getInstalledPackages接口传入的值。userId为Context类中的userId变量。

更加深入的源码分析就到这里,有兴趣的读者可以自行查看。在这里我们的目的已经达到了,即找到PackageManager抽象类的具体实现类,下面我们开始探讨如何去拦截它的getInstalledPackages接口。

Hook->getInstalledPackages接口

上面我们已经分析到,getInstalledPackages接口本身是一个抽象方法,在ApplicationPackageManager类中进行了该抽象方法的具体实现,所以我们Hook的目标类是ApplicationPackageManager,目标方法是getInstalledPackages(Int)。

实现代码为:

XposedHelpers.findAndHookMethod("android.app.ApplicationPackageManager", loadPackageParam.classLoader, "getInstalledPackages", int.class, new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { //Hook后的具体逻辑操作 } });

在这里还有一个疑问,通常我们拦截到该方法后,需要返回我们自己的处理信息,我们了解到getInstalledPackages(Int)方法返回的是一个PackageInfo类的List集合,具体做修改的话难以下手,比如修改其中的某一个item信息,或者修改的是整条list,这该怎么办?具体的处理逻辑为:遍历原先的List集合,找到我们要修改的某条item,然后赋值修改该item的具体信息。

代码如下:

XposedHelpers.findAndHookMethod("android.app.ApplicationPackageManager", loadPackageParam.classLoader, "getInstalledPackages", int.class, new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { //自定义一个新的List用来接收原来的返回信息 List packageInfos=(List) param.getResult(); //开始遍历查找 for (PackageInfo packageInfo:packageInfos){ //匹配信息为你想要修改的某个App标签识别码 if (packageInfo.applicationInfo.labelRes==2131230747){ //找到了,修改该App的应用包名 packageInfo.packageName="com.test.testing"; } } //把修改后的List当作结果返回去 param.setResult(packageInfos); } });

这里具体再说一下出现的标签识别码labelRes

labelRes位于PackageItemInfo类中,官方描述为:

/** * A string resource identifier (in the package's resources) of this * component's label. From the "label" attribute or, if not set, 0. */

意思就是此包的字符串资源标识符(在包的资源中),主要作用是在查找App标签文本的时候作为标签文本的标识符,已确定查找到相应的App标签文本,我把它喊作是App标签识别码,意思更形象一些。

具体labelRes用到的地方就是我们接下来要将的部分,获取App标签文本信息。

博主获取App标签文本的代码是这样写的:

packageInfo.applicationInfo.loadLabel(getPackageManager()).toString()

其中packageInfo为PackageInfo类的对象。这里我们主要关注的是loadLabel()方法

loadLabel()方法位于PackageItemInfo类中,参数为一个PackageManager类的对象,这里并没有出现labelRes的影子,别急,我们接下来去看一下loadLabel()方法的源码:

/** * Retrieve the current textual label associated with this item. This * will call back on the given PackageManager to load the label from * the application. * * @param pm A PackageManager from which the label can be loaded; usually * the PackageManager from which you originally retrieved this item. * * @return Returns a CharSequence containing the item's label. If the * item does not have a label, its name is returned. */ public CharSequence loadLabel(PackageManager pm) { if (nonLocalizedLabel != null) { return nonLocalizedLabel; } if (labelRes != 0) { CharSequence label = pm.getText(packageName, labelRes, getApplicationInfo()); if (label != null) { return label.toString().trim(); } } if (name != null) { return name; } return packageName; }

 我们先一步一步的看,首先是判断了一个变量nonLocalizedLabel 是否为空,不为空的话就直接返回该变量。那么nonLocalizedLabel变量又是什么呢?我们看一下它的官方描述:

/** * The string provided in the AndroidManifest file, if any. You * probably don't want to use this. You probably want * {@link PackageManager#getApplicationLabel} */

意思为:AndroidManifest文件中提供的字符串(如果有的话)。也就是说如果App的核心配置文件里面写上了该App的标签,那么就可以直接返回该标签信息,不用执行下一步。很可惜的是大多数情况下nonLocalizedLabel变量为空,所以我们继续往下分析:

我们看到判断labelRes是否为0,这里主要是判断是否获取到了标识符labelRes,在获得labelRes值的时候,如果获取失败则返回0,成功就返回labelRes码,只有获取labelRes码成功才会进入获取App标签文本的方法getText().

毫无疑问,getText()方法的具体实现也是在ApplicationPackageManager类中,我们去看一下:

@Override public CharSequence getText(String packageName, @StringRes int resid, ApplicationInfo appInfo) { ResourceName name = new ResourceName(packageName, resid); CharSequence text = getCachedString(name); if (text != null) { return text; } if (appInfo == null) { try { appInfo = getApplicationInfo(packageName, sDefaultFlags); } catch (NameNotFoundException e) { return null; } } try { Resources r = getResourcesForApplication(appInfo); text = r.getText(resid); putCachedString(name, text); return text; } catch (NameNotFoundException e) { Log.w("PackageManager", "Failure retrieving resources for " + appInfo.packageName); } catch (RuntimeException e) { // If an exception was thrown, fall through to return // default icon. Log.w("PackageManager", "Failure retrieving text 0x" + Integer.toHexString(resid) + " in package " + packageName, e); } return null; }

 我们可以看到,具体的获取方法是getCachedString(),传入的参数是一个ResourceName 对象。我们的标识符labelRes最终传入了ResourceName类的构造函数里面。具体的源码分析不再讲解,这里我们主要的核心是如何去修改某个App相应的标签文本。

修改App相应的标签文本

两种Hook方式:

第一种:Hook目标类是PackageItemInfo类,目标方法是loadLabel()

XposedHelpers.findAndHookMethod(PackageItemInfo.class, "loadLabel", PackageManager.class, new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { //匹配想要修改的App的标签 if (param.getResult().equals("喜马拉雅FM")){ param.setResult("测试"); } } });

第二种:Hook目标是ApplicationPackageManager类,目标方法是getText()

XposedHelpers.findAndHookMethod("android.app.ApplicationPackageManager",loadPackageParam.classLoader, methodName, String.class,int.class, ApplicationInfo.class,new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { //匹配想要修改的App标签文本识别码 if ((int)param.args[1]==2131230747){ param.setResult("测试"); } } });

两种方式都可以修改某一条item的标签文本信息,具体的使用看业务逻辑。

 

本文到此结束,需要用到本文,请标明出处!



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3