Android Service 與 AIDL 詳解

0x10 服務的基本用法

Service 是安卓四大組件之一,Activity 一般有具體的用戶界面,而 Service 則是 Android 實現程序後臺運行的解決方案。那些不需要與用戶交互,或者需要長期運行的任務,就適合使用服務。服務的特性如下

  • 服務並不能獨立運行,而是依靠某個進程創建,該進程被殺死,服務也就不復存在
  • 服務不會自動開啓線程,默認是在主線程中運行。如果不手動在服務的內部創建子線程,很可能會導致主線程被阻塞

0x11 定義服務

右擊 com.example.servicetest→New→Service→Service,Service 默認會有 MyService() 構造方法和 onBind() ,所有 Service 默認繼承自 Service 父類, onBind() 用來實現 Activity 與 Service 的交互。如果你要在活動和服務之間傳遞消息,可以用 onBind() 實現相應的邏輯。那麼服務要實現的邏輯在哪寫呢?在 Service 中寫入以下代碼

public class MyService extends Service {
    public MyService() {
    }

    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        throw new UnsupportedOperationException("Not yet implemented");
    }

    @Override
    public void onCreate(){
        Log.d("MyService", "Service_onCreate executed!");
        super.onCreate();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId){
        Log.d("MyService", "Service_onStartCommand executed!");
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onDestroy(){
        Log.d("MyService", "Service_onDestroy executed!");
        super.onDestroy();
    }
}

學過 Android 的人不難看出,onCreate 實在組件創建的時候運行,onStart 是在組件啓動的時候運行,onDestroy 則是在組件銷燬的時候運行。因此,定義服務要完成的任務,應該是在 onStartCommand 中完成。

0x12 啓動和關閉服務

接下來,我們在主活動 Activity 中,定義兩個按鈕,來實現服務的開啓和關閉。

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Button buttonStart = findViewById(R.id.button_start_service);
        Button buttonStop = findViewById(R.id.button_stop_service);

        buttonStart.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(MainActivity.this, MyService.class);
                startService(intent);
            }
        });

        buttonStop.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(MainActivity.this, MyService.class);
                stopService(intent);
            }
        });
    }
}

日誌如下,當我們點擊 Start 按鈕,打印前兩條日誌,說明onCreateonStart 運行 ;當我們點擊 Stop 打印後面一條日誌,說明onDestroy運行,與我們之前分析的一致。
在這裏插入圖片描述
注意 onCreateonStart 區別: onCreate 只會在第一次創建服務時啓動, onStart 會在每次調用服務的時候都啓動,也就是如果你連續點擊 Start 按鈕,除了第一次,後面只會打印我們的第二條日誌。

0x13 綁定服務

綁定服務是指,將 Service 與 Activity 綁定,這樣可以讓 Activity 與 Service 進行通信。在剛剛的 MyService 中,添加如下代碼,我們現在的目標就是讓 Activity 通過 Service 獲取系統時間。需要在 Service 中,實現 Binder 對象對獲取系統時間進行管理。

private ServiceBinder mBinder = new ServiceBinder();

@Override
public IBinder onBind(Intent intent) {
    // TODO: Return the communication channel to the service.
    // throw new UnsupportedOperationException("Not yet implemented");
    return mBinder;
}

// 定義一個內部類
class ServiceBinder extends Binder{
    public String getTime(){
        SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//設置日期格式
        return df.format(new Date());   // new Date()爲獲取當前系統時間
    }
}

思路就是,自定義一個類繼承 Binder,在自定義類中實現相應的邏輯。用自定義類創建一個對象,再利用 onBinder() 方法返回這個對象,這樣 Service 的任務就完成了。接下來是修改 Activity 的代碼

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Button buttonStart = findViewById(R.id.button_start_service);
        Button buttonStop = findViewById(R.id.button_stop_service);
        Button buttonBind = findViewById(R.id.button_bind);
        Button buttonUnbind = findViewById(R.id.button_unbind);

        final ServiceConnection mConnection = new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                // 服務啓動時回調
                MyService.ServiceBinder testBinder = (MyService.ServiceBinder) service;
                // Log.i("Info from service", testBinder.getTime());
            }

            @Override
            public void onServiceDisconnected(ComponentName name) {
                // 服務終止時回調
                // Log.i("Info from service", "Stop!");
            }
        };

        buttonStart.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(MainActivity.this, MyService.class);
                startService(intent);
            }
        });

        buttonStop.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(MainActivity.this, MyService.class);
                stopService(intent);
            }
        });

        buttonBind.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(MainActivity.this, MyService.class);
                bindService(intent, mConnection, BIND_AUTO_CREATE);
            }
        });

        buttonUnbind.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                unbindService(mConnection);
            }
        });
    }
}

我們創建了一個ServiceConnection 的匿名類,在裏面重寫了 onServiceConnected() 方法和onServiceDisconnected() 方法,這兩個方法分別會在活動與服務成功綁定以及活動與服務的連接斷開的時候調用。
在這裏插入圖片描述

0x20 AIDL

如果想讓服務同時處理多個請求,可以使用 AIDL。AIDL 是 Andorid 進程間通信(IPC,Inter-Process Communication)的一種方式。AIDL 全稱是 Android Interface Definition Language,即安卓接口定義語言。可以讓 AIDL 綁定另一個進程的服務,實現跨進程通信。

0x21 AIDL 定義(服務端)

新建一個項目,選擇 project,右擊,選擇創建 AIDL 文件。aidl 文件其實就是一個接口,定義我們想實現的函數

interface IMyAidlInterface {
    /**
     * Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */
    void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
            double aDouble, String aString);

    String getTime();
}

創建完 aidl 文件後,需要 rebuild ,即重新編譯一下。緊接着,AIDL 接口需要在 Service 裏實現,在 java/com…包名 路徑下,創建一個服務 MyService,實現 getTime()

public class MyService extends Service {
    public MyService() {
    }

    @Override
    public IBinder onBind(Intent intent) {
        // 返回內部類實例化的對象
        return myBinder;
    }

    MyBinder myBinder = new MyBinder();

    class MyBinder extends IMyAidlInterface.Stub{
        @Override
        public String getTime(){
            SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//設置日期格式
            return df.format(new Date());   // new Date()爲獲取當前系統時間
        }

        // 默認生成 AIDL 支持的基本類型,可以不用管
        @Override
        public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
                        double aDouble, String aString){};
    }
}

0x22 AIDL(客戶端)

再創建一個應用,隨便命名,比如這裏我命名爲 AidlClient,其目的是讓該應用通過 AIDL 調用服務端應用的某個服務下的方法。需要將上一節的應用創建的 aidl 文件,原封不動的複製到 AidlClient(請注意是要複製完整路徑),如下圖所示,是要複製整個 aidl 文件夾,複製到 main 下
在這裏插入圖片描述

類似於 0x13 節所說的綁定服務,這裏,是要讓應用的 Activity 綁定另外一個應用的 service,MainActivity 代碼如下

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Button button = findViewById(R.id.bind_service);

        final ServiceConnection mConnection = new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
                IMyAidlInterface iMyAidlInterface = IMyAidlInterface.Stub.asInterface(iBinder);
                try{
                    String result = iMyAidlInterface.getTime();
                    Toast.makeText(MainActivity.this, result, Toast.LENGTH_LONG).show();
                }catch (RemoteException e){
                    e.printStackTrace();
                }

            }

            @Override
            public void onServiceDisconnected(ComponentName componentName) {

            }
        };

        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                // Andoird 5 之後規定必須顯示綁定服務
                Intent intent = new Intent();
                ComponentName componentName = new ComponentName("com.example.aidlservice", "com.example.aidlservice.MyService");
                intent.setComponent(componentName);
                bindService(intent, mConnection, BIND_AUTO_CREATE);
            }
        });
    }
}

從 Android 5.0開始 隱式 Intent 綁定服務的方式已不能使用,所以這裏需要設置 Service 所在服務端的包名及類名。至此,AidlClient 通過 AIDL 綁定 AidlServer 的服務,實現了進程間通信。在 onServiceConnected 方法中通過 IMyAidlInterface.Stub.asInterface(service) 獲取 iMyAidlInterface 對象,即可調用另外一個進程中 service 的方法。
在這裏插入圖片描述

0x30 總結

本篇文章從 Android 四大組件之一的 Service 入手,首先講解其一般用法,在此基礎上,引入 Android 中一種重要的進程間通信(IPC)手段 —— AIDL,其實質就是一個應用的 Activity 綁定另外一個應用的 Service ,從而實現進程間通信。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章