心血来潮 ,突然想实现一个类似ios上assistive touch 功能的app ,发现这个在许多应用上都比较常见,例如许多清理类产品(例如360)都有那么一个小悬浮窗,觉得这个应该不是很难,所以打算将一些基础的功能实现。
首先,这个touch是浮动在其他应用上的(不是正常app该有的样子),这一定是某种特殊的手段,百度找了一圈, 发现这是通过WindowManager来实现的。
WindowManager是一个android服务,api中的定义是:The interface that apps use to talk to the window manager.(app和窗口管理者交互的地方)
WindowManager是一个android的系统服务,他是唯一的。通过WindowManager我们可以实现一切和窗口有关的事情。对于这个WindowManager我们可以通过下面这句代码来获取:
windowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
这里的mContext是某个Activity的context,无论怎样,我们都需要一个有界面的app作为载体吧。
另外,不得不说的十分重要的一个类,WindowManager.LayoutParams, 他是WindowManager的内部类,所有关于需要通过WindowManager设置的view的参数都通过这个类的对象来配置,在介绍这个类之前,我们先看三个方法:
关于WindowManager,我们需要了解的方法有三个:
addView
void addView (View view, ViewGroup.LayoutParams params)
Assign the passed LayoutParams to the passed View and add the view to the window.
将LayoutParams分配给view,并将这个view添加到window上。
removeView
将view从window移除
updateViewLayout
更新view的布局,主要用来更新view的位置的参数。
通过这三个方法,我们大致了解了这个WindowManager的基本使用了。但是存在一个疑惑的地方,为什么没有检查重复添加。其实文档中有指出,这个方法会抛出异常:
Throws WindowManager.BadTokenException for certain programming errors, such as adding a second view to a window without removing the first view.
但是removeView方法就没有抛出异常,所以我在程序中没有使用异常处理,而是为我的自定义view设定了静态的boolean标签。通过这个判断是否被添加。
它是WindowManager的内部类,一般用于配置一些WindowManager的参数。
关于LayoutParams, 其中比较重要的是两个变量的设置
flags
Various behavioral options/flags. Default is none.
flags主要用于设定一些窗口焦点相关的属性。
挑一个参数来解释一波
FLAG_NOT_FOCUSABLE: 窗口标志:此窗口永远不会获得关键输入焦点,因此用户不能将键或其他按钮事件发送给它。那么会将这个焦点传递到后面的窗口。这个标志也将设置flag_not_touch_modal。
设置这个标志也意味着窗口将不会与输入法相互作用,所以它将z序和独立于任何输入法(这通常意味着会z序顶部的输入法,所以它可以使用全屏幕的内容,如果需要覆盖的输入法。你可以使用flag_alt_focusable_im修改这个行为。
关于这个,这里不解释太多,我们可以在文档中逐个查看它参数的含义
type
The general type of window. There are three main classes of window types:
主要分为三种
* Application windows (ranging from FIRST_APPLICATION_WINDOW to LAST_APPLICATION_WINDOW) are normal top-level application windows. For these types of windows, the token must be set to the token of the activity they are a part of (this will normally be done for you if token is null).
* Sub-windows (ranging from FIRST_SUB_WINDOW to LAST_SUB_WINDOW) are associated with another top-level window. For these types of windows, the token must be the token of the window it is attached to.
* System windows (ranging from FIRST_SYSTEM_WINDOW to LAST_SYSTEM_WINDOW) are special types of windows for use by the system for specific purposes. They should not normally be used by applications, and a special permission is required to use them.
type的每个常量都用一个整数来表示
解释一个参数:
TYPE_PHONE : 窗口类型:phone. 这些都是非应用程序窗口,提供用户与电话的交互(特别是呼入)。这些窗口通常放在所有应用程序之上,而在状态栏后面。在多用户系统中显示所有用户的窗口。
这里也不多解释,请查看官方文档。
介绍完这两个变量,我们看一下这个类的构造函数,挑一个最长的
WindowManager.LayoutParams(int w, int h, int xpos, int ypos, int_type, int_flags, int_format)
其中w,h表示宽度和高度,xpos和ypos是view的左上角的位置,type和flags上面已经解释过,最后一个format是这个类中的一个成员变量,代表所需位图格式。可能是在PixelFormat的一个常数,默认格式是PixelFormat.OPAQUE,我在使用时设置了TRANSLUCENT,代表支持透明度。
上面就是WindowManager和LayoutParams的基本使用了。
接下来我们聊一聊WindowManager有关的权限。
首先,推荐大家查看android 6.0 的权限变化。
我们知道在android 6.0添加了运行时权限,许多权限并不需要在安装时就授权,而是在使用时询问用户是否授权,下面我们简单介绍一些这些权限。
Android 是一个权限分隔的操作系统,其中每个应用都有其独特的系统标识(Linux 用户 ID 和组 ID)。系统各部分也分隔为不同的标识。Linux 据此将不同的应用以及应用与系统分隔开来。系统权限分为正常权限和危险权限。
正常权限
很多权限被指定为protection_normal,这表明让应用程序获取权限对用户的隐私或安全没有太大风险。例如,用户将合理地知道应用程序是否可以读取其联系人信息,因此用户必须显式地授予此权限。与此相反,在允许应用程序抖动设备时没有很大的风险,因此许可被指定为正常。
如果应用程序在其清单中声明它需要一个正常的权限,系统会自动在安装时授予该应用程序的权限。系统不会提示用户授予正常权限,用户不能撤消这些权限。
其实也就是一些正常的运行程序可能需要的并且不会获取用户隐私或者可能对用户的重要数据造成修改的权限。
危险权限
所有危险的 Android 系统权限都属于权限组。如果设备运行的是 Android 6.0(API 级别 23),并且应用的 targetSdkVersion 是 23 或更高版本,则当用户请求危险权限时系统会发生以下行为:
如果应用请求其清单中列出的危险权限,而应用目前在权限组中没有任何权限,则系统会向用户显示一个对话框,描述应用要访问的权限组。对话框不描述该组内的具体权限。例如,如果应用请求 READ_CONTACTS 权限,系统对话框只说明该应用需要访问设备的联系信息。如果用户批准,系统将向应用授予其请求的权限。
如果应用请求其清单中列出的危险权限,而应用在同一权限组中已有另一项危险权限,则系统会立即授予该权限,而无需与用户进行任何交互。例如,如果某应用已经请求并且被授予了 READ_CONTACTS 权限,然后它又请求 WRITE_CONTACTS,系统将立即授予该权限。
如果设备运行的是 Android 5.1(API 级别 22)或更低版本,并且应用的 targetSdkVersion 是 22 或更低版本,则系统会在安装时要求用户授予权限。再次强调,系统只告诉用户应用需要的权限组,而不告知具体权限。
关于危险权限,在程序安装的时候它们已权限组的形式和正常权限一样出现在获取权限列表中,但是在Android 6.0(API 级别 23)以及以上版本,这些权限在调用的时候就会向用户申请,等待用户授权。这就是运行时权限。
特殊权限
有许多权限其行为方式与正常权限及危险权限都不同。SYSTEM_ALERT_WINDOW 和 WRITE_SETTINGS 特别敏感,因此大多数应用不应该使用它们。如果某应用需要其中一种权限,必须在清单中声明该权限,并且发送请求用户授权的 intent。系统将向用户显示详细管理屏幕,以响应该 intent。
关于这两个权限,谷歌推荐引导用户进入系统的特殊权限列表给应用程序授权。
对于SYSTEM_ALERT_WINDOW 权限,在google play上线的app,可以在程序安装好后就获得这个权限,无需引导用户授权。好了,其实完成悬浮窗的关键权限就在于SYSTEM_ALERT_WINDOW这个权限。下面介绍这个权限的使用。
授权
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
我们需要在AndroidManifest.xml文件中声明这个权限。
检查权限
The app can check whether it has this authorization by calling Settings.canDrawOverlays().
通过调用 Settings.canDrawOverlays(Context context).来检查是否拥有这个权限。当我在api 21的设备上运行声明这个权限的应用程序的时候,发现这个权限已经被自动允许了。所以这个权限其实也是属于危险权限,并且是特殊危险权限,因为一旦搞不好,整个屏幕的点击事件都会被屏蔽,你点哪儿都没用,可以说是很危险了。
引导
Note: If the app targets API level 23 or higher, the app user must explicitly grant this permission to the app through a permission management screen. The app requests the user's approval by sending an intent with action ACTION_MANAGE_OVERLAY_PERMISSION.
我们需要通过这个intent引导用户进入允许出现在其他应用上的授权列表并且为自己的应用程序授权。
Intent intent = new Intent(android.provider.Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
startActivityForResult(intent, REQUESTCODE);
好了现在已经可以通过WindowManager来将我们的view加入window。