10

我有一个 Android 应用程序,它扫描设备上安装的所有应用程序,然后将其报告给服务器(它是 MDM 代理)。有关如何获取应用程序类别的任何建议?每个人都有不同的类别列表,但基本上是游戏、娱乐、工具/实用程序等。

据我所知,与设备本身存储的类别无关。我正在考虑使用 android market API 在市场中搜索应用程序并使用搜索返回的 Category 值。不确定这将如何成功找到匹配项。关于如何最好地做到这一点的任何建议?

关于不同方法的任何建议?

提前致谢。麦克风

4

7 回答 7

9

我知道这是一篇旧帖子,但对于仍在寻找此内容的任何人,API 级别 26 (O) 已将类别添加到android.content.pm.ApplicationInfo.

从文档https://developer.android.com/reference/android/content/pm/ApplicationInfo#category

public int category

此应用程序的类别。类别用于将多个应用程序聚集到有意义的组中,例如在汇总电池、网络或磁盘使用情况时。应用程序应仅在它们完全适合特定类别之一时定义此值。

从清单中的R.attr.appCategory属性设置。如果清单未定义类别,则此值可能已由安装程序通过PackageManager#setApplicationCategoryHint(String, int)提供。值为CATEGORY_UNDEFINED, CATEGORY_GAME, CATEGORY_AUDIO, CATEGORY_VIDEO, CATEGORY_IMAGE, CATEGORY_SOCIAL, CATEGORY_NEWS, CATEGORY_MAPS, 或CATEGORY_PRODUCTIVITY

现在可以执行以下操作:

PackageManager pm = context.getPackageManager();
ApplicationInfo applicationInfo = pm.getApplicationInfo(packageName, 0);
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
    int appCategory = applicationInfo.category;
    String categoryTitle = (String) ApplicationInfo.getCategoryTitle(context, appCategory)
    // ...
}
于 2020-05-14T15:28:16.560 回答
9

如果您为每个应用程序获取其包名称,则可以直接询问播放商店应用程序所属的类别,并使用此库解析 html 响应页面:

org.jsoup.jsoup1.8.3

这是解决您问题的代码段:

public class MainActivity extends AppCompatActivity {

public final static String GOOGLE_URL = "https://play.google.com/store/apps/details?id=";
public static final String ERROR = "error";

...


   private class FetchCategoryTask extends AsyncTask<Void, Void, Void> {

       private final String TAG = FetchCategoryTask.class.getSimpleName();
       private PackageManager pm;
       private ActivityUtil mActivityUtil;

       @Override
       protected Void doInBackground(Void... errors) {
          String category;
           pm = getPackageManager();
           List<ApplicationInfo> packages = pm.getInstalledApplications(PackageManager.GET_META_DATA);
           Iterator<ApplicationInfo> iterator = packages.iterator();
           while (iterator.hasNext()) {
               ApplicationInfo packageInfo = iterator.next();
               String query_url = GOOGLE_URL + packageInfo.packageName;
               Log.i(TAG, query_url);
               category = getCategory(query_url);
               // store category or do something else
           }
           return null;
        }


        private String getCategory(String query_url) {
           boolean network = mActivityUtil.isNetworkAvailable();
           if (!network) {
               //manage connectivity lost
               return ERROR;
           } else {
               try {
                   Document doc = Jsoup.connect(query_url).get();
                   Element link = doc.select("span[itemprop=genre]").first();
                   return link.text();
               } catch (Exception e) {
                   return ERROR;
               }
           }
        }
    }  
}

您可以在 AsyncTask 或服务中进行这些查询。希望对您有所帮助。

于 2016-03-09T21:39:13.773 回答
6

有关如何获取应用程序类别的任何建议?

应用程序没有类别。一些市场有类别。

据我所知,与设备本身存储的类别无关。

正确的。

我正在考虑使用 android market API 在市场中搜索应用程序并使用搜索返回的 Category 值。

目前没有“android market API”,除非你有一个好的法律团队和大量的法律辩护基金。

此外,并非所有应用程序都通过“android 市场”分发,尤其是在 Kindle Fire 等设备上。此外,欢迎开发人员随时更改类别,至少在 Google Play 商店中是这样。

关于如何最好地做到这一点的任何建议?

放弃该功能并继续使用其他方式来增加价值。

于 2012-05-23T00:16:22.223 回答
4

我也面临同样的问题。上述查询的解决方案如下所述。

首先,下载Jsoup库或下载jar文件。

或将此添加到您的build.gradle(模块:应用程序)implementation 'org.jsoup:jsoup:1.11.3'

private class FetchCategoryTask extends AsyncTask<Void, Void, Void> {

    private final String TAG = FetchCategoryTask.class.getSimpleName();
    private PackageManager pm;
    //private ActivityUtil mActivityUtil;

    @Override
    protected Void doInBackground(Void... errors) {
        String category;
        pm = getPackageManager();
        List<ApplicationInfo> packages = pm.getInstalledApplications(PackageManager.GET_META_DATA);
        Iterator<ApplicationInfo> iterator = packages.iterator();
        //  while (iterator.hasNext()) {
        // ApplicationInfo packageInfo = iterator.next();
        String query_url = "https://play.google.com/store/apps/details?id=com.imo.android.imoim";  //GOOGLE_URL + packageInfo.packageName;
        Log.i(TAG, query_url);
        category = getCategory(query_url);
        Log.e("CATEGORY", category);

        // store category or do something else
        //}
        return null;
    }


    private String getCategory(String query_url) {

        try {
             Document doc = Jsoup.connect(query_url).get();
            Elements link = doc.select("a[class=\"hrTbp R8zArc\"]");
               return link.text();
        } catch (Exception e) {
            Log.e("DOc", e.toString());
        }
    }

}

作为回报,您将获得应用程序公司名称和应用程序类别

于 2018-12-18T03:24:52.597 回答
3

我根据@Ankit Kumar Singh 的回答制作了一个 Kotlin 解决方案。
此解决方案将类别映射到枚举,以防您想做其他事情而不仅仅是显示它。

import kotlinx.coroutines.*
import org.jsoup.Jsoup
import javax.inject.Inject
import javax.inject.Singleton

class AppCategoryService {
    companion object {
        private const val APP_URL = "https://play.google.com/store/apps/details?id="
        private const val CAT_SIZE = 9
        private const val CATEGORY_STRING = "category/"
    }

    suspend fun fetchCategory(packageName: String): AppCategory {
        val url = "$APP_URL$packageName&hl=en" //https://play.google.com/store/apps/details?id=com.example.app&hl=en
        val categoryRaw = parseAndExtractCategory(url) ?: return AppCategory.OTHER
        return AppCategory.fromCategoryName(categoryRaw)
    }

    @Suppress("BlockingMethodInNonBlockingContext")
    private suspend fun parseAndExtractCategory(url: String): String? = withContext(Dispatchers.IO) {
        return@withContext try {
            val text = Jsoup.connect(url).get()?.select("a[itemprop=genre]") ?: return@withContext null
            val href = text.attr("abs:href")

            if (href != null && href.length > 4 && href.contains(CATEGORY_STRING)) {
                getCategoryTypeByHref(href)
            } else {
                null
            }
        } catch (e: Throwable) {
            null
        }
    }

    private fun getCategoryTypeByHref(href: String) = href.substring(href.indexOf(CATEGORY_STRING) + CAT_SIZE, href.length)
}

这是包含此时所有可能值的枚举:

// Note: Enum name matches API value and should not be changed
enum class AppCategory {
    OTHER,
    ART_AND_DESIGN,
    AUTO_AND_VEHICLES,
    BEAUTY,
    BOOKS_AND_REFERENCE,
    BUSINESS,
    COMICS,
    COMMUNICATION,
    DATING,
    EDUCATION,
    ENTERTAINMENT,
    EVENTS,
    FINANCE,
    FOOD_AND_DRINK,
    HEALTH_AND_FITNESS,
    HOUSE_AND_HOME,
    LIBRARIES_AND_DEMO,
    LIFESTYLE,
    MAPS_AND_NAVIGATION,
    MEDICAL,
    MUSIC_AND_AUDIO,
    NEWS_AND_MAGAZINES,
    PARENTING,
    PERSONALIZATION,
    PHOTOGRAPHY,
    PRODUCTIVITY,
    SHOPPING,
    SOCIAL,
    SPORTS,
    TOOLS,
    TRAVEL_AND_LOCAL,
    VIDEO_PLAYERS,
    WEATHER,
    GAMES;

    companion object {
        private val map = values().associateBy(AppCategory::name)
        private const val CATEGORY_GAME_STRING = "GAME_" // All games start with this prefix

        fun fromCategoryName(name: String): AppCategory {
            if (name.contains(CATEGORY_GAME_STRING)) return GAMES
            return map[name.toUpperCase(Locale.ROOT)] ?: OTHER
        }
    }
}
于 2018-09-24T17:42:55.187 回答
0

可能有点晚了,但问题仍然存在。OP 具有将这些结果发送到 API 的优势(这里我假设 API 至少由 OP 或他的 API 同事管理)。

因此,对于有类似问题的任何人,我建议以下内容:

  1. 从设备中收集您感兴趣的所有包名称。
  2. 将该数据发送到您的API
  3. API 应该提取包名并尝试从其缓存/数据库中读取结果...
  4. 对于那些在 cache/db 中不存在的包,进行“market API”调用并提取类别 - 将其保存到 db/cache 以在此迭代中重用。
  5. 当所有请求(缓存/数据库和市场 API)都完成后,对结果做任何你喜欢的事情。

需要考虑的事项:

当多个用户尝试查询您的 API 以获取相同的包名称并且您的缓存/数据库中没有该包的类别时...执行 1 次请求“市场 API”并在您的缓存/数据库中packagex更新为“packagex等待结果”状态 - 下一个请求应该获得“等待结果”或“市场 API”返回的结果。

人们还应该考虑为可能的“市场 API”失败(市场 API 不起作用,不是 google play 应用程序,或类似的东西)的后备。这个决定基本上与你的领域相关,你试图抓住的商业趋势将迫使你做出关于这个的决定。如果您真的想整理此类内容,则可以将此回退到人为决策的管道上,并packagex相应地更新您的 API 数据库/缓存。

建立一个很好的 API 可以优雅地处理这些和类似的场景,然后甚至可以将它商业化到一定程度和“市场 API 端点”——AKA 商店包详细信息页面。该页面将失去大部分虚假用户:)

于 2018-10-29T21:20:10.090 回答
0

您可以使用下面的 AsyncTask 使用应用程序包 ID 从 playStore 中提取 Android 应用程序类别。

import android.content.Context;
import android.content.pm.PackageManager;
import android.os.AsyncTask;
import android.util.Log;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import java.io.IOException;

public class GetAppCategory extends AsyncTask<String, Void, String> {

    //Main URL for each app on Play Store
    public static final String APP_URL = "https://play.google.com/store/apps/details?id=";

    //Use below String if extracting 'CATEGORY' from href tag.
    private final String CATEGORY_STRING = "category/";

    private final int cat_size = 9;

    /*Use below String for identified 'GAME' apps, which must start with this prefix.
    Here I only display 'Games' as category for all Games app instead of showing their subCategory also*/
    private final String CATEGORY_GAME_STRING = "GAME_";

    //Name of the app package for which you want to get category.
    private String packageName = null;

    private PackageManager pm = null;

    //Activity or Application context as per requirement.
    private Context appContext;

    /* You can add default system app OR non play store app package name here as comma seprated for ignore them
     and set their category directly 'Others' OR anythings you wish. */
    private final String extractionApps = "com.android.providers.downloads.ui, com.android.contacts," +
            " com.android.gallery3d, com.android.vending, com.android.calculator2, com.android.calculator," +
            " com.android.deskclock, com.android.messaging, com.android.settings, com.android.stk";

    //Class level TAG, use for Logging.
    private final String TAG = "GetAppCategory";

    /**
     * @param packageName: package name of the app, you want to extract category.
     * @param appContext:  Activity/Application level Context ap per requirement.
     */
    public GetAppCategory(String packageName, Context appContext) {
        this.packageName = packageName;
        this.appContext = appContext;
    }

    @Override
    protected String doInBackground(String... params) {
        try {

            pm = appContext.getPackageManager();

            if (packageName != null && packageName.length() > 1) {

                if (packageName.contains("package:")) {
                    packageName = packageName.replace("package:", "");
                }
                /**
                 * Mathod used for parse play store html page and extract category from their.
                 */
                String appCategoryType = parseAndExtractCategory(packageName);

                Log.i(TAG, "package :" + packageName);
                Log.i(TAG, "APP_CATEGORY: " + appCategoryType);
            }

        } catch (Exception e) {
            //TODO:: Handle Exception
            e.printStackTrace();
        } finally {
            //TODO::
        }
        return null;
    }

    @Override
    protected void onPostExecute(String result) {

    }


    /**
     * @param packageName
     * @return
     */

    private String parseAndExtractCategory(String packageName) {

        //You can pass hl={language_code} for get category in some other langauage also other than English.
        //String url = APP_URL + packageName + "&hl=" + appContext.getString(R.string.app_lang);

        String url = APP_URL + packageName + "&hl=en"; //{https://play.google.com/store/apps/details?id=com.example.app&hl=en}
        String appCategoryType = null;
        String appName = null;

        try {

            if (!extractionApps.contains(packageName)) {
                Document doc = null;
                try {
                    doc = Jsoup.connect(url).get();

                    if (doc != null) {

                        //TODO: START_METHOD_1
                        //Extract category String from a <anchor> tag value directly.
                        //NOTE: its return sub category text, for apps with multiple sub category.
                        //Comment this method {METHOD_1}, if you wish to extract category by href value.
                        Element CATEGORY_SUB_CATEGORY = doc.select("a[itemprop=genre]").first();
                        if (CATEGORY_SUB_CATEGORY != null) {
                            appCategoryType = CATEGORY_SUB_CATEGORY.text();
                        }
                        //TODO: END_METHOD_1

                        //TODO: START_METHOD_2
                        // Use below code only if you wist to extract category by href value.
                        //Its return parent or Main Category Text for all app.
                        //Comment this method {METHOD_2}, If you wihs to extract category from a<anchor> value.
                        if (appCategoryType == null || appCategoryType.length() < 1) {
                            Elements text = doc.select("a[itemprop=genre]");

                            if (text != null) {
                                if (appCategoryType == null || appCategoryType.length() < 2) {
                                    String href = text.attr("abs:href");
                                    if (href != null && href.length() > 4 && href.contains(CATEGORY_STRING)) {
                                        appCategoryType = getCategoryTypeByHref(href);
                                    }
                                }
                            }
                        }
                        //TODO: END_METHOD_2

                        if (appCategoryType != null && appCategoryType.length() > 1) {
                            /**
                             * Ger formatted category String by removing special character.
                             */
                            appCategoryType = replaceSpecialCharacter(appCategoryType);
                        }

                    }
                } catch (IOException e) {
                    //appCategoryType = appContext.getString(R.string.category_others);
                    appCategoryType = "OTHERS";
                    //TODO:: Handle Exception
                    e.printStackTrace();
                }
            } else {
                //appCategoryType = appContext.getString(R.string.category_others);
                appCategoryType = "OTHERS";
            }
        } catch (Exception e) {
            //TODO:: Handle Exception
            e.printStackTrace();
        }
        return appCategoryType;
    }

    /**
     * @param href
     * @return
     */
    private String getCategoryTypeByHref(String href) {
        String appCategoryType = null;
        try {
            appCategoryType = href.substring((href.indexOf(CATEGORY_STRING) + cat_size), href.length());
            if (appCategoryType != null && appCategoryType.length() > 1) {
                if (appCategoryType.contains(CATEGORY_GAME_STRING)) {
                    //appCategoryType = appContext.getString(R.string.games);
                    appCategoryType = "GAMES";
                }
            }
        } catch (Exception e) {
            //TODO:: Handle Exception
            e.printStackTrace();
        }
        return appCategoryType;
    }

    /**
     * @param appCategoryType
     * @return: formatted String
     */
    private String replaceSpecialCharacter(String appCategoryType) {
        try {
            //Find and Replace '&amp;' with '&' in category Text
            if (appCategoryType.contains("&amp;")) {
                appCategoryType = appCategoryType.replace("&amp;", " & ");
            }

            //Find and Replace '_AND_' with ' & ' in category Text
            if (appCategoryType.contains("_AND_")) {
                appCategoryType = appCategoryType.replace("_AND_", " & ");
            }

            //Find and Replace '_' with ' ' <space> in category Text
            if (appCategoryType.contains("_")) {
                appCategoryType = appCategoryType.replace("_", " ");
            }
        } catch (Exception e) {
            //TODO:: Handle Exception
            e.printStackTrace();
        }
        return appCategoryType;
    }
}

它需要 jsoup 库来解析 html 页面。你可以在这里找到它org.jsoup.jsoup1.11.1

于 2018-05-29T12:07:10.990 回答