An overview of the

Service is one of the most commonly used components in Android development. In the previous study note, WE did a simple study of Service. Service is divided into background Service, foreground Service and binding Service. Either Service is implemented by inheriting Service, but it is started differently: StartForegroundService is used to start the background service, and startForegroundService is used to start the foreground service. You need to specify a notification to bind the current service, and bindService is used to bind the binding service.

When we use a Service, since the Service is also running on the main thread, we still start one or more worker threads to perform tasks for time-consuming operations. In this case, we need to manage and maintain these threads ourselves, which can lead to dangerous operations. If you don’t need to do more than one task at a time, Android recommends IntentService.

IntentService, a subclass of Service, uses worker threads to process startup requests one by one, which is the best option when you don’t need to process multiple tasks at once. We do this by inheriting the IntentService class and implementing the onHandleIntent() method, which receives each Intent that initiates the request so that we can pass the data we need to process.

The source code to preview

IntentService IntentService IntentService IntentService IntentService IntentService

  • ServiceSubclass, then the life cycle should andServiceIt is consistent and should be supportedstartServiceandbindServiceThere are two ways to start
  • Use worker thread specificationsIntentServiceHas helped us implement processing tasks on the worker thread
  • Processing startup requests one by one indicates that only one startup request can be processed at a time. If multiple requests exist, the first incoming request is processed first

IntentService: IntentService: IntentService: IntentService: IntentService

public abstract class IntentService extends Service {
    private volatile Looper mServiceLooper;
    @UnsupportedAppUsage
    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); }}public IntentService(String name) {
        super(a); mName = name; }public void setIntentRedelivery(boolean enabled) {
        mRedelivery = enabled;
    }
    
    @Override
    public void onCreate(a) {
        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);
    }
    @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(a) {
        mServiceLooper.quit();
    }
    @Override
    @Nullable
    public IBinder onBind(Intent intent) {
        return null;
    }
     @WorkerThread
    protected abstract void onHandleIntent(@Nullable Intent intent);
}

Copy the code

As you can see, there is very little source code for IntentService. Let’s look at the code in the Service lifecycle function.

onCreate()

  1. onCreate()

    Either way the Service is started, the onCreate() method is first executed and can only be executed once. We usually perform some initialization operations in this method, which now performs the following operations:

        @Override
        public void onCreate(a) {
            super.onCreate();
            HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
            thread.start();
    
            mServiceLooper = thread.getLooper();
            mServiceHandler = new ServiceHandler(mServiceLooper);
        }
    Copy the code

    Create HandlerThread object first, the following HandlerThread source:

    public class HandlerThread extends Thread {
        int mPriority;
        int mTid = -1;
        Looper mLooper;
        private @Nullable Handler mHandler;
    
        public HandlerThread(String name) {
            super(name);
            mPriority = Process.THREAD_PRIORITY_DEFAULT;
        }
        public HandlerThread(String name, int priority) {
            super(name);
            mPriority = priority;
        }
        protected void onLooperPrepared(a) {}public void run(a) {
            mTid = Process.myTid();
            Looper.prepare();
            synchronized (this) {
                mLooper = Looper.myLooper();
                notifyAll();
            }
            Process.setThreadPriority(mPriority);
            onLooperPrepared();
            Looper.loop();
            mTid = -1;
        }
        public Looper getLooper(a) {
            if(! isAlive()) {return null;
            }
            
            // If the thread has been started, wait until the looper has been created.
            synchronized (this) {
                while (isAlive() && mLooper == null) {
                    try {
                        wait();
                    } catch (InterruptedException e) {
                    }
                }
            }
            return mLooper;
        }
        @NonNull
        public Handler getThreadHandler(a) {
            if (mHandler == null) {
                mHandler = new Handler(getLooper());
            }
            return mHandler;
        }
        public boolean quit(a) {
            Looper looper = getLooper();
            if(looper ! =null) {
                looper.quit();
                return true;
            }
            return false;
        }
        public boolean quitSafely(a) {
            Looper looper = getLooper();
            if(looper ! =null) {
                looper.quitSafely();
                return true;
            }
            return false;
        }
        public int getThreadId(a) {
            returnmTid; }}Copy the code

    As you can see, the HandlerThread class inherits from Thread, so creating the HandlerThread object here creates a Thread object, and then calling thread.start() starts the Thread. Call the start() method and start a new thread to execute the run() method.

        public void run(a) {
            mTid = Process.myTid();
            Looper.prepare();
            synchronized (this) {
                mLooper = Looper.myLooper();
                notifyAll();
            }
            Process.setThreadPriority(mPriority);
            onLooperPrepared();
            Looper.loop();
            mTid = -1;
        }
    Copy the code

    In the new thread, the looper.prepare () method is first called.

        public static void prepare(a) {
            prepare(true);
        }
    
        private static void prepare(boolean quitAllowed) {
            if(sThreadLocal.get() ! =null) {
                throw new RuntimeException("Only one Looper may be created per thread");
            }
            sThreadLocal.set(new Looper(quitAllowed));
        }
    Copy the code

    We call prepare(true), an overloaded function, which gets a Looper object from ThreadLocal, raises an exception if it already exists, or creates a Looper object and stores it in ThreadLocal. Objects stored in ThreadLocal are bound to the current thread and cannot be manipulated by other threads. Enter the code that creates the Looper(True) object:

        private Looper(boolean quitAllowed) {
            mQueue = new MessageQueue(quitAllowed);
            mThread = Thread.currentThread();
        }
    Copy the code

    The MessageQueue(true) object is created and the current thread’s object is stored in Looper. Here is how to create MessageQueue:

        MessageQueue(boolean quitAllowed) {
            mQuitAllowed = quitAllowed;
            mPtr = nativeInit();
        }
    Copy the code

    We have created a message loop that is the same as the Handler we used before, except that we have been using it in the UI thread and have not cared how the message loop was created in the UI thread. At this point, both Looper and MessageQueue in the message loop mechanism have been created. Unlike the message loop mechanism in the UI thread, the message loop mechanism in the UI thread cannot exit, whereas the message loop mechanism here allows exit.

  2. Back to the handlerThread.run () method, where Looper and MessageQueue are created, the following code follows:

        @Override
        public void run(a) {
            mTid = Process.myTid();
            // This step creates Looper and MessageQueue
            Looper.prepare();
            synchronized (this) {
            	// Save the current Looper and wake up the thread
                mLooper = Looper.myLooper();
                notifyAll();
            }
            Process.setThreadPriority(mPriority);
            // This is an empty implementation method that subclasses can implement to do something
            onLooperPrepared();
            Call the loop() method
            Looper.loop();
            mTid = -1;
        }
    Copy the code

    NotifyAll () and looper.loop () methods are more important than notifyAll() and looper.loop () methods. For looper.loop () methods, notifyAll() and looper.loop () methods are notifyAll and looper.loop () methods. Internally, it retrieves the next Message in the MessageQueue through a for loop, which is an infinite loop and exits when it can’t get a Message.

  3. With the message loop created in the previous step, continue with the code in onCreate() :

    mServiceLooper = thread.getLooper();
    mServiceHandler = new ServiceHandler(mServiceLooper);
    Copy the code

    The ServiceHandler(mServiceLooper) method is created to pass in the Looper created in the previous step. The handlerThread.getLoOper () method is provided below:

        public Looper getLooper(a) {
            if(! isAlive()) {return null;
            }
            
            // If the thread has been started, wait until the looper has been created.
            synchronized (this) {
                while (isAlive() && mLooper == null) {
                    try {
                        wait();
                    } catch (InterruptedException e) {
                    }
                }
            }
            return mLooper;
        }
    
    Copy the code

    As you can see from the above code, if the current Looper is empty, then the thread will wait, because as we mentioned above, the Looper will be created when the run() method is executed. With this, we can see that if the Looper is not created, there will be a problem. So this is an infinite loop. If Looper is empty, wait until the notify() call is made to wake up the thread after Looper is created, and the Looper can be retrieved normally.

    ServiceHandler inherits from Handler, so we need to pass Looper when we create ServiceHandler. The ServiceHandler knows which thread to add the message to, as can be seen in the Handler constructor, as shown below:

        public Handler(@Nullable Callback callback, boolean async) {... Irrelevant code... mLooper = Looper.myLooper();if (mLooper == null) {
                throw new RuntimeException(
                    "Can't create handler inside thread " + Thread.currentThread()
                            + " that has not called Looper.prepare()");
            }
            mQueue = mLooper.mQueue;
            mCallback = callback;
            mAsynchronous = async;
        }
    Copy the code

    As you can see from the above code, if we do not specify Looper, we will use looper.mylooper () to get the Looper of the current thread. Since our Handler is created in the main thread, if we do not pass Looper, this will be the main thread Looper. If we don’t pass this Looper, ANR is likely to occur.

    At this point, we see what happens in the onCreate() method of the IntentService, essentially creating a HandlerThread, which is a worker thread, and binding event handling to that thread. You then create a ServiceHandler, which is a Handler for submitting and processing the data.

onStartCommand()

After starting IntentService with the startService() method, onCreate() is followed by the onStartCommand() method. Here is the source code for this method:

    @Override
    public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
        onStart(intent, startId);
        return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
    }
Copy the code

As you can see, the onStart() method is called directly and a constant is returned based on the value of the mRedelivery parameter. If mRedelivery is true, START_REDELIVER_INTENT is returned. This constant roughly means that if a Service is unexpectedly terminated, the last Intent delivered will be re-delivered to the Service when it is restarted. If mRedelivery is false, START_NOT_STICKY is returned. This constant means that if a service is terminated unexpectedly, it will not be restarted until the context.startService() method is called again. The last Intent data is not submitted.

We can set this value in a subclass by calling setIntentRedelivery().

onStart()

The onStart() method is called in the onStartCommand() method.

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

As you can see, here is to create a Message, and then we passed the Intent as parameter is set to the Message, and then send the Message out, here is through the Handler to send out, so directly in the Handler for the method of processing data:

    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); }}Copy the code

Up to that point, the Handler is handleMessage. In the above method, onHandleIntent() is called directly, which is implemented by subclasses, and stopSelf() is called to shut down the current service.

IntentService: IntentService: IntentService: IntentService: IntentService: IntentService

  1. Start by creating a thread and create one for this threadLooperIn this way, the thread has event-passing capability.
  2. To create aHandlerAnd will thread the previous stepLooperPass it to thisHandlerIn this way, throughHandlerThe event being processed is executed in the worker thread created above
  3. throughstartService(Intent)Start the current service and set the data we need to process toIntentIf the service is started for the first time, the service is started firstonCreate(), then executeonStartCommand(Intent)Otherwise, the command is executed directlyonStartCommand(Intent)
  4. inIntentServicetheonStartCommand(Intent)Will callonStart(Intent,id)Method, and within this method, will create aMessage, will contain our dataIntentAnd mark the serviceidSet it to thisMessage, respectively asMessagetheobjandarg1The value of the argument to
  5. Created by the aboveHandlerSend the one created in step 4MessageEnter the sequence of events
  6. HandlerTo get the event that needs to be processedMessageWill be calledonHandleIntent()Method, we need to implement this method to write its own logic
  7. Called when all events in the service have been executedstopSelf()Automatic shutdown of service

At this point, the entire execution process is over.

Start the service with onBind()

Starting IntentService with onBind() is not a good idea, because onBind() is called only once when the service is started, so it can only be done after the binding is successful, and since we usually close the connection when the page is closed, As a result, the task may end up unfinished, but if we really need it, we can do it. The following steps show how to bind a service using onBind().

  1. Implement your own Service from IntentService:

    class MyIntentService : IntentService("MyIntentService") {
    
        private val mBinder by lazy {
            MyIntentServiceBinder()
        }
    
        var mValueListener: IntentServiceStudyActivity.IntentServiceValueListener? = null
    
        override fun onHandleIntent(intent: Intent?). {
            // Thread sleep
            Thread.sleep(1 * 1000) intent? .let {val value = it.getStringExtra("key")
                Logs.e("value is $value")}}override fun onCreate(a) {
            Logs.e("onCreate...")
            super.onCreate()
        }
    
        override fun onStartCommand(intent: Intent? , flags:Int, startId: Int): Int {
            Logs.e("onStartCommand...")
            return super.onStartCommand(intent, flags, startId)
        }
    
    
        override fun onDestroy(a) {
            Logs.e("onDestroy...")
            super.onDestroy()
        }
    
        override fun onBind(intent: Intent?).: IBinder {
            super.onStart(intent, 0)
            return mBinder
        }
        
        inner class MyIntentServiceBinder : Binder() {
            fun getService(a): MyIntentService = this@MyIntentService}}Copy the code
  2. Create a connection in your Activity:

        private val mServiceConnection by lazy {
            object : ServiceConnection {
                override fun onServiceConnected(name: ComponentName? , service:IBinder?). {
                    mBindIntentServiceSuccess = trueservice? .let {for (i in 1 until 10) {
                            val intent = Intent()
                            intent.putExtra("key"."value$i")
                            binder.getService().onBind(intent)
                        }
    
                    }
                }
    
                override fun onServiceDisconnected(name: ComponentName?). {
                    Logs.e("Service connection down")
                    mBindIntentServiceSuccess = false}}}Copy the code

    As you can see, the Intent is created after connecting to the Service and setting the required data in the Intent. In this case, we call the service.onbind () method directly, as long as it is exposed in the Service.

    The onBind() method does not have the ability to call the onBind() method directly. You can define other public methods in the Service to call the onBind() method.