我有一个 Android 应用程序,它扫描设备上安装的所有应用程序,然后将其报告给服务器(它是 MDM 代理)。有关如何获取应用程序类别的任何建议?每个人都有不同的类别列表,但基本上是游戏、娱乐、工具/实用程序等。
据我所知,与设备本身存储的类别无关。我正在考虑使用 android market API 在市场中搜索应用程序并使用搜索返回的 Category 值。不确定这将如何成功找到匹配项。关于如何最好地做到这一点的任何建议?
关于不同方法的任何建议?
提前致谢。麦克风
我有一个 Android 应用程序,它扫描设备上安装的所有应用程序,然后将其报告给服务器(它是 MDM 代理)。有关如何获取应用程序类别的任何建议?每个人都有不同的类别列表,但基本上是游戏、娱乐、工具/实用程序等。
据我所知,与设备本身存储的类别无关。我正在考虑使用 android market API 在市场中搜索应用程序并使用搜索返回的 Category 值。不确定这将如何成功找到匹配项。关于如何最好地做到这一点的任何建议?
关于不同方法的任何建议?
提前致谢。麦克风
我知道这是一篇旧帖子,但对于仍在寻找此内容的任何人,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)
// ...
}
如果您为每个应用程序获取其包名称,则可以直接询问播放商店应用程序所属的类别,并使用此库解析 html 响应页面:
这是解决您问题的代码段:
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 或服务中进行这些查询。希望对您有所帮助。
有关如何获取应用程序类别的任何建议?
应用程序没有类别。一些市场有类别。
据我所知,与设备本身存储的类别无关。
正确的。
我正在考虑使用 android market API 在市场中搜索应用程序并使用搜索返回的 Category 值。
目前没有“android market API”,除非你有一个好的法律团队和大量的法律辩护基金。
此外,并非所有应用程序都通过“android 市场”分发,尤其是在 Kindle Fire 等设备上。此外,欢迎开发人员随时更改类别,至少在 Google Play 商店中是这样。
关于如何最好地做到这一点的任何建议?
放弃该功能并继续使用其他方式来增加价值。
我也面临同样的问题。上述查询的解决方案如下所述。
首先,下载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());
}
}
}
作为回报,您将获得应用程序公司名称和应用程序类别
我根据@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
}
}
}
可能有点晚了,但问题仍然存在。OP 具有将这些结果发送到 API 的优势(这里我假设 API 至少由 OP 或他的 API 同事管理)。
因此,对于有类似问题的任何人,我建议以下内容:
需要考虑的事项:
当多个用户尝试查询您的 API 以获取相同的包名称并且您的缓存/数据库中没有该包的类别时...执行 1 次请求“市场 API”并在您的缓存/数据库中packagex更新为“packagex等待结果”状态 - 下一个请求应该获得“等待结果”或“市场 API”返回的结果。
人们还应该考虑为可能的“市场 API”失败(市场 API 不起作用,不是 google play 应用程序,或类似的东西)的后备。这个决定基本上与你的领域相关,你试图抓住的商业趋势将迫使你做出关于这个的决定。如果您真的想整理此类内容,则可以将此回退到人为决策的管道上,并packagex相应地更新您的 API 数据库/缓存。
建立一个很好的 API 可以优雅地处理这些和类似的场景,然后甚至可以将它商业化到一定程度和“市场 API 端点”——AKA 商店包详细信息页面。该页面将失去大部分虚假用户:)
您可以使用下面的 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 '&' with '&' in category Text
if (appCategoryType.contains("&")) {
appCategoryType = appCategoryType.replace("&", " & ");
}
//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