У меня есть фрагмент с кнопкой. При щелчке он сообщает службе начать опрос датчиков, а затем вставить данные датчиков в базу данных в фоновом потоке. При повторном нажатии кнопки служба останавливается. Когда нажата кнопка «Стоп», в очереди исполнителя, которая вставляется в базу данных, все еще могут быть задачи, поэтому в это время я хочу отобразить диалоговое окно выполнения и закрыть его, как только вся очередь будет очищена. Фрагмент с кнопкой выглядит так:

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

Итак, я хочу отобразить диалог при 2-м нажатии кнопки. Затем, когда он снова будет нажат, служба остановится, и я хочу подождать, пока очередь не очистится, а затем отправить событие отклонения обратно во фрагмент, чтобы закрыть диалоговое окно выполнения.

Показать диалог очень просто. Я могу просто добавить код диалога выполнения в метод onClick фрагмента до того, как будет вызван stopService

Мне сложно понять, как отправить сообщение обратно в onDestroy из SensorService, чтобы закрыть это диалоговое окно

Как лучше всего это сделать, не прибегая к внешним библиотекам?

Можно ли каким-то образом использовать BroadcastReceiver, который я использую в SensorService? Или, может быть, лучше создать новый Handler во фрагменте и как-то передать его службе, чтобы она могла отправить сообщение обратно фрагменту?

РЕДАКТИРОВАТЬ:

Я пробовал следующее, основываясь на одном из ответов ниже:

К моему классу фрагмента добавлен класс MessageHandler:

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) на ссылка на нулевой объект

И это относится к строке 105 SensorService, где у меня есть messageHandler.send(message)

Мысли о том, что может быть не так?

3
Simon 20 Апр 2016 в 08:07

3 ответа

Лучший ответ

Оказывается, код в редактировании моего исходного вопроса работает, но мне нужно перетасовать часть моего кода:

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

Вышеуказанное необходимо переместить в onStartCommand из SensorService вместо того, чтобы находиться в BroadcastReceiver

0
Simon 21 Апр 2016 в 22:17

В действии:

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

Обращай внимание! - не используйте какие-либо представления при обработке, потому что это приведет к застреванию основного потока, сделает обработку базы данных аналогичной асинхронной задачей, не пишите часто в базу данных, делая запись с транзакциями.

3
Stepan Maksymov 24 Май 2016 в 23:26

Я бы предложил сделать это с помощью сообщений обработчика: вы отправляете сообщение из службы в свою активность, которая должна регистрироваться как обработчик обратного вызова (реализовать http://developer.android.com/reference/android/os/Handler.Callback.html). Используйте собственный код сообщения (message.what) и слушайте его. Не забывайте отправлять это в главный цикл вашего приложения (из службы).

Вы также можете проверить этот комментарий, который иллюстрирует такое взаимодействие с другим кодом: https://stackoverflow.com/a/20595215/4310905

0
Community 23 Май 2017 в 10:29