如果我想检索当前用户所有应用程序的ApplicationInfo列表,我可以运行:
PackageManager pkgmanager = ctx.getPackageManager();
List<ApplicationInfo> installedApps = pkgmanager.getInstalledApplications(PackageManager.GET_META_DATA);
但我正在寻找一种方法来为每个用户获取这些列表,而不仅仅是当前用户。顺便说一句,我正在开发的应用程序具有根权限!
据我了解,用户可以像这样检索用户 ID:
List<Integer> userIds = new ArrayList<>();
PackageManager pkgmanager = ctx.getPackageManager();
final UserManager um = (UserManager) ctx.getSystemService(Context.USER_SERVICE);
List<UserHandle> list = um.getUserProfiles();
for (UserHandle user : list) {
Matcher m = p.matcher(user.toString());
if (m.find()) {
int id = Integer.parseInt(m.group(1));
userIds.add(id);
}
}
现在我正在寻找这样的东西:
for (int i=0; i<userIds.size(); i++) {
Integer userId = userIds.get(i)
List<ApplicationInfo> installedApps = pkgmanager.getInstalledApplicationsByUserId(userId, PackageManager.GET_META_DATA);
}
显然getInstalledApplicationsByUserId
不存在。
起初我认为getPackagesForUid可以解决这个问题,但事实证明,Linux意义上的"用户"和用户配置文件之间存在差异。通常,每个Android应用程序都在自己的用户下运行,以实现隔离目的。但是可以在同一用户下运行两个应用程序,以便他们可以轻松访问彼此的数据。getPackagesForUid
仅返回在给定用户 ID 下运行的所有应用程序的名称,该用户 ID 通常正好是一个。除了"用户"之外,还有"用户配置文件",这就是我希望该方法所指的。也许我也应该在我的代码中编写userProfileId
而不是userId
。
编辑: 使用 adb shell,我可以像这样检索应用程序 ID:
# Get user profile IDs
USER_PROFILE_IDS="$(pm list users | grep UserInfo | cut -d '{' -f2 | cut -d ':' -f1)"
# Iterate over user profile IDs
while read -r USER_PROFILE_ID ; do
# List the packages for each user profile by ID
PACKAGE_IDS_FOR_THIS_USER="$(pm list packages --user "$USER_PROFILE_ID" | cut -d ':' -f2)"
echo "#######################################################################"
echo "The user with id $USER_PROFILE_ID has the following packages installed:"
echo "$PACKAGE_IDS_FOR_THIS_USER"
done <<< "$USER_PROFILE_IDS"
但那是Bash而不是Java...
编辑2: 如果我错了,请纠正我(这是我第一次用 Java 编写代码),但似乎没有 Java API 可以做到这一点。因此,执行此操作的唯一方法是使用 shell。所以这就是我想出的:
import com.stericson.rootshell.RootShell;
import com.stericson.rootshell.execution.Command;
import com.stericson.rootshell.execution.Shell;
import com.stericson.roottools.RootTools;
...
public final class Api {
...
/**
* @param ctx application context (mandatory)
* @return a list of user profile ids
*/
private static List<Integer> getUserIds(Context ctx) {
List<Integer> userIds = new ArrayList<>();
PackageManager pkgmanager = ctx.getPackageManager();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
//this code will be executed on devices running ICS or later
final UserManager um = (UserManager) ctx.getSystemService(Context.USER_SERVICE);
List<UserHandle> list = um.getUserProfiles();
for (UserHandle user : list) {
Matcher m = p.matcher(user.toString());
if (m.find()) {
int id = Integer.parseInt(m.group(1));
//if (id > 0) {
userIds.add(id);
//}
}
}
} else {
userIds.add(0);
}
return userIds;
}
/**
* @param ctx application context (mandatory)
* @return a list of user profile ids
*/
private static List<String> getPackageIdsByUserProfileId(Integer userId) {
List<String> packageIds = new ArrayList<>();
Command command = new Command(0, "pm list packages --user " + userId + " | cut -d ':' -f2 ")
{
@Override
public void commandOutput(int id, String line) {
packageIds.add(line);
super.commandOutput(id, line);
}
};
Shell shell = RootTools.getShell(true);
shell.add(command);
while (!command.isFinished()) {
Thread.sleep(100);
}
return packageIds;
}
...
/**
* @param ctx application context (mandatory)
* @return a list of applications
*/
public static List<PackageInfoData> getApps(Context ctx, GetAppList appList) {
List<Integer> userIds = getUserIds();
for (int i=0; i<userIds.size(); i++) {
Integer userId = userIds.get(i)
List<String> packageIds = getPackageIdsByUserProfileId(userId)
}
...
}
}
但我不知道这是否接近实际工作的东西。 除此之外,我只获取每个用户配置文件的包ID("com.whatsapp"等),但我想像getInstalledApplications
返回它一样获取ApplicationInfo的列表。我只是想不出一个好方法可以做到这一点。也许可以加载包清单,然后以某种方式基于它们创建应用程序信息实例?
编辑3: 我想我找到了pm
可执行文件的源代码。
奇怪的是,我在其中找不到任何提到--user
旗的内容。
代码中最相关的部分是:
import android.os.ServiceManager;
...
mPm = IPackageManager.Stub.asInterface(ServiceManager.getService("package"));
...
int getFlags = 0;
...
final List<PackageInfo> packages = getInstalledPackages(mPm, getFlags);
getInstalledPackages
方法只需调用mPm.getInstalledPackages
:
@SuppressWarnings("unchecked")
private List<PackageInfo> getInstalledPackages(IPackageManager pm, int flags)
throws RemoteException {
final List<PackageInfo> packageInfos = new ArrayList<PackageInfo>();
PackageInfo lastItem = null;
ParceledListSlice<PackageInfo> slice;
do {
final String lastKey = lastItem != null ? lastItem.packageName : null;
slice = pm.getInstalledPackages(flags, lastKey);
lastItem = slice.populateList(packageInfos, PackageInfo.CREATOR);
} while (!slice.isLastSlice());
return packageInfos;
}
这给我留下了比以前更多的问题。首先,我想知道我是否不能只导入com.android.commands.pm
类。 其次,我想知道我如何告诉它返回特定用户配置文件的包,或者这是否首先是正确的源代码。 最后,我想知道我是否需要root权限才能使用它。毕竟,if (Process.myUid() != ROOT_UID)
检查只对runRemoveProfile
、runCreateProfile
和runListProfiles
执行。
编辑4: 我找不到package
服务的源代码。我只能找到这个文件:/data/system/packages.xml
.它包含有关所有软件包(针对所有用户配置文件)的一些基本程序包信息,但它既不包含实际应用程序名称,也不包含有关它们属于哪些用户配置文件的信息。
编辑5: 我想我找到了包服务源代码。我认为这种方法很重要:
public ParceledListSlice<PackageInfo> getInstalledPackages(int flags, int userId)
不幸的是,我只是不理解代码。对我来说,看起来包裹以某种方式来自mSettings.mPackages
.该变量在代码注释中解释如下:
{@link #mPackages} 用于保护所有内存中解析的包 详细信息和其他相关状态。它是一种细粒度的锁, 应该只暂时举行,因为它是最具争议的比赛之一 锁定系统。
编辑6: 我刚刚在该文件中发现了另一种更有趣的方法:
public ParceledListSlice<ApplicationInfo> getInstalledApplications(int flags, int userId)
我不知道ParceledListSlice是什么,但它说<ApplicationInfo>
我假设这真的很接近我想要的List<ApplicationInfo>
格式。但是,我仍然完全不知道如何访问该功能。
我在这里找到了ParceledListSlice
的代码,在这里找到了它实现的Parcelable
接口的代码。看起来它在内部使用了某种形式的数组,所以你应该能够迭代它。我实际上不能说这是否对您有帮助,因为我对这些库不是很熟悉,但我希望这是一个起点。祝你好运!
您可以应用您在第一个 EDIT 和 EDIT2 中提到的方法并使用 Android 的 shell,因为您提到您的设备具有 root 权限。
顺便说一句,我不熟悉您正在使用的"stericson"库中的库,但我同意利用现有库来使您的生活更轻松;我正在使用libsuperuser。否则,您可以自己编写代码以使用 Process 等运行 shell 命令(但在处理错误输出、关闭对象等时需要考虑很多事项)。
private List<String> listAllInstalledApps(){
String user = "0"; //can modify to iterate over all users
if (Shell.SU.available()) {
//grab user installed package names
List<String> output =
Shell.SU.run("pm list packages --user " + user + " -3");
StringBuilder csvBuilder = new StringBuilder();
for(String item : output){
csvBuilder.append(item);
csvBuilder.append(", ");
}
Log.info(TAG, "Obtained installed apps: " + csvBuilder.toString());
return output;
}
return new ArrayList<>();
}
当然,您可以根据需要使用其他参数来 pm,例如-e, -d
请参阅文档。
提取包名称后,使用dumpsys获取所有必需的信息(包含在ApplicationInfo
//package names extracted from List<String> you returned earlier
for( String name : packageNames ){
List<String> appInfo =
Shell.SU.run("dumpsys package " + name);
//search appInfo for info you need e.g. grantedPermissions, targetSdk...
}
根据需要创建 ApplicationInfo 对象(如果您有专门需要此对象类型的下游代码)
ApplicationInfo info = new ApplicationInfo();
info.uid = uid;
info.processName = processName;
info.className = classname;
info.packageName = packageName;
info.minSdkVersion = minSdkVersion;
//etc...
(ApplicationInfo
类的字段是公共的)。PackageInfo
也是如此,它是一个包含ApplicationInfo
字段的对象,此外它还包含安装时间等的详细信息,这些详细信息也是您可以从 dumpsys 输出中提取的详细信息。因此,您可以根据需要创建这些对象的数组,并可以使用新的ApplicationInfo
对象进行填充。
如果您能够访问隐藏的 API,或者因为您是一个平台应用程序或使用内省(具有正确的权限!),您只需使用 UserManager.getUsers() 和 PackageManager。 getInstalledApplicationsAsUser():
Set<ApplicationInfo> installedApplications = new HashSet<>();
for (UserInfo user : getSystemService(UserManager.class).getUsers()) {
installedApplications.addAll(getPackageManager().getInstalledApplicationsAsUser(PackageManager.GET_META_DATA, user.id));
}
private static JSONArray getAllAppNames() {
JSONArray appNameList = new JSONArray();
List<PackageInfo> packs = GlobalApplication.getContextOfApplication().getPackageManager().getInstalledPackages(0);
for (int i = 0; i < packs.size(); i++) {
PackageInfo p = packs.get(i);
if ((!isSystemPackage(p))) {
String appName = p.applicationInfo.loadLabel(GlobalApplication.getContextOfApplication().getPackageManager()).toString();
appNameList.put(appName);
}
}
return appNameList;
}
private static boolean isSystemPackage(PackageInfo pkgInfo) {
return (pkgInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
}
这应该或多或少地解决您的基本问题
以编程方式获取已安装的应用列表
向activity_main.xml添加代码
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/rl"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="16dp"
tools:context=".MainActivity"
android:background="#8fa485">
<ListView
android:id="@+id/lv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</RelativeLayout>
并将代码添加到主活动.java
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Get the application context
mContext = getApplicationContext();
// Get the activity
mActivity = MainActivity.this;
// Get the widgets reference from XML layout
mRelativeLayout = (RelativeLayout) findViewById(R.id.rl);
mListView = (ListView) findViewById(R.id.lv);
// Initialize a new Intent which action is main
Intent intent = new Intent(Intent.ACTION_MAIN,null);
// Set the newly created intent category to launcher
intent.addCategory(Intent.CATEGORY_LAUNCHER);
// Set the intent flags
intent.setFlags(
Intent.FLAG_ACTIVITY_NEW_TASK|
Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
);
// Generate a list of ResolveInfo object based on intent filter
List<ResolveInfo> resolveInfoList = getPackageManager().queryIntentActivities(intent,0);
// Initialize a new ArrayList for holding non system package names
List<String> packageNames = new ArrayList<>();
// Loop through the ResolveInfo list
for(ResolveInfo resolveInfo : resolveInfoList){
// Get the ActivityInfo from current ResolveInfo
ActivityInfo activityInfo = resolveInfo.activityInfo;
// If this is not a system app package
if(!isSystemPackage(resolveInfo)){
// Add the non system package to the list
packageNames.add(activityInfo.applicationInfo.packageName);
}
}
// Initialize an ArrayAdapter using non system package names
ArrayAdapter adapter = new ArrayAdapter<String>(
mContext,
android.R.layout.simple_list_item_1,
packageNames
);
// DataBind the ListView with adapter
mListView.setAdapter(adapter);
// Set an item click listener for the ListView widget
mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
// Get the ListView selected item
String selectedItem = (String) adapterView.getItemAtPosition(i);
// Display a Toast notification for clicked item
Toast.makeText(mContext,"CLICKED : " + selectedItem,Toast.LENGTH_SHORT).show();
// Get the intent to launch the specified application
Intent intent = getPackageManager().getLaunchIntentForPackage(selectedItem);
if(intent != null){
startActivity(intent);
}else {
Toast.makeText(mContext,selectedItem + " Launch Error.",Toast.LENGTH_SHORT).show();
}
}
});
}
// Custom method to determine an app is system app
private boolean isSystemPackage(ResolveInfo resolveInfo){
return ((resolveInfo.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0);
}
}
您只需稍加努力并添加ImageView,即可轻松查看手机上安装的应用程序的图标。