Android在片段和服务之间发送消息 [英] Android sending messages between fragment and service

查看:73
本文介绍了Android在片段和服务之间发送消息的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个带有按钮的片段.当单击时,它告诉服务开始轮询传感器,然后将传感器数据插入后台线程的数据库中.当再次按下按钮时,服务将停止.当按下停止"按钮时,执行者队列中仍可能有一些任务正在插入到数据库中,因此在此期间,我想显示一个进度对话框,并在清除整个队列后将其关闭.带有按钮的片段如下所示:

I have a fragment with a button. When clicked it tells a service to start polling sensors and then insert the sensor data into a database on a background thread. When the button is pushed again, the service will stop. When the Stop button is pushed, there may still be tasks in the executor queue that is inserting into the DB, so during this time I want to display a progress dialog, and dismiss it once the entire queue is clear. The fragment with the button looks like this:

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));
        }
    }
}

首次单击该按钮时,将启动以下服务:

When the button is clicked the first time, the following service will start:

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
    }
}

所以我想在按下第二个按钮时显示对话框.然后再次按下该按钮,服务将停止,我要等到队列清除后再将dismiss事件发送回该片段,以关闭进度对话框.

So I want to display the dialog on the 2nd button press. Then once it is pressed again, service will stop, and I want to wait until the queue is clear and then send a dismiss event back to the fragment to dismiss the progress dialog.

显示对话框很容易.我可以在调用stopService之前在片段的onClick方法中添加进度对话框代码

Showing the dialog is easy. I can just add progress dialog code in the onClick method of the fragment, before stopService is called

我很难弄清楚如何在SensorServiceonDestroy中发回消息以消除该对话框

I'm having difficulty with figuring out how to send a message back in onDestroy of the SensorService to dismiss that dialog

在不借助外部库的情况下实现此目的的最佳方法是什么?

Whats the best way of doing this without resorting to external libraries?

是否可以使用我在SensorService中使用的BroadcastReceiver的某种方式?还是最好在片段中创建一个新的Handler,然后以某种方式将其传递给服务,以便它可以将消息发送回该片段?

Is there some way that the BroadcastReceiver I'm using in SensorService can be used? Or maybe it's better to create a new Handler in the fragment and somehow pass it through to the service so it can send a message back to the fragment?

我已根据以下答案之一尝试了以下方法:

I have tried the following based on one of the answers below:

在我的片段类中添加了MessageHandler类:

Added a MessageHandler class to my fragment 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的新实例(试图将其放置在各个地方...结果相同):

Created a new instance of MessageHandler in my fragment (tried placing this in a variety of places...same results):

public static Handler messageHandler = new MessageHandler();

然后使用以下命令从我的片段启动服务:

The service is then started from my fragment using:

Intent startService = new Intent(mainActivity, SensorService.class);
startService.putExtra("MESSENGER", new Messenger(messageHandler));
getContext().startService(startService);

在我的SensorService BroadcastReceiver中,我创建了messageHandler:

In my SensorService BroadcastReceiver I create the messageHandler:

Bundle extras = intent.getExtras();
messageHandler = (Messenger) extras.get("MESSENGER");

然后我在SensorService onDestroy的开头显示对话框:

Then I show the dialog at the very beginning of SensorService onDestroy:

sendMessage("SHOW");

并在同一方法结束时将其关闭:

and dismiss it at the very end of that same method:

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();
        }
    }

这样我就可以启动服务正常"了,但是当我再次按它停止时,我得到了:

So I can start the Service OK, but when I press it again to stop, I get this:

java.lang.RuntimeException:无法停止服务com.example.app.SensorService@21124f0:java.lang.NullPointerException:尝试调用虚拟方法'void android.os.Messenger.send(android.os.Message) '在空对象引用上

java.lang.RuntimeException: Unable to stop service com.example.app.SensorService@21124f0: java.lang.NullPointerException: Attempt to invoke virtual method 'void android.os.Messenger.send(android.os.Message)' on a null object reference

及其对SensorService的行105的引用,其中我有messageHandler.send(message)

and its referring to Line 105 of SensorService where I have messageHandler.send(message)

关于可能出什么问题的想法?

Thoughts on what might be wrong?

推荐答案

原来,我的原始问题"的编辑"中的代码有效,但是我必须重新整理一下一些代码:

It turns out that the code in the Edit of my original question works, but I have to shuffle around some of my code:

Bundle extras = intent.getExtras();
messageHandler = (Messenger) extras.get("MESSENGER");

以上内容需要移到SensorServiceonStartCommand中,而不是放在BroadcastReceiver

The above needs to be moved to onStartCommand of SensorService instead of being in the BroadcastReceiver

这篇关于Android在片段和服务之间发送消息的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆