权限
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
Allows an app to create windows using the type TYPE_SYSTEM_ALERT, shown on top of all other apps. Very few apps should use this permission; these windows are intended for system-level interaction with the user. 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. The app can check whether it has this authorization by calling Settings.canDrawOverlays(). Protection level: signature Constant Value: “android.permission.SYSTEM_ALERT_WINDOW”
Window设置
dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
如果是 M0 以前的系统,这个时候就可以 show 了;但自 M0 开始这么 show 就会报错了。
如前面注释所说,自M0开始,显示系统级对话框,需要单独开启这个权限。这时我们可以引导用户手动打开权限,注释中也给出了提示,这里提供一个方案:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (!Settings.canDrawOverlays(this)) {
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
Uri.parse("package:" + getPackageName()));
startActivityForResult(intent, 100);
}
}
上面的代码就会跳转到相应的设置界面,当用户返回到APP时,我们还应该通过如下代码再次确认权限是否已打开:
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (100 == requestCode) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (!Settings.canDrawOverlays(this)) {
// SYSTEM_ALERT_WINDOW permission not granted...
}
else{
//执行其它逻辑
}
}
}
}
踩坑
1、MIUI 问题
MIUI 的权限管理改的有点厉害,权限问题表现和原版Android,甚至大多数ROM表现都不太一致,在适配 MIUI 的时候一定要多小心。
2、AlertDialog 导包问题
这里我写 AlertDialog 时,AS 提示有如下两个类可选:
android.support.v7.app.AlertDialog
android.app.AlertDialog
一开始我想,support 包的应该兼容性更好,就用了 support 包的,结果 run 的时候报错了:
Process: com.example.sevenun.littledemo, PID: 2085 java.lang.RuntimeException: Unable to start receiver xxx: java.lang.IllegalStateException:
You need to use a Theme.AppCompat theme (or descendant) with this activity.
提示信息已经很明确了,theme 不对,可怎么解呢?
一开始我想可能是 Service 的 context 没有 Theme,或者 Theme 不对,于是试了ApplicationContext
,发现还是不行。
然后尝试 create dialog 的时候,手动设置一个 Theme.AppCompat
的 Theme,结果还是不行。
最后才想到会不会是导包不对,然后换了 android.app.AlertDialog
,运行就OK了。
3、Looper 问题
如果要在 service 使用,直接调用即可,如果是在 service 的线程中显示对话框,因为自行创建的线程没有 Looper,因此需要添加代码如下:
Looper.prepare();
//TODO: showDialog()
Looper.loop();
若不添加 Looper,则会报错。
4、AlertDialog自定义布局问题
实际使用中,有时候需要自定义 Dialog 布局,一般情况下我们是这么做的:
val view = View.inflate(this, R.layout.dialog_test, null) //填充布局
val title = view.findViewById<TextView>(R.id.title) //绑定控件
title.text = "这里是Title" //设置数据
val dialog = android.app.AlertDialog.Builder(this)
.setView(view) //设置自定义布局给Dialog
.create()
dialog.show()
这样做没毛病,就是有点繁琐,有没有更好的办法呢,比如像 Preference
替换自定义布局一样,只需要设置资源ID即可。
我们知道 Preference
替换布局资源时,自需要将对应控件的 ID 设为系统默认的 ID,即可自动完成绑定,就像这样:(注意ID的写法)
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_horizontal"
android:orientation="vertical"
android:padding="8dp">
<TextView
android:id="@android:id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:gravity="center"
android:textColor="@color/colorAccent"
android:textSize="24sp" />
<TextView
android:id="@android:id/message"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:gravity="center"
android:textColor="@color/lighter_gray" />
</LinearLayout>
用相同的方法,试下 Dialog:
val dialog = android.app.AlertDialog.Builder(this)
.setView(R.layout.dialog_test)
.setTitle("Title")
.setMessage("Message")
.create()
dialog.show()
可实际情况是,Title 还是原来的 Title,Message 还是原来的 Message,新的布局并没有替换原始的,而是成为了原始布局的一部分,被添加在底部了。
于是就想,是不是因为Builder不支持这么替换布局呢?那就换个方式:
val dialog = android.app.AlertDialog.Builder(this).create()
dialog.setContentView(R.layout.dialog_wifi_fence)
dialog.setTitle("Title")
dialog.setMessage("Message")
dialog.show()
重点来了!
这样写,看起来没问题,但实际运行就会报错了:
requestFeature() must be called before adding content
requestFeature
这个方法我们在 Activity 里面设置无标题栏时用过:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState)
window.requestFeature(Window.FEATURE_NO_TITLE)
this.setContentView(R.layout.activity_debug)
}
我们知道,这个时候如果 setContentView -> requestFeature
就会报同样的错误。
Android 上,能够 show 给用户的,都和 Window 有联系,那 Dialog 是什么时候和 Window 联系上的呢?
简单看了下实现过程,发现是 show 的时候,才被add上来:
public void show() {
······
mWindowManager.addView(mDecor, l);
mShowing = true;
······
}
按照 Activity 那样先和 Window 联系上,再设置自己的布局的方式,把 Dialog 修改一下:
val dialog = android.app.AlertDialog.Builder(this).create()
dialog.show()
dialog.setContentView(R.layout.dialog_wifi_fence)
dialog.setTitle("Title")
dialog.setMessage("Message")
//或者
val dialog = android.app.AlertDialog.Builder(this).create()
dialog.setTitle("Title")
dialog.setMessage("Message")
dialog.show()
dialog.setContentView(R.layout.dialog_wifi_fence)
//或者
val dialog = android.app.AlertDialog.Builder(this).create()
dialog.show()
dialog.setTitle("Title")
dialog.setMessage("Message")
dialog.setContentView(R.layout.dialog_wifi_fence)
结果发现,只要 show -> setContentView
就不会报错,但是 setTitle、setMessage
却不会生效,看来 Dialog 替换默认布局不能像 Preference
那么玩。