Android中InputMethodService监听网络变化弹窗崩溃问题求助
问题描述
开发Android自定义键盘时,需求是键盘弹出后,网络中断时显示提示弹窗。此前在Activity中实现正常,但基于InputMethodService的场景下运行崩溃,报错如下:
错误信息
Caused by: android.view.WindowManager$BadTokenException: Unable to add window -- token null is not valid; is your activity running? at android.view.ViewRootImpl.setView(ViewRootImpl.java:1159) at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:399) at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:133) at android.app.Dialog.show(Dialog.java:345) at com.prasath.openaikeyboard.util.NetworkChangeListener.onReceive(NetworkChangeListener.java:25) at android.app.LoadedApk$ReceiverDispatcher$Args.lambda$getRunnable$0$LoadedApk$ReceiverDispatcher$Args(LoadedApk.java:1679)
相关代码:
InputMethodService类代码
public class MyInputMethodService extends InputMethodService implements KeyboardView.OnKeyboardActionListener { private static final String TAG = "MyInputMethodService"; private NetworkChangeListener networkChangeListener=new NetworkChangeListener(); @Override public View onCreateInputView() { KeyboardView keyboardView=(KeyboardView) getLayoutInflater().inflate(R.layout.keyboard_view,null); Keyboard keyboard=new Keyboard(this,R.xml.numberpad); keyboardView.setKeyboard(keyboard); keyboardView.setOnKeyboardActionListener(this); networkChangeListener=new NetworkChangeListener(); return keyboardView; } }
NetworkChangeListener类代码
public class NetworkChangeListener extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { if(!Common.isConnectedToInternet(context)){ AlertDialog.Builder builder=new AlertDialog.Builder(context); View view= LayoutInflater.from(context).inflate(R.layout.check_internet_dialog,null); builder.setView(view); Button button=view.findViewById(R.id.btnrefreshid); AlertDialog alertDialog=builder.create(); alertDialog.show(); alertDialog.setCancelable(false); alertDialog.getWindow().setGravity(Gravity.CENTER); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { alertDialog.dismiss(); onReceive(context,intent); } }); } } }
问题原因
AlertDialog依赖Activity类型的Context创建窗口,但你在onReceive中传入的是InputMethodService的Context(属于Service类型),没有有效的窗口token,导致无法添加Dialog窗口,触发BadTokenException。
解决方法
方法1:绑定输入法窗口token创建Dialog
利用InputMethodService自身的窗口token,让Dialog依附在输入法窗口上,具体步骤如下:
调整MyInputMethodService代码
public class MyInputMethodService extends InputMethodService implements KeyboardView.OnKeyboardActionListener { private static final String TAG = "MyInputMethodService"; private NetworkChangeListener networkChangeListener; @Override public void onCreate() { super.onCreate(); networkChangeListener = new NetworkChangeListener(this); // 注册网络状态广播(务必在onDestroy中注销) IntentFilter filter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION); registerReceiver(networkChangeListener, filter); } @Override public View onCreateInputView() { KeyboardView keyboardView = (KeyboardView) getLayoutInflater().inflate(R.layout.keyboard_view, null); Keyboard keyboard = new Keyboard(this, R.xml.numberpad); keyboardView.setKeyboard(keyboard); keyboardView.setOnKeyboardActionListener(this); return keyboardView; } @Override public void onDestroy() { super.onDestroy(); if (networkChangeListener != null) { unregisterReceiver(networkChangeListener); } } // 提供获取输入法窗口token的方法 public IBinder getInputWindowToken() { return getWindow().getWindow().getAttributes().token; } }
修改NetworkChangeListener代码
public class NetworkChangeListener extends BroadcastReceiver { private MyInputMethodService inputMethodService; public NetworkChangeListener(MyInputMethodService service) { this.inputMethodService = service; } @Override public void onReceive(Context context, Intent intent) { if (!Common.isConnectedToInternet(context)) { AlertDialog.Builder builder = new AlertDialog.Builder(inputMethodService); View view = LayoutInflater.from(inputMethodService).inflate(R.layout.check_internet_dialog, null); builder.setView(view); Button button = view.findViewById(R.id.btnrefreshid); AlertDialog alertDialog = builder.create(); // 设置Dialog依附于输入法窗口 alertDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG); WindowManager.LayoutParams params = alertDialog.getWindow().getAttributes(); params.token = inputMethodService.getInputWindowToken(); params.gravity = Gravity.CENTER; alertDialog.getWindow().setAttributes(params); alertDialog.setCancelable(false); alertDialog.show(); button.setOnClickListener(v -> { alertDialog.dismiss(); // 避免递归调用,直接重新检查网络 if (!Common.isConnectedToInternet(context)) { // 可添加重试提示或逻辑 } }); } } }
方法2:改用无依赖的提示组件
如果不需要强交互弹窗,可直接使用Toast或依附于输入法View的Snackbar,这类组件无需Activity Context:
@Override public void onReceive(Context context, Intent intent) { if (!Common.isConnectedToInternet(context)) { Toast.makeText(context, "网络连接中断,请检查网络", Toast.LENGTH_LONG).show(); } }
注意事项
- 广播需在
onCreate注册、onDestroy注销,避免内存泄漏; - 使用
TYPE_INPUT_METHOD_DIALOG确保Dialog显示在输入法上层; - 禁止在
onReceive中递归调用自身,避免无限循环。
内容的提问来源于stack exchange,提问作者Prasath S
相关产品推荐
相关产品推荐

