悬浮窗
参考:
文章目录
1. 权限
悬浮窗需要用户授权才能开启,如果在没有权限的情况下开启悬浮窗的话,会报window 2003
的错误。
悬浮窗的权限会因为SDK
版本而有所不同。对于SDK < 23
的系统可以直接由app申请权限,而SDK >= 23
的系统则必须通过用户授权才可以。
悬浮窗的权限跟普通的申请照相机、图片、通讯录等等资源不一样,申请这些资源的时候可以直接通过ActivityCompat.checkSelfPermission()
来实现,系统会弹出一个对话框来提示用户允许还是不允许app获取这些权限。而悬浮窗不是,因为允许该应用在其他应用上方显示
是在高级
里面的,所以必须往Settings
发送intent
跳到开启悬浮窗的页面让用户手动打开允许。
private void requestPermission() {
// 判断当前系统的SDK版本是否大于等于23
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (!Settings.canDrawOverlays(AppEn.appContext)) {
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
intent.setData(Uri.parse("package:" + PACKGE_NAME)); // 这个应用程序的包名
startActivityForResult(intent, REQUEST_PERMISSION_CODE); // 自己定义这个返回的确认码
}
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
// 一个非常大的坑就是 只要app顺利跳到了Settings里这个app的高级页面之后
// 不管用户有没有确认允许该应用显示在其他应用上方
// 只要应用点击返回 都会返回这个REQUEST_PERMISSION_CODE
// 所以应该在onActivityResult里面再一次判断究竟有没有成功获取权限
if (requestCode == REQUEST_PERMISSION_CODE) {
if (Settings.canDrawOverlays(PACKGE_NAMENAME)) {
Log.d(TAG, "成功获取开启悬浮窗的权限!");
}
}
}
而在Manifest.xml
里面也需要加上相关的uses-permission
。
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.SYSTEM_OVERLAY_WINDOW" />
2. 创建悬浮窗
(1) 布局及界面初始化
布局就是一层layout
垫底,上面加别的东西就行了,可以是ImageView
或者别的什么东西。总之就跟平时activity或者dialog或者fragment之类的布局一样就行了。
那些必要的findViewById()
和LayoutInflater.from(CONTEXT).inflate(R.layout.LAYOUT_NAME, null)
,以及各种必要的回调函数就还是按以往的一样。
View windowView;
private void initView() {
windowView = LayoutInflater.from(CONTEXT).inflate(R.layout.LAYOUT_NAME, null);
}
(2) 参数初始化
因为悬浮窗是用WindowManager
实现的,只要它依附的Service
开启了并且没有stopSelf()
,它就可以不受任何activity影响穿透整个app存在。
在参数初始化时,还是要在参数里面写明权限申请,就是把Manifest
里面的两个uses-permission
写一遍。
除此之外,还要初始化一下悬浮窗的长、宽、位置、是否透明、可不可以获取焦点之类的基础设置。
WindowManager windowManager = null;
private void initParams() {
WindowManager.LayoutParams params = new WindowManager.LayoutParams();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
params.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
} else {
params.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
}
windowManager = (WindowManager) CONTEXT.getSystemService(Context.WINDOW_SERVICE);
params.format = PixelFormat.RGBA_8888; // 窗口透明
params.gravity = Gravity.BOTTOM;
params.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
params.width = WindowManager.LayoutParams.WRAP_CONTENT;
params.height = WindowManager.LayoutParams.WRAP_CONTENT;
params.x = CONTEXT.getResources.getDimensionPixelSize(R.dimen.DIMEN_X);
params.y = CONTEXT.getResources.getDimensionPixelSize(R.dimen.DIMEN_Y);
windowManager.addView(windowView, params); // windowView就是界面初始化的时候用来inflate的那个view
}
(4) 销毁悬浮窗
销毁悬浮窗的时候不能单纯地把windowManager
置为null
,也不要轻易使用removeViewImmediate
,非常容易错。
private void destroyWindow() {
windowManger.removeView(windowView);
}
3. Service
Service是安卓的四大组件之一,它是跑在前台的服务。它不像activity一样需要界面,不过它可以绑定一个view来处理事务。
Service分为两种,绑定的和不绑定的。这里暂时先用普通的,不绑定的Service。
对于这种不绑定的Service,生命周期是onCreate()
、onStartCommand()
、onDestroy()
。
当一个Service开启了之后,如果没有停止它的话,它将一直运行,除非被系统杀死。
一个Service首次创建的时候会调用onCreate()
,但是一旦创建并且没有被停止,之后的每一次startService()
,系统都不会调用onCreate()
,而是从onStartCommand()
开始跑。
@Override
public void onCreate() {
super.onCreate();
init();
}
private void init() {
// 进行必要的初始化工作
// 例如创建一个悬浮窗或者别的东西
// ...
// ...
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// 最好判断一下是否为空
// 因为onStartCommand传进来的intent是Nullable的
// 有时候系统不知道干什么就可能会莫名其妙传个null的intent进来
if (intent == null) return super.onStartCommand(null, flags, startId);
// 然后开始对悬浮窗进行想要的操作
// ...
// ...
return super.onStartCommand(intent, flags, startId);
}
private void destroyService() {
// 在结束service之前记得销毁悬浮窗
// destroyWindow();
stopSelf();
}
Champagne&Caviar
发布了33 篇原创文章 · 获赞 4 · 访问量 1万+
私信
关注