前言
因为项目中有sticker键盘功能,而在安卓应用中想要发送键盘中的sticker只能通过分享的方式(iOS可以将图片放在粘贴板上),而直接调用系统的分享面板比较鸡肋,用户选择一个sticker后还要选择分享的途径。参考其他键盘产品,可以在分享的时候进行筛选,比如,在微信应用中打开了键盘点击sticker的时候只弹出 发送到朋友圈发送给朋友和添加到收藏,在短信中打开就直接转换为彩信。这样可用性就提高了很多。
实现方法就是在用intent调用系统分享的时候加上intent.setPackage(packName);
,packName为当前打开键盘的应用包名,那么如何获取到当前手机内运行在最上面的进程包名就成了问题关键。
Android自从Lollipo以泄露用户信息的理由彻底禁止了getRunningTasks方法之后,世道就变得艰难起来,StackOverFlow上曾经大张旗鼓的讨论此问题,很多人使用ActivityManager.RunningAppProcessInfo方法来获取顶层app,这个方法似乎在某个版本中是有效的。但是幸福总是短暂的,到了Android6.0版本,即Marshmallow(api level 23)时,这些方法统统的废了,除了自己app中的信息外,只能获取启动器的信息。道理是很简单的,Marshmallow以权限严格著称,因此对于这种可能泄露其他应用信息的方法一概禁止了。
解决方法 UsageStatsManager
UsageStatsManager是用来统计app使用情况的类,直到Lollipop(api level 21)才加入Android。此类的具体用法可以参考:
UsageStatsManager
-
使用
-
1:修改AndroidManifest.xml,添加权限
<!--注意这里:添加权限--> <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" tools:ignore="ProtectedPermissions"/>
-
-
2:检测并引导用户开启权限
//检测用户是否对本app开启了“Apps with usage access”权限
private boolean hasPermission() {
AppOpsManager appOps = (AppOpsManager)
getSystemService(Context.APP_OPS_SERVICE);
int mode = 0;
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT) {
mode = appOps.checkOpNoThrow(AppOpsManager.OPSTR_GET_USAGE_STATS,
android.os.Process.myUid(), getPackageName());
}
return mode == AppOpsManager.MODE_ALLOWED;
}
引导用户去设置中开启权限
private static final int MY_PERMISSIONS_REQUEST_PACKAGE_USAGE_STATS = 1101;
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == MY_PERMISSIONS_REQUEST_PACKAGE_USAGE_STATS) {
if (!hasPermission()) {
//若用户未开启权限,则引导用户开启“Apps with usage access”权限
startActivityForResult(
new Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS),
MY_PERMISSIONS_REQUEST_PACKAGE_USAGE_STATS);
}
}
}
- 3:使用UsageStatsManager来获取当前运行的app
private String printForegroundTask(Context context) {
String packagename = "";
// if the sdk >= 21. It can only use getRunningAppProcesses to get task top package name
UsageStatsManager usm = (UsageStatsManager) getSystemService("usagestats");
long time = System.currentTimeMillis();
List<UsageStats> appList = usm.queryUsageStats(UsageStatsManager.INTERVAL_DAILY,
time - 1000 * 1000, time);
if (appList != null && appList.size() > 0) {
SortedMap<Long, UsageStats> mySortedMap = new TreeMap<Long, UsageStats>();
for (UsageStats usageStats : appList) {
mySortedMap.put(usageStats.getLastTimeUsed(),
usageStats);
}
if (mySortedMap != null && !mySortedMap.isEmpty()) {
packagename = mySortedMap.get(
mySortedMap.lastKey()).getPackageName();
}
} else {// if sdk <= 20, can use getRunningTasks
ActivityManager am = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
//4.获取正在开启应用的任务栈
List<ActivityManager.RunningTaskInfo> runningTasks = am.getRunningTasks(1);
ActivityManager.RunningTaskInfo runningTaskInfo = runningTasks.get(0);
//5.获取栈顶的activity,然后在获取此activity所在应用的包名
packagename = runningTaskInfo.topActivity.getPackageName();
}
return packagename;
}
至此我们就能在安卓5.0之后拿到正在运行的进程名了
But 别的键盘类应用并没有申请过这个权限,而且一个对于开发者来说都不熟悉的系统级权限,用户就更不懂了,那么肯定是实现思路出了问题,仔细研读Android中的IME的API发现了一个方法
@Override
public void onStartInput(EditorInfo attribute, boolean restarting) {
super.onStartInput(attribute, restarting);
packName = attribute.packageName;
Log.e(TAG, "onStartInput: "+packName );
}
在onStartInput方法中就能直接拿到当前进程名。至此键盘分享的问题就解决了。
更多Android中的IME生命周期问题参见https://blog.csdn.net/wyhuiwyhui/article/details/8074691
网友评论