IntentService, run out

Keywords: Android ButterKnife

IntentService

IntentService, which can be seen as a combination of Service and Handler Thread, stops automatically after completing its mission, and is suitable for scenarios where UI-independent tasks need to be handled by worker threads.

  • IntentService is a class that inherits from Service and handles asynchronous requests. There is a worker thread in IntentService to handle time-consuming operations.
  • When the task is finished, IntentService will stop automatically, and we don't need to finish it manually.
  • If IntentService is started many times, then each time-consuming operation will be executed in the way of work queue in the onHandle Intent callback method of IntentService, which will be executed sequentially and automatically terminated by serial mode.

Example

Here's an example. Click Start to start an IntentService to update the progress bar, and the IntentService will automatically end when the update is completed. If multiple clicks start, the onDestroy method will be executed multiple times, and after multiple executions, the IntentService will execute the onDestroy method.


IntentService:

package com.bourne.android_common.ServiceDemo;

import android.app.IntentService;
import android.content.Intent;
import android.support.v4.content.LocalBroadcastManager;

import com.bourne.common_library.utils.Logout;

public class MyIntentService extends IntentService {

    /**
     * Is it running?
     */
    private boolean isRunning;

    /**
     *Speed of progress
     */
    private int count;

    /**
     * Radio broadcast
     */
    private LocalBroadcastManager mLocalBroadcastManager;

    public MyIntentService() {
        super("MyIntentService");
        Logout.e("MyIntentService");
    }

    @Override
    public void onCreate() {
        super.onCreate();
        Logout.e("onCreate");
        mLocalBroadcastManager = LocalBroadcastManager.getInstance(this);
    }

    @Override
    protected void onHandleIntent(Intent intent) {
        Logout.e("onHandleIntent");
        try {
            Thread.sleep(1000);
            isRunning = true;
            count = 0;
            while (isRunning) {
                count++;
                if (count >= 100) {
                    isRunning = false;
                }
                Thread.sleep(50);
                sendThreadStatus("Thread Running...", count);
            }

        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    /**
     * Send progress messages
     */
    private void sendThreadStatus(String status, int progress) {
        Intent intent = new Intent(IntentServiceActivity.ACTION_TYPE_THREAD);
        intent.putExtra("status", status);
        intent.putExtra("progress", progress);
        mLocalBroadcastManager.sendBroadcast(intent);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Logout.e("Thread End Running..." + count);
    }
}

After starting, the construction method is executed, then the onCreate method is executed, and then the onHandleIntent method is executed. In onHandleIntent, schedule increases by 50ms each time, and broadcasts to Activity and delivers progress data.

IntentServiceActivity:

package com.bourne.android_common.ServiceDemo;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.support.v4.content.LocalBroadcastManager;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.ProgressBar;
import android.widget.TextView;

import com.bourne.android_common.R;

import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;

public class IntentServiceActivity extends AppCompatActivity {
    /**
     * Status text
     */
    @BindView(R.id.tv_status)
    TextView tv_status;

    /**
     * Progress Text
     */
    @BindView(R.id.tv_progress)
    TextView tv_progress;

    /**
     * Progress bar
     */
    @BindView(R.id.progressbar)
    ProgressBar progressbar;

    private LocalBroadcastManager mLocalBroadcastManager;
    private MyBroadcastReceiver mBroadcastReceiver;
    public final static String ACTION_TYPE_THREAD = "action.type.thread";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_intent_service);
        ButterKnife.bind(this);

        //Registered Broadcasting
        mLocalBroadcastManager = LocalBroadcastManager.getInstance(this);
        mBroadcastReceiver = new MyBroadcastReceiver();
        IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction(ACTION_TYPE_THREAD);
        mLocalBroadcastManager.registerReceiver(mBroadcastReceiver, intentFilter);

        initView();
    }

    public void initView() {
        tv_status.setText("Thread state: not running");
        progressbar.setMax(100);
        progressbar.setProgress(0);
        tv_progress.setText("0%");
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        //Cancellation of broadcasting
        mLocalBroadcastManager.unregisterReceiver(mBroadcastReceiver);
    }

    public class MyBroadcastReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            switch (intent.getAction()) {

                case ACTION_TYPE_THREAD:
                    //Changing UI
                    int progress = intent.getIntExtra("progress", 0);
                    tv_status.setText("Thread state:" + intent.getStringExtra("status"));
                    progressbar.setProgress(progress);
                    tv_progress.setText(progress + "%");
                    if (progress >= 100) {
                        tv_status.setText("Thread termination");
                    }
                    break;
            }
        }
    }

    @OnClick({R.id.btn_start})
    public void onClick(View view) {
        switch (view.getId()) {
            case R.id.btn_start:
                Intent intent = new Intent(IntentServiceActivity.this, MyIntentService.class);
                startService(intent);
                break;
        }
    }
}

Hit the Start button to start MyIntentService. MLocalBroadcast Manager. RegiserReceiver (mBroadcast Receiver, intentFilter) registers broadcasts, receives broadcast messages and data, and changes progress bar at all times.

Register MyIntentService

        <service
            android:name=".ServiceDemo.MyIntentService">
        </service>

IntentService Source Code Analysis

Source code

package android.app;

import android.annotation.WorkerThread;
import android.annotation.Nullable;
import android.content.Intent;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;

public abstract class IntentService extends Service {
    private volatile Looper mServiceLooper;
    private volatile ServiceHandler mServiceHandler;
    private String mName;
    private boolean mRedelivery;

    private final class ServiceHandler extends Handler {
        public ServiceHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) {
            onHandleIntent((Intent)msg.obj);
            stopSelf(msg.arg1);
        }
    }

    /**
     * Creates an IntentService.  Invoked by your subclass's constructor.
     */
    public IntentService(String name) {
        super();
        mName = name;
    }

    /**
     * Sets intent redelivery preferences.  Usually called from the constructor
     */
    public void setIntentRedelivery(boolean enabled) {
        mRedelivery = enabled;
    }

    @Override
    public void onCreate() {
        // TODO: It would be nice to have an option to hold a partial wakelock
        // during processing, and to have a static startService(Context, Intent)
        // method that would launch the service & hand off a wakelock.

        super.onCreate();
        HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
        thread.start();

        mServiceLooper = thread.getLooper();
        mServiceHandler = new ServiceHandler(mServiceLooper);
    }

    @Override
    public void onStart(@Nullable Intent intent, int startId) {
        Message msg = mServiceHandler.obtainMessage();
        msg.arg1 = startId;
        msg.obj = intent;
        mServiceHandler.sendMessage(msg);
    }

    /**
     * You should not override this method for your IntentService. Instead,
     */
    @Override
    public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
        onStart(intent, startId);
        return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
    }

    @Override
    public void onDestroy() {
        mServiceLooper.quit();
    }

    /**
     * Unless you provide binding for your service, you don't need to implement this
     */
    @Override
    @Nullable
    public IBinder onBind(Intent intent) {
        return null;
    }

    /**
     */
    @WorkerThread
    protected abstract void onHandleIntent(@Nullable Intent intent);
}

IntentService inherits from Service and has a HandlerThread object inside.

In onCreate, a HandlerThread object is created and threads are started. Next, a ServiceHandler object is created, which inherits from the Handler and is used to process messages. ServiceHandler will get the Looper of Handler Thread and start working properly.

Each time the onStart method is started, several messages and data are sent to mService Handler, which is equivalent to a message queue that sends a message to Handler Thread. mServiceHandler passes data to an onHandleIntent method, which is an abstract method that needs to be implemented in IntentService, so every time after the onStart method, we call our own onHandleIntent method to process it. After processing, use stopSelf to inform Handler Thread that it has finished processing, Handler Thread continues to observe the message queue, and if there are any messages that have not been played, it continues to execute, otherwise it ends.


Why don't you need a new thread to start IntentService?

Handler Thread in IntentService inherits from Thread and encapsulates Looper internally, where new threads are created and started, so starting IntentService does not require new threads.


Why not suggest starting IntentService through bindService()?

@Override
public IBinder onBind(Intent intent) {
    return null;
}

onBind() in the source code of IntentService returns null by default; it is not suitable for bindService() to start the service. If you insist on bindService() to start the IntentService, maybe because you want to make IntentService and Active Service communicate through Binder or Messenger, then onHandleIntent() will not. Callback is equivalent to using Service instead of IntentService.


Why do IntentService start multiple times to execute events sequentially, and subsequent events can't be executed after service stops?

@Override
public void onStart(Intent intent, int startId) {
    Message msg = mServiceHandler.obtainMessage();
    msg.arg1 = startId;
    msg.obj = intent;
    mServiceHandler.sendMessage(msg);
}

@Override
public void onDestroy() {
    mServiceLooper.quit();
}

The Hadler, Looper and MessageQueue mechanisms used in IntentService send messages to threads for execution, so starting IntentService multiple times will not recreate new services and threads, but only add messages to message queues for execution, and if the service stops, it will clear the message queue. Messages, follow-up events are not executed.

Reference Articles

Posted by abhi_madhani on Wed, 17 Jul 2019 14:20:16 -0700