Android 在片段和服务之间发送消息
Android sending messages between fragment and service
我有一个带按钮的片段。单击时,它会告诉服务开始轮询传感器,然后将传感器数据插入后台线程上的数据库中。再次按下按钮时,服务将停止。当按下停止按钮时,执行程序队列中可能仍有任务正在插入数据库,因此在此期间我想显示一个进度对话框,并在整个队列清除后将其关闭。带有按钮的片段如下所示:
public class StartFragment extends Fragment implements View.OnClickListener {
Button startButton;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_start, container, false);
startButton = (Button) view.findViewById(R.id.startButton);
startButton.setOnClickListener(this);
return view;
}
@Override
public void onClick(View v) {
if (recording has not yet started){
mainActivity.startService(new Intent(mainActivity, SensorService.class));
} else {
//I want to display a progress dialog here when the service is told to stop
//Once all executor task queue is clear, I want to dismiss this dialog
mainActivity.stopService(new Intent(mainActivity, SensorService.class));
}
}
}
第一次点击按钮时,会启动以下服务:
public class SensorService extends Service implements SensorEventListener {
public static final int SCREEN_OFF_RECEIVER_DELAY = 100;
private SensorManager sensorManager = null;
private WakeLock wakeLock = null;
ExecutorService executor;
Runnable insertHandler;
private void registerListener() {
//register 4 sensor listeners (acceleration, gyro, magnetic, gravity)
}
private void unregisterListener() {
sensorManager.unregisterListener(this);
}
public BroadcastReceiver receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
Log.i(TAG, "onReceive("+intent+")");
if (!intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
return;
}
Runnable runnable = new Runnable() {
public void run() {
Log.i(TAG, "Runnable executing...");
unregisterListener();
registerListener();
}
};
new Handler().postDelayed(runnable, SCREEN_OFF_RECEIVER_DELAY);
}
};
public void onSensorChanged(SensorEvent event) {
//get sensor values and store into 4 different arrays here
//insert into database in background thread
executor.execute(insertHandler);
}
@Override
public void onCreate() {
super.onCreate();
//get sensor manager and sensors here
PowerManager manager = (PowerManager) getSystemService(Context.POWER_SERVICE);
wakeLock = manager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
registerReceiver(receiver, new IntentFilter(Intent.ACTION_SCREEN_OFF));
//Executor service and runnable for DB inserts
executor = Executors.newSingleThreadExecutor();
insertHandler = new InsertHandler();
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
super.onStartCommand(intent, flags, startId);
startForeground(Process.myPid(), new Notification());
registerListener();
wakeLock.acquire();
return START_STICKY;
}
@Override
public void onDestroy() {
//Prevent new tasks from being added to thread
executor.shutdown();
try {
//Wait for all tasks to finish before we proceed
while (!executor.awaitTermination(1, TimeUnit.SECONDS)) {
Log.i(TAG, "Waiting for current tasks to finish");
}
} catch (InterruptedException e) {
executor.shutdownNow();
Thread.currentThread().interrupt();
}
if (executor.isTerminated()){
//Stop everything else once the task queue is clear
unregisterReceiver(receiver);
unregisterListener();
wakeLock.release();
dbHelper.close();
stopForeground(true);
//Once the queue is clear, I want to send a message back to the fragment to dismiss the progress dialog here
}
}
class InsertHandler implements Runnable {
public void run() {
//get sensor values from 4 arrays, and insert into db here
}
}
所以我想在第二次按下按钮时显示对话框。然后一旦再次按下,服务就会停止,我想等到队列清空后再向片段发送一个关闭事件以关闭进度对话框。
显示对话框很容易。我可以在片段的 onClick
方法中添加进度对话框代码,在调用 stopService
之前
我很难弄清楚如何在 SensorService
的 onDestroy
中发回消息以关闭该对话框
在不借助外部库的情况下执行此操作的最佳方法是什么?
有什么方法可以使用我在 SensorService
中使用的 BroadcastReceiver
吗?或者最好在片段中创建一个新的 Handler
并 以某种方式 将其传递给服务以便它可以将消息发送回片段?
编辑:
我已经根据以下答案之一尝试了以下方法:
在我的片段中添加了 MessageHandler
class class:
public static class MessageHandler extends Handler {
@Override
public void handleMessage(Message message) {
int state = message.arg1;
switch (state) {
case 0:
stopDialog.dismiss();
break;
case 1:
stopDialog = new ProgressDialog(mainActivity);
stopDialog.setMessage("Stopping...");
stopDialog.setTitle("Saving data");
stopDialog.setProgressNumberFormat(null);
stopDialog.setCancelable(false);
stopDialog.setMax(100);
stopDialog.show();
break;
}
}
}
在我的片段中创建了一个 MessageHandler
的新实例(尝试将其放置在不同的地方...相同的结果):
public static Handler messageHandler = new MessageHandler();
然后使用我的片段启动服务:
Intent startService = new Intent(mainActivity, SensorService.class);
startService.putExtra("MESSENGER", new Messenger(messageHandler));
getContext().startService(startService);
在我的 SensorService
BroadcastReceiver
中,我创建了 messageHandler:
Bundle extras = intent.getExtras();
messageHandler = (Messenger) extras.get("MESSENGER");
然后我在 SensorService
的开头显示对话框 onDestroy
:
sendMessage("SHOW");
并在同一方法的最后关闭它:
sendMessage("HIDE");
我的 sendMessage
方法如下所示:
public void sendMessage(String state) {
Message message = Message.obtain();
switch (state) {
case "SHOW":
message.arg1 = 1;
break;
case "HIDE" :
message.arg1 = 0;
break;
}
try {
messageHandler.send(message);
} catch (RemoteException e) {
e.printStackTrace();
}
}
所以我可以正常启动服务,但是当我再次按下它停止时,我得到这个:
java.lang.RuntimeException:无法停止服务com.example.app.SensorService@21124f0:java.lang.NullPointerException:尝试在空对象引用
上调用虚拟方法'void android.os.Messenger.send(android.os.Message)'
并且它指的是 SensorService
的第 105 行,其中我有 messageHandler.send(message)
关于可能有什么问题的想法?
我建议通过处理程序消息执行此操作:您将消息从服务发送到您的 Activity,它必须注册为回调处理程序(实现 http://developer.android.com/reference/android/os/Handler.Callback.html)。使用自定义消息代码 (message.what) 并收听它。请记住将其发送到应用程序的主循环器(来自服务)。
您还可以查看此评论,它说明了这种与更多代码的交互:
事实证明,我原来问题的 Edit 中的代码有效,但我必须改写我的一些代码:
Bundle extras = intent.getExtras();
messageHandler = (Messenger) extras.get("MESSENGER");
以上需要移动到 SensorService
的 onStartCommand
而不是 BroadcastReceiver
在activity中:
protected BroadcastReceiver mMessageReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, final Intent intent) {
runOnUiThread(new Runnable() {
@Override
public void run() {
if(intent.hasExtra("someExtraMessage")){
doSomething(intent.getStringExtra("someExtraMessage"));
}
}
});
}
};
@Override
public void onCreate(Bundle savedInstanceState, PersistableBundle persistentState) {
super.onCreate(savedInstanceState, persistentState);
LocalBroadcastManager.getInstance(this).registerReceiver(mMessageReceiver,
new IntentFilter("message-id"));
}
protected void onDestroy() {
super.onDestroy();
LocalBroadcastManager.getInstance(this).unregisterReceiver(mMessageReceiver);
}
public void doSomething(){
//...
}
然后从服务的某个地方:
Context context = BamBamApplication.getApplicationContext(); // Can be application or activity context.
// BamBamApplicaiton extends Application ;)
Intent intent = new Intent("message-id");
intent.putExtra("someExtraMessage", "Some Message :)");
LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
实际上你从一开始就做错了:)所有服务都在主线程上运行,所以在这里你最好开始所有硬处理到异步任务以将其移动到后台否则你会卡住你的应用程序,否则你会突然意外崩溃。
这是异步任务示例,它在后台解析 json api 响应,并通过参数输入结果。
class ParseJsonInBackground<T> extends AsyncTask<String, Void, ApiResponseModel<T>> {
private ProcessResponse<T> func;
private Type inClass;
public ParseJsonInBackground(ProcessResponse<T> f, Type inClass){
this.func = f;
this.inClass = inClass;
}
@Override
protected void onPreExecute() {
super.onPreExecute();
}
@Override
protected ApiResponseModel<T> doInBackground(String... json) {
Gson gson = new Gson();
try {
ApiResponseModel<T> result = (ApiResponseModel<T>) gson.fromJson(json[0], inClass);
return result;
}catch(Exception e){
ApiResponseModel<T> result = new ApiResponseModel<T>();
result.data = null;
result.success = false;
result.error = new ArrayList<>();
result.error.add(new ErrorModel(0, "Parsing error", "Parsing error"));
return result;
}
}
@Override
protected void onPostExecute(ApiResponseModel<T> result) {
Utils.hideLoadingProgress(mContext);
if(result != null && func != null){
if(result.success){
func.onSuccess(result);
}else{
func.onError(result);
}
}
}
}
并示例如何调用:
new ParseJsonInBackground<T>(responseFunc, inClass).execute(json.toString());
注意! - 不要在处理过程中使用任何视图,因为这会卡住主线程,在类似的异步任务中进行数据库处理,不要经常写入数据库进行事务记录。
我有一个带按钮的片段。单击时,它会告诉服务开始轮询传感器,然后将传感器数据插入后台线程上的数据库中。再次按下按钮时,服务将停止。当按下停止按钮时,执行程序队列中可能仍有任务正在插入数据库,因此在此期间我想显示一个进度对话框,并在整个队列清除后将其关闭。带有按钮的片段如下所示:
public class StartFragment extends Fragment implements View.OnClickListener {
Button startButton;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_start, container, false);
startButton = (Button) view.findViewById(R.id.startButton);
startButton.setOnClickListener(this);
return view;
}
@Override
public void onClick(View v) {
if (recording has not yet started){
mainActivity.startService(new Intent(mainActivity, SensorService.class));
} else {
//I want to display a progress dialog here when the service is told to stop
//Once all executor task queue is clear, I want to dismiss this dialog
mainActivity.stopService(new Intent(mainActivity, SensorService.class));
}
}
}
第一次点击按钮时,会启动以下服务:
public class SensorService extends Service implements SensorEventListener {
public static final int SCREEN_OFF_RECEIVER_DELAY = 100;
private SensorManager sensorManager = null;
private WakeLock wakeLock = null;
ExecutorService executor;
Runnable insertHandler;
private void registerListener() {
//register 4 sensor listeners (acceleration, gyro, magnetic, gravity)
}
private void unregisterListener() {
sensorManager.unregisterListener(this);
}
public BroadcastReceiver receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
Log.i(TAG, "onReceive("+intent+")");
if (!intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
return;
}
Runnable runnable = new Runnable() {
public void run() {
Log.i(TAG, "Runnable executing...");
unregisterListener();
registerListener();
}
};
new Handler().postDelayed(runnable, SCREEN_OFF_RECEIVER_DELAY);
}
};
public void onSensorChanged(SensorEvent event) {
//get sensor values and store into 4 different arrays here
//insert into database in background thread
executor.execute(insertHandler);
}
@Override
public void onCreate() {
super.onCreate();
//get sensor manager and sensors here
PowerManager manager = (PowerManager) getSystemService(Context.POWER_SERVICE);
wakeLock = manager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
registerReceiver(receiver, new IntentFilter(Intent.ACTION_SCREEN_OFF));
//Executor service and runnable for DB inserts
executor = Executors.newSingleThreadExecutor();
insertHandler = new InsertHandler();
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
super.onStartCommand(intent, flags, startId);
startForeground(Process.myPid(), new Notification());
registerListener();
wakeLock.acquire();
return START_STICKY;
}
@Override
public void onDestroy() {
//Prevent new tasks from being added to thread
executor.shutdown();
try {
//Wait for all tasks to finish before we proceed
while (!executor.awaitTermination(1, TimeUnit.SECONDS)) {
Log.i(TAG, "Waiting for current tasks to finish");
}
} catch (InterruptedException e) {
executor.shutdownNow();
Thread.currentThread().interrupt();
}
if (executor.isTerminated()){
//Stop everything else once the task queue is clear
unregisterReceiver(receiver);
unregisterListener();
wakeLock.release();
dbHelper.close();
stopForeground(true);
//Once the queue is clear, I want to send a message back to the fragment to dismiss the progress dialog here
}
}
class InsertHandler implements Runnable {
public void run() {
//get sensor values from 4 arrays, and insert into db here
}
}
所以我想在第二次按下按钮时显示对话框。然后一旦再次按下,服务就会停止,我想等到队列清空后再向片段发送一个关闭事件以关闭进度对话框。
显示对话框很容易。我可以在片段的 onClick
方法中添加进度对话框代码,在调用 stopService
之前
我很难弄清楚如何在 SensorService
的 onDestroy
中发回消息以关闭该对话框
在不借助外部库的情况下执行此操作的最佳方法是什么?
有什么方法可以使用我在 SensorService
中使用的 BroadcastReceiver
吗?或者最好在片段中创建一个新的 Handler
并 以某种方式 将其传递给服务以便它可以将消息发送回片段?
编辑:
我已经根据以下答案之一尝试了以下方法:
在我的片段中添加了 MessageHandler
class class:
public static class MessageHandler extends Handler {
@Override
public void handleMessage(Message message) {
int state = message.arg1;
switch (state) {
case 0:
stopDialog.dismiss();
break;
case 1:
stopDialog = new ProgressDialog(mainActivity);
stopDialog.setMessage("Stopping...");
stopDialog.setTitle("Saving data");
stopDialog.setProgressNumberFormat(null);
stopDialog.setCancelable(false);
stopDialog.setMax(100);
stopDialog.show();
break;
}
}
}
在我的片段中创建了一个 MessageHandler
的新实例(尝试将其放置在不同的地方...相同的结果):
public static Handler messageHandler = new MessageHandler();
然后使用我的片段启动服务:
Intent startService = new Intent(mainActivity, SensorService.class);
startService.putExtra("MESSENGER", new Messenger(messageHandler));
getContext().startService(startService);
在我的 SensorService
BroadcastReceiver
中,我创建了 messageHandler:
Bundle extras = intent.getExtras();
messageHandler = (Messenger) extras.get("MESSENGER");
然后我在 SensorService
的开头显示对话框 onDestroy
:
sendMessage("SHOW");
并在同一方法的最后关闭它:
sendMessage("HIDE");
我的 sendMessage
方法如下所示:
public void sendMessage(String state) {
Message message = Message.obtain();
switch (state) {
case "SHOW":
message.arg1 = 1;
break;
case "HIDE" :
message.arg1 = 0;
break;
}
try {
messageHandler.send(message);
} catch (RemoteException e) {
e.printStackTrace();
}
}
所以我可以正常启动服务,但是当我再次按下它停止时,我得到这个:
java.lang.RuntimeException:无法停止服务com.example.app.SensorService@21124f0:java.lang.NullPointerException:尝试在空对象引用
上调用虚拟方法'void android.os.Messenger.send(android.os.Message)' 并且它指的是 SensorService
的第 105 行,其中我有 messageHandler.send(message)
关于可能有什么问题的想法?
我建议通过处理程序消息执行此操作:您将消息从服务发送到您的 Activity,它必须注册为回调处理程序(实现 http://developer.android.com/reference/android/os/Handler.Callback.html)。使用自定义消息代码 (message.what) 并收听它。请记住将其发送到应用程序的主循环器(来自服务)。
您还可以查看此评论,它说明了这种与更多代码的交互:
事实证明,我原来问题的 Edit 中的代码有效,但我必须改写我的一些代码:
Bundle extras = intent.getExtras();
messageHandler = (Messenger) extras.get("MESSENGER");
以上需要移动到 SensorService
的 onStartCommand
而不是 BroadcastReceiver
在activity中:
protected BroadcastReceiver mMessageReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, final Intent intent) {
runOnUiThread(new Runnable() {
@Override
public void run() {
if(intent.hasExtra("someExtraMessage")){
doSomething(intent.getStringExtra("someExtraMessage"));
}
}
});
}
};
@Override
public void onCreate(Bundle savedInstanceState, PersistableBundle persistentState) {
super.onCreate(savedInstanceState, persistentState);
LocalBroadcastManager.getInstance(this).registerReceiver(mMessageReceiver,
new IntentFilter("message-id"));
}
protected void onDestroy() {
super.onDestroy();
LocalBroadcastManager.getInstance(this).unregisterReceiver(mMessageReceiver);
}
public void doSomething(){
//...
}
然后从服务的某个地方:
Context context = BamBamApplication.getApplicationContext(); // Can be application or activity context.
// BamBamApplicaiton extends Application ;)
Intent intent = new Intent("message-id");
intent.putExtra("someExtraMessage", "Some Message :)");
LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
实际上你从一开始就做错了:)所有服务都在主线程上运行,所以在这里你最好开始所有硬处理到异步任务以将其移动到后台否则你会卡住你的应用程序,否则你会突然意外崩溃。
这是异步任务示例,它在后台解析 json api 响应,并通过参数输入结果。
class ParseJsonInBackground<T> extends AsyncTask<String, Void, ApiResponseModel<T>> {
private ProcessResponse<T> func;
private Type inClass;
public ParseJsonInBackground(ProcessResponse<T> f, Type inClass){
this.func = f;
this.inClass = inClass;
}
@Override
protected void onPreExecute() {
super.onPreExecute();
}
@Override
protected ApiResponseModel<T> doInBackground(String... json) {
Gson gson = new Gson();
try {
ApiResponseModel<T> result = (ApiResponseModel<T>) gson.fromJson(json[0], inClass);
return result;
}catch(Exception e){
ApiResponseModel<T> result = new ApiResponseModel<T>();
result.data = null;
result.success = false;
result.error = new ArrayList<>();
result.error.add(new ErrorModel(0, "Parsing error", "Parsing error"));
return result;
}
}
@Override
protected void onPostExecute(ApiResponseModel<T> result) {
Utils.hideLoadingProgress(mContext);
if(result != null && func != null){
if(result.success){
func.onSuccess(result);
}else{
func.onError(result);
}
}
}
}
并示例如何调用:
new ParseJsonInBackground<T>(responseFunc, inClass).execute(json.toString());
注意! - 不要在处理过程中使用任何视图,因为这会卡住主线程,在类似的异步任务中进行数据库处理,不要经常写入数据库进行事务记录。