1、 悬浮窗的基本操作
- 1) 创建悬浮窗
WindowManager windowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
if (floatView == null) {
//设置悬浮窗的ui
View floatView = LayoutInflater.from(this).inflate(R.layout.float_window_text, null);
//设置悬浮窗的参数
params = new WindowManager.LayoutParams();
//使用 TYPE_SYSTEM_ALERT 需要有用户选择app 进行权限申请
//使用Toast方式,不需要权限
params.type = WindowManager.LayoutParams.TYPE_TOAST;
params.format = PixelFormat.RGBA_8888;
params.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
params.gravity = Gravity.LEFT | Gravity.TOP;
params.width = WindowManager.LayoutParams.WRAP_CONTENT;
params.height = WindowManager.LayoutParams.WRAP_CONTENT;
windowManager.addView(floatView,params);//创建悬浮窗
}
- 2) 更新悬浮窗
修改View 或者参数之后进行更新
windowManager.updateViewLayout(floatView, params);
- 3) 移除悬浮窗
windowManager.removeView(floatView);
2、 悬浮窗与Activity
在Activity中对悬浮窗进行操作,遇到了一些列问题。
原:通过下列界面来控制悬浮窗
模拟器(版本号17)运行效果
在开启悬浮窗时销毁activity,悬浮窗一起销毁了。再开启activity进行操作,就是重新开始。
乐视2(android 6.0)运行效果
在开启悬浮窗时销毁activity,悬浮窗没有一起销毁,再开启activity进行操作,不能关闭原来的悬浮窗,activity 重新开始,不能对原来的悬浮窗进行操作。
悬浮窗显示效果分析:在activity中创建悬浮窗,悬浮窗没有被销毁,当重新进入activity的时候,activity获取到的悬浮窗对象并不是之前的悬浮窗对象,对之前的悬浮窗已经失去了可控性。而想要保持对悬浮窗的可控性,就需要将悬浮窗与activity的生命周期绑定,悬浮窗和创建它的这个activity共存亡。
3、 悬浮窗与服务
场景:在activity中开启服务,使用悬浮窗显示手机栈顶app所在的应用包名。
如何获取栈顶app所在的应用包名—另一篇demo中记录。
Service代码:
/**
* 自定义服务 时刻记录手机的栈顶activity 所在包
* 并将包名显示到悬浮窗 服务停止的时候关闭悬浮窗
*/
public class TopAppService extends Service {
private Timer timer;
private WindowManager windowManager;
private View floatView;
private WindowManager.LayoutParams params;
private boolean isFirst;
private TextView tvFloatWindow;
Handler handler = new Handler() {
};
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
initFloatWindow();
isFirst = true;//默认是第一次
if (timer == null) {
timer = new Timer();
timer.scheduleAtFixedRate(new FloatWindowTimer(), 0, 5000);//每隔5s 执行一次
}
return super.onStartCommand(intent, flags, startId);
}
/**
* 获取栈顶app的应用包名,显示到悬浮窗
*
* @param topApp 栈顶app的应用包名
*/
private void uploadFloatWindow(final String topApp) {
//第一次执行时,创建悬浮窗,后面的操作,只对悬浮窗进行更新
if (isFirst) {
//创建悬浮窗
handler.post(new Runnable() {
@Override
public void run() {
tvFloatWindow.setText(topApp);
windowManager.addView(floatView, params);
}
});
isFirst = false;
} else {
//更新悬浮窗
handler.post(new Runnable() {
@Override
public void run() {
tvFloatWindow.setText(topApp);
windowManager.updateViewLayout(floatView, params);
}
});
}
}
/**
* 初始化FloatWindow
*/
private void initFloatWindow() {
windowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
if (floatView == null) {
floatView = LayoutInflater.from(this).inflate(R.layout.float_window_text, null);
params = new WindowManager.LayoutParams();
//使用 TYPE_SYSTEM_ALERT 需要有用户选择app 进行权限申请
//使用Toast方式,不需要权限
params.type = WindowManager.LayoutParams.TYPE_TOAST;
params.format = PixelFormat.RGBA_8888;
params.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
params.gravity = Gravity.LEFT | Gravity.TOP;
params.width = WindowManager.LayoutParams.WRAP_CONTENT;
params.height = WindowManager.LayoutParams.WRAP_CONTENT;
}
tvFloatWindow = (TextView) floatView.findViewById(R.id.tv_float_window);
}
/**
* 判断 用户查看历史记录的权利是否给予app(获取栈顶app的权限)
*
* @return
*/
private boolean isUseGranted() {
Context appContext = MyApplication.getAppContext();
AppOpsManager appOps = (AppOpsManager) appContext
.getSystemService(Context.APP_OPS_SERVICE);
int mode = -1;
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP) {
mode = appOps.checkOpNoThrow("android:get_usage_stats",
android.os.Process.myUid(), appContext.getPackageName());
}
boolean granted = mode == AppOpsManager.MODE_ALLOWED;
return granted;
}
/**
* 高版本:获取顶层的activity的包名
*
* @return
*/
private String getHigherPackageName() {
String topPackageName = "";
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
UsageStatsManager mUsageStatsManager = (UsageStatsManager) getSystemService(Context.USAGE_STATS_SERVICE);
long time = System.currentTimeMillis();
// We get usage stats for the last 10 seconds
//time - 1000 * 1000, time 开始时间和结束时间的设置,在这个时间范围内 获取栈顶Activity 有效
List<UsageStats> stats = mUsageStatsManager.queryUsageStats(UsageStatsManager.INTERVAL_DAILY, time - 1000 * 1000, time);
// Sort the stats by the last time used
if (stats != null) {
SortedMap<Long, UsageStats> mySortedMap = new TreeMap<Long, UsageStats>();
for (UsageStats usageStats : stats) {
mySortedMap.put(usageStats.getLastTimeUsed(), usageStats);
}
if (mySortedMap != null && !mySortedMap.isEmpty()) {
topPackageName = mySortedMap.get(mySortedMap.lastKey()).getPackageName();
Log.e("TopPackage Name", topPackageName);
}
}
}
return topPackageName;
}
/**
* 低版本:获取栈顶app的包名
*
* @return
*/
private String getLowerVersionPackageName() {
String topPackageName;//低版本 直接获取getRunningTasks
ActivityManager activityManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
ComponentName topActivity = activityManager.getRunningTasks(1).get(0).topActivity;
topPackageName = topActivity.getPackageName();
return topPackageName;
}
/**
* 计时器:获取栈顶app的应用包名显示到悬浮窗
*/
class FloatWindowTimer extends TimerTask {
public void run() {
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP) {
boolean useGranted = isUseGranted();
Log.e("TopAppService", "查看历史记录权限 是否允许授权=" + useGranted);
if (useGranted) {
String topApp = getHigherPackageName();
uploadFloatWindow(topApp);
} else {
//开启应用授权界面
Intent intent = new Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}
} else {
String topApp = getLowerVersionPackageName();
uploadFloatWindow(topApp);
}
}
}
@Override
public void onDestroy() {
super.onDestroy();
//如果悬浮窗是显示的 需要移除悬浮窗
timer.cancel();
timer = null;
windowManager.removeView(floatView);
}
}
模拟器(版本号17)效果:
乐视2 (android 6.0)效果: