Android - 在未知时间点将数据从 Service 传递到 main

Android - passing data from Service to main at unknown point in time

我在 SO 上查看了其他一些问题,none 似乎在回答我的问题。我有一个带有简单按钮的主 activity。现在它调用 startService(new Intent(MainActivity.this, BluetoothScanner.class) 它一直运行直到找到另一个蓝牙设备。完成后,我希望 BluetoothScanner 报告它已完成,并且 return 一些东西(可能是一个字符串)到主 activity,所以我可以创建一个弹出窗口或通知用户的东西在附近发现了蓝牙设备。我该如何最好地解决这个问题?

按钮看起来像:

public void onClick(View v) {
    switch(on) {
        case 0:
            startService(new Intent(this, BluetoothScanner.class));
            on = 1;
            break;
        case 1:
            stopService(new Intent(...);
            on = 0;
            break;
       }
 } 
});

似乎有一些替代方案,例如全局变量(坏)、resultReceivers、意图中的捆绑内容。但我不确定该怎么做。你会建议什么解决我的问题?

此致

我建议使用事件总线。让服务 post 成为 BluetoothScanCompletedEvent 或类似的服务。让您的 activity 在总线上订阅以监听此类事件并在事件到达时开始工作。

我个人的偏好是 greenrobot's EventBus. LocalBroadcastManager is in the Android Support package and would also fill the role. Square's Otto 是 Android 的第三个主要事件总线,但它的线程方法可能不适合您的用例。

This directory 包含演示所有三个事件总线实现的示例项目。

有很多方法可以做到这一点,最好取决于您的具体情况。一种没有外部依赖性的简单解决方案是在 Activity 中使用 BroadcastReceiver,覆盖 onReceive() 并在那里做任何您想做的事情,例如弹出 Dialog 显示您的 Service 返回的一些 String。在你的 Service 然后你会做你需要做的,当你完成后,使用 LocalBroadcastManager 发送一个广播 sendBroadcast.

这是一个最小的例子(MCVE 展示了如何做到这一点(没有蓝牙部分,从你的问题看来这不是你的问题):

示例ActionBarActivity

public class MainActivity extends ActionBarActivity {
    private Dialog dialog;

    private BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            dialog = new AlertDialog.Builder(MainActivity.this)
                    .setTitle("Some title")
                    .setMessage(intent.getExtras().getString("payload"))
                    .setPositiveButton("OK", new DialogInterface.OnClickListener() {
                        public void onClick(DialogInterface dialog, int id) {
                        }
                    })
                    .create();
            dialog.show();
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        LinearLayout layout = new LinearLayout(this);
        Button button = new Button(this);
        button.setText("Click!");
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                startService(new Intent(MainActivity.this, MyService.class));
            }
        });
        layout.addView(button);
        setContentView(layout);
        LocalBroadcastManager.getInstance(this).registerReceiver(broadcastReceiver, new IntentFilter("MyAction"));
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        LocalBroadcastManager.getInstance(this).unregisterReceiver(broadcastReceiver);
        if (dialog != null && dialog.isShowing())
            dialog.dismiss();
    }

}

示例Service

public class MyService extends Service {
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Handler handler = new Handler();
        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                LocalBroadcastManager.getInstance(MyService.this).sendBroadcast(new Intent("MyAction").putExtra("payload", "Some string"));
                stopSelf();
            }
        }, 3000);
        return START_NOT_STICKY;
    }
}

示例AndroidManifest.xml

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.testso" >

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="TestSO"
        android:theme="@style/Theme.AppCompat" >
        <activity
            android:name=".MainActivity"
            android:label="TestSO" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <service
            android:name=".MyService"
            android:exported="false" />
    </application>

</manifest>