Android中Widget与Service/Activity通信方案咨询(蓝牙场景)
我来给你梳理下Android里Widget和Service、Activity通信的可行方案,尤其是你提到的getService导致蓝牙断开的问题——这个坑很多人都踩过,咱们一步步来解决。
一、为什么getService会导致蓝牙断开?
首先得搞清楚根源:AppWidgetProvider本质是个广播接收器,它的onReceive方法执行完后,进程随时可能被系统回收。如果你用绑定服务(比如getService)的方式调用Service方法,当Widget的广播生命周期结束,绑定会自动解除,Service如果没有其他绑定或启动来源,就会被系统销毁,自然蓝牙连接就断了。
所以核心思路是:避免通过绑定的方式和Service交互,改用「启动Service+Intent传递命令」的模式,让Service保持独立运行(必要时设为前台服务)。
二、Widget与Bluetooth Service的通信实现
方案:通过带Action的Intent给Service发指令
在Widget的onUpdate方法里,给每个按钮设置PendingIntent,直接启动Service并传递命令参数,Service在onStartCommand里处理这些指令。
1. Widget端代码(设置按钮点击事件)
@Override public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { for (int appWidgetId : appWidgetIds) { RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.your_widget_layout); // 给按钮1设置点击事件:发送指令1 Intent cmd1Intent = new Intent(context, BluetoothService.class); cmd1Intent.setAction("BLUETOOTH_SEND_CMD_1"); cmd1Intent.putExtra("COMMAND", "YOUR_CUSTOM_CMD_1"); PendingIntent pendingIntent1 = PendingIntent.getService( context, 0, cmd1Intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE ); views.setOnClickPendingIntent(R.id.btn_cmd1, pendingIntent1); // 按钮2、3、4同理,设置不同的Action和Command // ... appWidgetManager.updateAppWidget(appWidgetId, views); } }
2. Bluetooth Service端代码(处理指令)
@Override public int onStartCommand(Intent intent, int flags, int startId) { if (intent == null || intent.getAction() == null) { return START_STICKY; // 被杀死后自动重启 } switch (intent.getAction()) { case "BLUETOOTH_SEND_CMD_1": String cmd1 = intent.getStringExtra("COMMAND"); sendToBluetoothDevice(cmd1); break; case "BLUETOOTH_SEND_CMD_2": // 处理其他指令 break; // 对应其他按钮的Action } // 让Service保持运行,避免被系统回收 return START_STICKY; } // 你的蓝牙发送逻辑 private void sendToBluetoothDevice(String command) { if (bluetoothSocket != null && bluetoothSocket.isConnected()) { try { OutputStream outputStream = bluetoothSocket.getOutputStream(); outputStream.write(command.getBytes()); outputStream.flush(); } catch (IOException e) { e.printStackTrace(); // 处理发送失败的情况 } } }
3. 让Service稳定运行(可选但重要)
如果需要长期保持蓝牙连接,建议把Service设为前台服务,避免被系统后台杀死:
private void startForegroundService() { Notification notification = new NotificationCompat.Builder(this, "BLUETOOTH_CHANNEL") .setContentTitle("蓝牙连接中") .setContentText("正在与设备通信") .setSmallIcon(R.drawable.ic_bluetooth) .build(); startForeground(1, notification); }
三、Widget与Activity的通信方案
Widget和Activity的通信有几种常用方式,根据你的需求选:
方案1:本地广播(LocalBroadcastManager)
适合Widget主动给Activity发消息,比如点击按钮通知Activity更新状态。
Activity端:注册广播接收器
private BroadcastReceiver widgetMessageReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if ("WIDGET_TO_ACTIVITY_ACTION".equals(action)) { String data = intent.getStringExtra("MESSAGE"); // 处理Widget传来的数据,比如更新UI updateActivityUI(data); } } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.your_activity_layout); // 注册本地广播 IntentFilter filter = new IntentFilter("WIDGET_TO_ACTIVITY_ACTION"); LocalBroadcastManager.getInstance(this).registerReceiver(widgetMessageReceiver, filter); } @Override protected void onDestroy() { super.onDestroy(); // 务必注销广播,避免内存泄漏 LocalBroadcastManager.getInstance(this).unregisterReceiver(widgetMessageReceiver); }
Widget端:发送广播
// 在按钮点击的PendingIntent逻辑里,或者Widget的其他回调中 Intent broadcastIntent = new Intent("WIDGET_TO_ACTIVITY_ACTION"); broadcastIntent.putExtra("MESSAGE", "来自Widget的指令"); LocalBroadcastManager.getInstance(context).sendBroadcast(broadcastIntent);
方案2:SharedPreferences + ContentObserver
适合需要双向同步状态的场景,比如Widget修改配置,Activity实时感知变化。
Widget端:修改SharedPreferences
SharedPreferences prefs = context.getSharedPreferences("WidgetActivityPrefs", Context.MODE_PRIVATE); prefs.edit().putString("last_command", "CMD_FROM_WIDGET").apply();
Activity端:监听SharedPreferences变化
private ContentObserver prefsObserver = new ContentObserver(new Handler()) { @Override public void onChange(boolean selfChange) { super.onChange(selfChange); SharedPreferences prefs = getSharedPreferences("WidgetActivityPrefs", MODE_PRIVATE); String lastCmd = prefs.getString("last_command", ""); // 根据指令更新Activity handleWidgetCommand(lastCmd); } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.your_activity_layout); // 注册ContentObserver,监听SharedPreferences变化 getContentResolver().registerContentObserver( Uri.parse("content://com.your.package.widgetactivityprefs"), true, prefsObserver ); } @Override protected void onDestroy() { super.onDestroy(); getContentResolver().unregisterContentObserver(prefsObserver); }
方案3:直接打开Activity传递数据
如果点击Widget按钮需要跳转到Activity并传递参数,直接用PendingIntent.getActivity:
Intent openActivityIntent = new Intent(context, YourActivity.class); openActivityIntent.putExtra("COMMAND", "CMD_FROM_WIDGET"); PendingIntent pendingIntent = PendingIntent.getActivity( context, 0, openActivityIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE ); views.setOnClickPendingIntent(R.id.btn_open_activity, pendingIntent);
四、从Service向Widget回传状态(可选)
如果需要把蓝牙连接状态、发送结果同步到Widget,可以在Service里调用AppWidgetManager更新Widget UI:
private void updateWidgetStatus(boolean isConnected) { AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(this); int[] widgetIds = appWidgetManager.getAppWidgetIds( new ComponentName(this, YourWidget.class) ); RemoteViews views = new RemoteViews(getPackageName(), R.layout.your_widget_layout); views.setTextViewText(R.id.tv_connection_status, isConnected ? "已连接" : "未连接"); appWidgetManager.updateAppWidget(widgetIds, views); }
内容的提问来源于stack exchange,提问作者Bhanu Pro

