权限
<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时,我们还应该通过如下代码再次确认权限是否已打开:
@Overrideprotected 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.AlertDialogandroid.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"><TextViewandroid: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" /><TextViewandroid: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 那么玩。
