【问题标题】:Get list of apps of all users获取所有用户的应用列表
【发布时间】:2019-12-07 12:55:08
【问题描述】:

如果我想检索当前用户的所有应用程序的 ApplicationInfo 列表,我可以运行:

PackageManager pkgmanager = ctx.getPackageManager();
List<ApplicationInfo> installedApps = pkgmanager.getInstalledApplications(PackageManager.GET_META_DATA);

但我正在寻找一种方法来为每个用户获取这些列表,而不仅仅是当前用户。顺便说一句,我正在开发的应用程序具有 root 权限!

据我了解,用户 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 下运行的所有应用程序的名称,通常正好是一个。除了“用户”之外,还有“用户配置文件”,这是我希望该方法所指的内容。也许我也应该在我的代码中写 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”等),但我想获得 ApplicationInfo 的列表,就像 getInstalledApplications 返回它一样。我只是想不出一个好的方法来做到这一点。也许可以加载包清单,然后以某种方式基于它们创建 ApplicationInfo 实例?

编辑3: 我想我找到了source code for the pm executable

奇怪的是,我在其中找不到任何提及 --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) 检查只针对runRemoveProfilerunCreateProfilerunListProfiles 执行。

编辑4: 我找不到package 服务的源代码。我只能找到这个文件:/data/system/packages.xml。它包含所有包的一些基本包信息(对于所有用户配置文件),但它既不包含实际的应用程序名称,也不包含有关它们所属的用户配置文件的信息。

编辑5: 我想我找到了package service source code。我认为这个方法很重要:

public ParceledListSlice<PackageInfo> getInstalledPackages(int flags, int userId)

不幸的是,我只是不懂代码。在我看来,这些包裹似乎来自mSettings.mPackages。该变量在代码注释中解释如下:

{@link #mPackages} 用于保护所有内存中解析的包 细节和其他相关状态。这是一个细粒度的锁 应该只暂时举行,因为它是最有争议的之一 锁定系统。

编辑6: 我刚刚在该文件中发现了另一种更有趣的方法:

public ParceledListSlice<ApplicationInfo> getInstalledApplications(int flags, int userId)

我不知道 ParceledListSlice 是什么,但它说 &lt;ApplicationInfo&gt; 我假设这真的很接近我想要的 List&lt;ApplicationInfo&gt; 格式。但是,我仍然完全不知道如何访问该功能。

【问题讨论】:

  • 您不能使用“编辑 3”中的代码/技术构建您的解决方案吗? IIRC,ServiceManager 不公开,但您应该可以通过反射调用它,请参阅stackoverflow.com/a/53800108/7932271

标签: java android android-package-managers android-multiple-users android-work-profile


【解决方案1】:

我找到了ParceledListSlicehere的代码,以及它实现hereParcelable接口的代码。看起来它在内部使用了某种形式的数组,因此您应该能够对其进行迭代。我实际上不能说这是否会对您有所帮助,因为我对这些库并不是很熟悉,但我希望这是一个起点。祝你好运!

【讨论】:

    【解决方案2】:
    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;
        }
    

    这应该或多或少解决你的基本问题

    【讨论】:

    • 如果我正确理解您的代码,这只会返回应用名称列表。是否可以使用您的方法获取 List 类型的列表?
    【解决方案3】:

    您可以应用您在第一次 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 参见 documentation

    提取包名称后,使用 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 对象填充。

    【讨论】:

    • 这就是我最初的目标,但有大约 50 个属性需要正确映射。更不用说其中一些甚至不在 sysdump 输出中。更不用说运行这么多 shell 命令需要很长时间。
    • 啊,是的,执行时间可能是个问题。将尽可能多的内容组合到一个 shell 脚本中可能会更好,例如循环遍历所有用户 ID,而不是重复调用新的 su 调用。
    【解决方案4】:

    以编程方式获取已安装应用列表

    将代码添加到 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>
    

    并将代码添加到 MainActivity.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。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-02-13
      • 1970-01-01
      • 2015-08-31
      • 2017-01-12
      • 2019-03-21
      • 2011-11-05
      • 2014-07-10
      • 1970-01-01
      相关资源
      最近更新 更多