首先,我们先明白一个问题,如何去遍历获取手机已安装应用的App相应的信息。
大多数情况下,我们使用PackageManager类提供的getInstalledPackages()接口来获取手机已安装应用信息。
例如博主这里的代码为:
PackageManager packageManager=getPackageManager();
List<PackageInfo> 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<PackageInfo> getInstalledPackages(@PackageInfoFlags int flags);
首先我们明确一下,PackageManager是一个抽象类,它内部的getInstalledPackages接口也是一个抽象方法,返回的是一个PackageInfo类的List集合,PackageInfo类即为App信息的实体类。
下面我们开始探讨如何去拦截getInstalledPackages()接口。
我们可以看到getInstalledPackages()是一个抽象方法,所以肯定不能直接去Hook它,只能去Hook它的实现方法,即找到PackageManager抽象类的具体实现类。我们继续往下查找代码,看看是谁实现了PackageManager抽象类。
首当其冲很容易就会找到MockPackageManager类,我们发现它确实继承了PackageManager抽象类,但是它并不是真正的实现类。看一下它内部实现的getInstalledPackages接口:
@Override
public List<PackageInfo> 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 <a href="https://github.com/mockito/mockito">Mockito</a>.
* New tests should be written using the
* <a href="{@docRoot}tools/testing-support-library/index.html">Android Testing Support Library</a>.
*/
意思为: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<MoveCallbackDelegate> mDelegates = new ArrayList<>();
@GuardedBy("mLock")
private String mPermissionsControllerPackageName;
.......
//代码过长,有两千多行,这里贴出具体实现代码
@SuppressWarnings("unchecked")
@Override
public List<PackageInfo> getInstalledPackages(int flags) {
return getInstalledPackagesAsUser(flags, mContext.getUserId());
}
/** @hide */
@Override
@SuppressWarnings("unchecked")
public List<PackageInfo> getInstalledPackagesAsUser(int flags, int userId) {
try {
ParceledListSlice<PackageInfo> 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<PackageInfo> 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<PackageInfo> 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的标签文本信息,具体的使用看业务逻辑。
本文到此结束,需要用到本文,请标明出处!