探讨Android实现后台(Service)按键监听的功能
Android 在Service中监听软键盘显示状态
项目里有需求:在后台监听到物理按键的键值,而且不能依赖activity
最后方案定为用一个像素悬浮窗,通过service控制启动和移除,利用悬浮窗的view来监听按键键值。
但是,悬浮窗的view必须获取到焦点才可以监听到键值,另外,还要及时在用户触摸屏幕或点击返回按键时释放焦点。因为悬浮窗view获取了焦点后,系统的软键盘就弹不出来,返回键也会失效
public class ButtonFloatService extends Service implements View.OnTouchListener, View.OnKeyListener,SoftKeyboardDetector.SoftKeyboardListener {
private static final String TAG = "ButtonFloatService";
private WindowManager windowManager;
private WindowManager.LayoutParams params;
private boolean isShowing;
private View view;
//private Timer timer;
//private TimerTask timerTask;
private boolean isRelease = false;
private static final int MSG = 180;
private boolean isSoftKeyboardShowing = false;
@Override
public void onCreate() {
super.onCreate();
LayoutInflater layoutInflater = LayoutInflater.from(this);
windowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
view = layoutInflater.inflate(R.layout.pix_layout, null);
initWindow();
view.setOnTouchListener(this);
view.setOnKeyListener(this);
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(TAG, "onstart");
show();
return START_NOT_STICKY;
}
private void initWindow() {
Log.d(TAG, "initWindow ");
isRelease = false;
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_PHONE;
}
params.format = PixelFormat.TRANSPARENT;
params.gravity = Gravity.START | Gravity.TOP;
params.x = 0;
params.y = 0;
params.width = 1;
params.height = 1;
params.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL |
WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH |
WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
params.width = WindowManager.LayoutParams.WRAP_CONTENT;
params.height = WindowManager.LayoutParams.WRAP_CONTENT;
view.post(() -> {
view.setFocusable(true);
view.setFocusableInTouchMode(true);
view.requestFocus();
});
}
private void refreshWindowCatchFocus() {
if (isRelease) {
Log.d(TAG, "refreshWindowCatchFocus: 获取焦点");
isRelease = false;
params.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL |
WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH |
WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
view.post(() -> {
view.setFocusable(true);
view.setFocusableInTouchMode(true);
view.requestFocus();
});
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
if (view.isAttachedToWindow()) {
windowManager.updateViewLayout(view, params);
}
} else {
if (view.getParent() != null) {
try {
windowManager.updateViewLayout(view, params);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
private void refreshWindowReleaseFocus() {
if (!isRelease) {
Log.d(TAG, "refreshWindowReleaseFocus: 释放焦点");
isRelease = true;
params.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH |
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
view.post(() -> {
view.setFocusable(false);
view.setFocusableInTouchMode(false);
view.clearFocus();
});
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
if (view.isAttachedToWindow()) {
windowManager.updateViewLayout(view, params);
}
} else {
if (view.getParent() != null) {
try {
windowManager.updateViewLayout(view, params);
} catch (Exception e) {
e.printStackTrace();
}
}
}
SoftKeyboardDetector.getInstance().setSoftKeyboardListener(this);
SoftKeyboardDetector.getInstance().initSoftKeyboardDetector();
}
}
private boolean addToWindow() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (!Settings.canDrawOverlays(this)) {
Log.d(TAG, "addToWindow: 未开启悬浮窗权限");
return false;
}
}
if (windowManager != null) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
if (!view.isAttachedToWindow()) {
windowManager.addView(view, params);
return true;
} else {
return false;
}
} else {
try {
if (view.getParent() == null) {
windowManager.addView(view, params);
}
return true;
} catch (Exception e) {
return false;
}
}
} else {
return false;
}
}
private void show() {
Log.d(TAG, "show float-->isShowing=" + isShowing);
if (isShowing)
return;
addToWindow();
isShowing = true;
}
private void remove() {
Log.d(TAG, "remove float-->isShowing=" + isShowing);
if (!isShowing) return;
removeFromWindow();
isShowing = false;
}
private void removeFromWindow() {
try {
if (windowManager != null) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
if (view.isAttachedToWindow()) {
Log.d(TAG, "float is removeViewImmediate");
windowManager.removeViewImmediate(view);
} else {
if (view.getParent() != null) {
Log.d(TAG, "float is removeView");
windowManager.removeView(view);
// windowManager.removeViewImmediate(view);
}
}
} else {
if (view.getParent() != null) {
windowManager.removeViewImmediate(view);
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void onDestroy() {
SoftKeyboardDetector.getInstance().clearSoftKeyboardDetector();
remove();
super.onDestroy();
}
@Override
public boolean onTouch(View v, MotionEvent event) {
Log.d(TAG, "onTouch: event=" + event);
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_OUTSIDE:
refreshWindowReleaseFocus();
break;
}
return false;
}
/* private void timerStart2CatchFocus() {
if (isRelease) {
Log.d(TAG, "timerStart2CatchFocus: 启动计时器准备获取焦点");
if (timerTask != null) {
timerTask.cancel();
timerTask = null;
}
timerTask = new TimerTask() {
@Override
public void run() {
//判断是否有软键盘,如果没有,获取焦点;否则,无动作
if (!SoftKeyboardDetector.getInstance().isInputMethodShowing(Utils.getContext())) {
Message m = Message.obtain();
m.what = MSG;
handler.sendMessage(m);
}
}
};
if (timer != null) {
timer.cancel();
timer = null;
}
timer = new Timer();
timer.schedule(timerTask, 1500);
}
}*/
@Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
Log.d(TAG, "onKey: keyCode=" + keyCode + "--keyEvent=" + event);
//桌面按键监听
if (event.getKeyCode() == KeyEvent.KEYCODE_BACK){
refreshWindowReleaseFocus();
// timerStart2CatchFocus();
return false;
}
if (event.getAction() == KeyEvent.ACTION_UP) {
ToastUtils.showShortToast("keyCode==" + keyCode);
}
return false;
}
private ButtonHandler handler = new ButtonHandler(this);
static class ButtonHandler extends Handler{
WeakReference<ButtonFloatService> weakReference;
ButtonHandler(ButtonFloatService buttonFloatService){
weakReference = new WeakReference<>(buttonFloatService);
}
@Override
public void handleMessage(Message msg) {
if (msg.what == MSG && weakReference.get() != null)
weakReference.get().refreshWindowCatchFocus();
super.handleMessage(msg);
}
}
@Override
public void onShowKeyboard() {
isSoftKeyboardShowing = true;
}
@Override
public void onDismissKeyboard() {
isSoftKeyboardShowing = false;
Message m = Message.obtain();
m.what = MSG;
handler.sendMessage(m);
}
}
/**
* 软键盘实时检测器,无需Activity
*/
public class SoftKeyboardDetector {
private static final String TAG = "SoftKeyboard";
private SoftKeyboardListener softKeyboardListener;
private static SoftKeyboardDetector instance;
private TimerTask timerTask;
private Timer timer;
public static SoftKeyboardDetector getInstance() {
if (instance == null) {
synchronized (SoftKeyboardDetector.class) {
if (instance == null) {
instance = new SoftKeyboardDetector();
}
}
}
return instance;
}
public void setSoftKeyboardListener(SoftKeyboardListener softKeyboardListener) {
this.softKeyboardListener = softKeyboardListener;
}
/**
* 初始化软键盘检测器
*/
public void initSoftKeyboardDetector() {
clearSoftKeyboardDetector();
timerTask = new TimerTask() {
@Override
public void run() {
if (isInputMethodShowing(Utils.getContext())) {
if (softKeyboardListener != null) {
softKeyboardListener.onShowKeyboard();
}
} else {
if (softKeyboardListener != null) {
softKeyboardListener.onDismissKeyboard();
}
}
}
};
timer = new Timer();
timer.schedule(timerTask, 500, 500);//周期为1s
}
/**
* 清理软键盘检测器
*/
public void clearSoftKeyboardDetector() {
if (timer != null) {
timer.cancel();
timer = null;
}
if (timerTask != null) {
timerTask.cancel();
timerTask = null;
}
}
/**
* 判断软键盘是否显示
* @param context application的上下文
* @return
*/
public boolean isInputMethodShowing(Context context) {
//得到默认输入法包名
String defaultInputName = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD);
defaultInputName = defaultInputName.substring(0, defaultInputName.indexOf("/"));
boolean isInputing = false;
if (android.os.Build.VERSION.SDK_INT > 20) {
try {
InputMethodManager imm = (InputMethodManager) context.getApplicationContext().getSystemService(Context.INPUT_METHOD_SERVICE);
Class clazz = imm.getClass();
Method method = clazz.getMethod("getInputMethodWindowVisibleHeight", null);
method.setAccessible(true);
int height = (Integer) method.invoke(imm, null);
if (height > 100) {
isInputing = true;
}
} catch (Exception e) {
e.printStackTrace();
}
} else {
ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
List<ActivityManager.RunningAppProcessInfo> appProcesses = activityManager.getRunningAppProcesses();
for (ActivityManager.RunningAppProcessInfo appProcess : appProcesses) {
if (appProcess.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE) {
if (appProcess.processName.equals(defaultInputName)) {
isInputing = true;
break;
}
}
}
}
if (isInputing) {
Log.d(TAG, "软键盘显示中");
} else {
Log.d(TAG, "软键盘隐藏中");
}
return isInputing;
}
public interface SoftKeyboardListener {
void onShowKeyboard();
void onDismissKeyboard();
}
}
pix_layout.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<View
android:background="@color/transparent"
android:layout_width="1dp"
android:layout_height="1dp">
</View>
</RelativeLayout>