我有一个 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