Android Service 初探(一)

一, Service 簡介
Service 是 Android 四大組件之一,可與其它組件進行交互,一般運行於後臺,如 IM 軟件, 音樂播放器等。 這裡對 Service 的常用屬性進行一次簡單的解析。
這裡對 Service 的使用做一個初步的記錄。

二, Service 生命周期
首先,我們來看一下 Google 官方給定的一個 Service 生命周期圖
Service 生命周期
Service 一般有兩種方式進行啟動, context.startService(), context.bindService(). 而對應的銷毀方式則分別是 context.stopService, context.unbindService.
來看代碼,首先定義 四個按鈕分別代表上面四個操作


定義我們的 Service:

public class MyService extends Service{
    @Override
    public void onCreate() {
        super.onCreate();
        log("onCreate");
    }

    @Override
    public boolean onUnbind(Intent intent) {
        log("onUnbind");
        return super.onUnbind(intent);
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        log("onStartCommand");
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onDestroy() {
        log("onDestroy");
        super.onDestroy();
    }

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

    private static void log(String msg){
        Log.e("MyService", msg);
    }
}

也別忘瞭在 Manifest 文件中聲明:




    
        
            
                

                
            
        

        
    

在 MainActivity 中處理事件

public class MainActivity extends AppCompatActivity implements View.OnClickListener{
    private static final String TAG = "MainActivity";


    private ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Log.e(TAG, "Service Connected, name: " + name + ", binder: " + service);
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            Log.e(TAG, "Service disconnected, name: " + name);
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        findViewById(R.id.start_service).setOnClickListener(this);
        findViewById(R.id.stop_service).setOnClickListener(this);
        findViewById(R.id.bind_service).setOnClickListener(this);
        findViewById(R.id.unbind_service).setOnClickListener(this);
    }

    private void startService(){
        Intent intent = new Intent(this, MyService.class);
        startService(intent);
    }

    private void stopService(){
        Intent intent = new Intent(this, MyService.class);
        stopService(intent);
    }

    private void bindService(){
        bindService(new Intent(this, MyService.class), connection, Service.BIND_AUTO_CREATE);
    }

    private void unbindService(){
        unbindService(connection);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.start_service:startService();break;
            case R.id.stop_service:stopService();break;
            case R.id.bind_service:bindService();break;
            case R.id.unbind_service:unbindService();break;
        }
    }
}

點擊 start service 後
start service
然後點擊 stop service
stZ喎?/kf/ware/vc/vcCBzZXJ2aWNl” src=”/uploadfile/Collfiles/20170815/20170815141114336.jpg” />
點擊 bind service 後再點擊 unbind service:
bind unbind
這裡可以發現並沒有運行 MainActivity 中 ServiceConnection 中的回調。原因在於當前Service 並沒有對應的 binder 來,也即沒有真正連接上,所以 onConnecton 的回調不會進行,下面看 MyService 和 MyBinder 進行連接。
這裡我們在 MyService 中定義一個 MyBinder

    public static class MyBinder extends Binder{}

修改 onBind 方法

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        log("onBind");
        return new MyBinder();
    }

然後點擊 bind service, unbind service
bind connect unbind
可以看到, service 在 onBind 的時候返回的不是 null 時, connection 就建立瞭連接瞭。有人或許會奇怪為什麼 onServiceDisconnected並沒有運行,實際上該回調發生在非正常失去連接的情況,如內存不足殺死 Service 的情況,在 Service 重新運行後 會重新進入 connected 回調。

三, Activity 和 Service 一般連接
我們可以在 MyBinder 中定義 public 方法直接讓 Service 進行工作,那麼接下來我們改動一下 MyBinder, 在中間添加一個下載任務:

    public static class MyBinder extends Binder{
        public void startDownload(){
            log("start download");
            try{
                Thread.sleep(30 * 1000);
            }catch (InterruptedException e){
                Log.e("MyService", "Thread Crashed", e);
            }
            log("download finished");
        }
    }

再來點擊 bind service:
start download
嗯,開始下載,正確,嗯?不對!為什麼會 ANR:
anr
原來, service 相關的這些函數都是運行在 UI 線程的,來看看:
首先我們在 MainActivity 中增加一條 log:

    private void bindService(){
        Log.e("MainActivity", "bindService");
        bindService(new Intent(this, MyService.class), connection, Service.BIND_AUTO_CREATE);
    }

然後重復上面的操作:
service thread
我們可以看到 PID-TID 一列, Service 和 MainActivity 的值是一樣的,這說明他們在同一個進程,同一個線程,也即 UI 線程,在 UI 線程做耗時操作不給你 ANR 給誰 ANR 呢?
以上為一般連接的情況,更多連接狀態待續。

四,一些需要註意的點
1. 多次 bind service 隻會運行一次,因為 service 已經 bind 瞭
2. 一個 service 隻有一個實例,因此在 onCreate 之後如果沒有 onDestroy 那麼就不會再次 onCreate;
3. 多次點擊 start service 會多次調用 onStartCommand.
4. 銷毀 service 時可以在service 內部調用 stopSelf
5. 對同一個 service 調用瞭 startService 和 bindService, 在 service 外需要 stopService 和 unbindService 都調用才能 destroy

You May Also Like