Android Intent Service

魯迅認識的那隻猹發表於2018-09-22

Android Intent Service

學習自

Overview

IntentService 是Service的子類,他被用來根據需求處理非同步(IntentService中存在一個工作執行緒)請求(表現為Intent,從類的名字也可以看出來)。 而且這個Service非常省心,當工作完成後會自動停止,不需要我們手動停止。

使用IntentService

下面是一個簡單的通過IntentService來更新進度條的示例,示例流程如下:

  1. Activity中祖冊臨時的廣播接受者,來接收訊息,然後更新UI
  2. 點選Button開啟IntentService,然後開始增加進度
  3. 當增加了進度以後,通過傳送廣播,來通知UI改變

6472982C-68A1-479B-AFBE-4A40564377ED

IntentService

class TestIntentService(name: String?) : IntentService(name) {
    /**
     * 注意必須要有一個無引數的建構函式
     * 當前環境使用的API是API26
     * IntentService並沒有無引數的建構函式
     * 所以我們這裡需要自己建立一個
     * 否則會報錯
     *
     * name 引數 代表的工作執行緒的命名
     * */
    constructor() : this("TestIntentService") {

    }

    companion object {
        val ACTION_UPDATE_PROGRESS = "com.shycoder.cn.studyintentservice.UPDATE_PROGRESS"
    }

    private var progress = 0

    private lateinit var mLocalBroadcastManager: LocalBroadcastManager

    private var isRunning = true

    override fun onCreate() {
        super.onCreate()
        this.mLocalBroadcastManager = LocalBroadcastManager.getInstance(this)
    }

    override fun onHandleIntent(intent: Intent?) {

        Log.e("TAG", "onHandleIntent")

        if (intent == null || intent.action != ACTION_UPDATE_PROGRESS) {
            return
        }

        this.isRunning = true
        this.progress = 0

        while (isRunning) {
            SystemClock.sleep(500)
            progress += 10
            if (progress >= 100) {
                this.isRunning = false
            }
            this.sendBroadcast(if (isRunning) "running" else "finish", progress)
        }
    }

    /**
     * send broadcast to activity for notifying change of status and progress
     * */
    private fun sendBroadcast(status: String, progress: Int) {
        val intent = Intent()
        intent.action = MainActivity.ACTION_STATUS_CHANGED
        intent.putExtra("status", status)
        intent.putExtra("progress", progress)
        this.mLocalBroadcastManager.sendBroadcast(intent)
    }

}
複製程式碼

Activity的程式碼

class MainActivity : AppCompatActivity() {

    val mReceiver = TestBroadcastReceiver()
    lateinit var mLocalBroadcastManager: LocalBroadcastManager

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        //register broadcast
        val intentFilter = IntentFilter(ACTION_STATUS_CHANGED)
        this.mLocalBroadcastManager = LocalBroadcastManager.getInstance(this)
        mLocalBroadcastManager.registerReceiver(mReceiver, intentFilter)
    }

    /**
     * start intent service
     * */
    fun startIntentService(view: View) {
        val intent = Intent(this, TestIntentService::class.java)
        intent.action = TestIntentService.ACTION_UPDATE_PROGRESS
        this.startService(intent)
    }

    override fun onDestroy() {
        super.onDestroy()
        this.unregisterReceiver(this.mReceiver)

    }

    inner class TestBroadcastReceiver : BroadcastReceiver() {

        override fun onReceive(context: Context?, intent: Intent?) {
            if (intent == null) {
                return
            }
            val status = intent.getStringExtra("status")
            val progress = intent.getIntExtra("progress", 0)
            tvStatus.text = status
            progress_barTest.progress = progress

        }
    }

    companion object {
        val ACTION_STATUS_CHANGED = "cn.shycoder.studyintentservice.STATUS_CHANGED"
    }
}

複製程式碼

多次開啟IntentService

當我們試著連續點選多次Button(比如說三次), 稍等片刻就會發現進度條滿了以後,又重新開始了直到三次位置。列印出來的Log如下:

E/TAG: onHandleIntent
E/TAG: onHandleIntent
E/TAG: onHandleIntent
複製程式碼

通過Log和UI的變化,我們可以發現,如果多次開啟Service的話,那麼 onHandleIntent 方法就會執行多次,這一點與 Service 大相庭徑需要我們格外的關注,至於為什麼是這種情況,在接下來的原始碼解析中會提到。

不要使用Bind的方式開啟服務

在我們使用 Service 的時候,為了能和Service進行互動,我們會通過Bind的方式開啟服務獲取與Service進行通訊的Binder,但是Bind開啟服務的方式並不適用於 IntentService 下面我們來驗證一下。

class TestIntentService(name: String?) : IntentService(name) {
    /**
     * 注意必須要有一個無引數的建構函式
     * 當前環境使用的API是API26
     * IntentService並沒有無引數的建構函式
     * 所以我們這裡需要自己建立一個
     * 否則會報錯
     *
     * name 引數 代表的工作執行緒的命名
     * */
    constructor() : this("TestIntentService") {

    }

    private val mBinder = MyBinder()

    override fun onBind(intent: Intent?): IBinder {
      Log.e("TAG", "onBind")
      return this.mBinder
    }

    inner class MyBinder : Binder() {
    }
    //...
}
複製程式碼

繫結服務

class MainActivity : AppCompatActivity() {
    //....
    lateinit var mLocalBroadcastManager: LocalBroadcastManager

    lateinit var mService: TestIntentService.MyBinder

    private val mConn = object : ServiceConnection {
        override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
            mService = service as TestIntentService.MyBinder
        }

        override fun onServiceDisconnected(name: ComponentName?) {
        }

    }

    /**
     * bind intent service
     * */
    fun bindService(view: View) {
        val intent = Intent(this, TestIntentService::class.java)
        intent.action = TestIntentService.ACTION_UPDATE_PROGRESS
        this.bindService(intent, this.mConn, Context.BIND_AUTO_CREATE)
    }
    //...
}
複製程式碼

當我們BindService後,檢視Log

E/TAG: onBind
複製程式碼

我們發現 onHandleIntent 方法的Log並沒有列印出來,這時候 IntentService 就是一個普通的Service了,而不具備IntentService的特性。由此我們可以得出結論——不用使用Bind的方式開啟IntentService。

IntentService原始碼解析


public abstract class IntentService extends Service {
    private volatile Looper mServiceLooper; //looper
    private volatile ServiceHandler mServiceHandler; //handler
    private String mName; //執行緒的名字
    private boolean mRedelivery;

    /**
      這個Handler是子執行緒的Handler,並不是與UI通訊的Handler
    */
    private final class ServiceHandler extends Handler {
        public ServiceHandler(Looper looper) {
            super(looper);
        }
        /**
          呼叫 onHandleIntent
        */
        @Override
        public void handleMessage(Message msg) {
            onHandleIntent((Intent)msg.obj);
            //當所有的訊息都處理完了就結束服務
            stopSelf(msg.arg1);
        }
    }

    public IntentService(String name) {
        super();
        mName = name;
    }

    public void setIntentRedelivery(boolean enabled) {
        mRedelivery = enabled;
    }

    @Override
    public void onCreate() {

        super.onCreate();
        //例項化HandlerThread
        //HandlerThread繼承自Thread
        HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
        thread.start();

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

    /**
    眾所周知,如果多次開啟Service的話,那麼 onStart方法就會執行多次
    IntentService在onStart找那個不斷地向Handler傳送訊息
    */
    @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() {
        mServiceLooper.quit();
    }

    /**
    onBind 方法被重寫,返回null
    */
    @Override
    @Nullable
    public IBinder onBind(Intent intent) {
        return null;
    }

    @WorkerThread
    protected abstract void onHandleIntent(@Nullable Intent intent);
}
複製程式碼

上面的程式碼很簡單,僅僅是對Service進行了一層封裝,大致流程如下:

  1. 當建立service的時候,進行IntentService的初始化操作(onCreate),例項化 HandlerThread
  2. onCreate方法執行以後,緊接著就會呼叫 onStart 方法,這時候就像向Handler傳送訊息
  3. handler 會進行排隊執行
  4. 當所有的訊息都處理完成了以後,會將服務結束

為什麼不能用Bind的方式開啟IntentService

通過檢視原始碼,我想大家已經找到答案了。因為Start方式和Bind的方式開啟Service的時候執行的生命週期的方法是不同的,通過Bind的方法開啟Service,並不會執行 onStart 生命週期方法。 所以雖然 Bind的方式開啟Service會執行onCreate方法來例項化 HandlerThread 但是因為 onStart 方法才向Handler 中傳送資料。

相關文章