最近用到悬浮窗的功能,android6.0以下的系统可以进行正常的显示,而android6.0及以上的则创建不了,运行直接崩溃,报如下错误:
Caused by: android.view.WindowManager$BadTokenException: Unable to add window android.view.ViewRootImpl$W@96872e8 -- permission denied for this window type
但是我在清单文件明明加了<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />这个权限,为什么还是不行呢?原因是自从android6.0之后,权限改了,权限的授权不是在应用第一次安装时对所有的权限进行授权,而是在需要用到某个权限时对其动态授权。
查看android的官方文档: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() .
能够熟练阅读各类英文文档也是一门非常重要的技能。上面的描述大概意思就是说,如果我们的 targetSdkVersion指定成了23或者更高,在使用 SYSTEM_ALERT_WINDOW权限时,需要先调用 Settings.canDrawOverlays() 来判断一下是否允许创建悬浮窗,如果允许的话就可以创建了,不允许的话还要发送一个action值为 ACTION_MANAGE_OVERLAY_PERMISSION 的Intent来让用户同意创建悬浮窗。
先上效果图:
那下面就这样试着写一下,代码如下:
package com.cool.floatviewdemo;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.provider.Settings;
import android.util.Log;
import android.view.View;
import android.widget.Toast;
public class MainActivity extends Activity {
private final static int REQUEST_CODE = 100;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void callPhone(View v){
startActivity(new Intent(this, CallPhoneActivity.class));
}
/**
*显示悬浮窗
* @param v
*/
public void showFloatView(View v) {
if (Build.VERSION.SDK_INT >= 23) {
if (Settings.canDrawOverlays(this)) {
showFloatView();
} else {
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
startActivityForResult(intent, REQUEST_CODE);
}
} else {
showFloatView();
}
}
@TargetApi(Build.VERSION_CODES.M)
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if(requestCode == REQUEST_CODE){
if (Settings.canDrawOverlays(this)) {
showFloatView();
} else {
Toast.makeText(this,"授权失败",Toast.LENGTH_SHORT).show();
}
}
}
/**
* 显示悬浮窗
*/
private void showFloatView() {
FloatView.showFloatView(getApplication(), R.layout.layout_float_window);
FloatView.setOnClickListener(new FloatView.OnClickListener() {
@Override
public void onClick(View view) {
Log.e("399", "点击了");
}
});
}
/**
* 隐藏悬浮窗
* @param v
*/
public void hideFloatView(View v){
FloatView.hideFloatView();
}
}
悬浮窗的View如下:
package com.cool.floatviewdemo;
import android.content.Context;
import android.graphics.PixelFormat;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;
/**
* Created by cool on 2016/8/30.
*/
public class FloatView {
private static Context mContext;
private static WindowManager mWindowManager;
private static WindowManager.LayoutParams wmParams;
private static View mView;
private static boolean isShow = false;//悬浮框是否已经显示
private static OnClickListener mListener;//view的点击回调listener
public static void setOnClickListener(OnClickListener listener){
mListener = listener;
}
interface OnClickListener{
void onClick(View view);
}
/**
* 显示悬浮框
*/
public static void showFloatView(Context context,int layoutId){
mContext = context;
if(isShow){
return;
}
mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
wmParams = new WindowManager.LayoutParams();
wmParams.type = WindowManager.LayoutParams.TYPE_PHONE;
wmParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
wmParams.gravity = Gravity.CENTER;
wmParams.format = PixelFormat.RGBA_8888;
wmParams.x = context.getResources().getDisplayMetrics().widthPixels;
wmParams.y = 0;
wmParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
wmParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
mView = LayoutInflater.from(context).inflate(layoutId, null);
mWindowManager.addView(mView, wmParams);
mView.setOnTouchListener(new View.OnTouchListener() {
float downX = 0;
float downY = 0;
int oddOffsetX = 0;
int oddOffsetY = 0;
@Override
public boolean onTouch(View v, MotionEvent event) {
switch(event.getAction()){
case MotionEvent.ACTION_DOWN:
downX = event.getX();
downY = event.getY();
oddOffsetX = wmParams.x;
oddOffsetY = wmParams.y;
break;
case MotionEvent.ACTION_MOVE:
float moveX = event.getX();
float moveY = event.getY();
//不除以3,拖动的view抖动的有点厉害
wmParams.x += (moveX - downX)/3;
wmParams.y += (moveY - downY)/3;
if(mView != null){
mWindowManager.updateViewLayout(mView,wmParams);
}
break;
case MotionEvent.ACTION_UP:
int newOffsetX = wmParams.x;
int newOffsetY = wmParams.y;
if(Math.abs(newOffsetX - oddOffsetX) <=20 && Math.abs(newOffsetY - oddOffsetY) <=20){
if(mListener != null){
mListener.onClick(mView);
}
}
break;
}
return true;
}
});
isShow = true;
}
/**
* 隐藏悬浮窗
*/
public static void hideFloatView(){
if(mWindowManager != null && isShow){
mWindowManager.removeView(mView);
isShow = false;
}
}
}