1. 程式人生 > >Android Service更新UI的方法之AIDL

Android Service更新UI的方法之AIDL

       Service作為Android中的四大元件之一,它重要性不言而喻。它可以分為本地服務和遠端服務:區分這兩種服務就是看客戶端和服務端是否在同一個程序中,本地服務是在同一程序中的,遠端服務是在兩個不同的應用中或者一個應用的不同程序。前面的文章中我們講過怎樣實現應用內多程序,看這裡:http://blog.csdn.net/goodlixueyong/article/details/49853079 。

       開啟服務也有兩種方式,一種是startService(),他對應的結束服務的方法是stopService(),另一種是bindService(),結束服務的是unBindService()。這兩種方式的區別是:當Client使用startService方法開啟服務時,這個服務和Client之間就沒有聯絡了,Service的執行和Client是相互獨立的。而當客戶端Client使用bindService方法開始服務的時候,這個服務和Client是一種關聯關係,他們之間使用Binder的代理物件進行互動,要是結束服務的話,需要在Client中和服務斷開,呼叫unBindService方法。

       很多情況下,為了實現業務需求,我們都希望Activity和Service能夠互動,而不是相互獨立的執行。尤其是我們使用Service在後臺做一些任務時,資料變化可能希望Activity能在UI的變化中體現出來。當Activity和Service執行在同一個程序中的時候,互動相對容易。有哪些實現方案呢?通過bindService可以拿到Service的Binder物件,然後通過這個Binder物件可以呼叫Service中的方法,反過來,可以在Service中定義一個回撥介面作為引數通過Binder傳遞給Activity,Activity通過Binder設定回撥,Service拿到回撥之後,就可以通過它更新UI。

       如果Activity和Service是執行在不同的程序中,Service再想通知UI更新就沒有那麼簡單了。容易實現的方法是通過廣播,在Activity中註冊一個廣播接收器,在Service中有資料變化時傳送廣播來通知UI更新。不過如果資料的更新特別頻繁,就要頻繁的傳送廣播,再使用廣播這種方式就顯得不合時宜了。那麼用什麼方式比較好呢?

       我們來介紹兩種方式:AIDL和Messenger。這兩種都是在跨程序通訊中常用的方式,它們的實質也是藉助了Binder。本文只講解AIDL方式實現Service更新UI的功能,Messenger方式在下一篇文章中介紹。

       AIDL(Android Interface Definition Language)是一種介面定義語言,用於生成可以在Android裝置上兩個程序之間通訊的程式碼。客戶端使用服務端的代理物件實現與服務端的通訊。如果在一個程序中要呼叫另一個程序中物件的操作,就可以使用AIDL生成可序列化的引數。

       AIDL介面和普通的java介面沒有什麼區別,只是副檔名為.aidl,通過AIDL進行通訊的應用需要建立同樣的AIDL檔案。

       實現AIDL時需要注意以下幾點:

       1)AIDL只支援介面方法,不能公開static變數。

       2)AIDL定義的介面名必須和檔名一致。 

       3)AIDL傳遞非基本可變長度變數,需要實現parcelable介面。  

       4)對於非基本資料型別,也不是String和CharSequence型別的,需要有方向指示,包括in、out和inout,in表示由客戶端設定,out表示由服務端設定,inout是兩者均可設定。

       5)AIDL的方法還提供了oneway關鍵字,可以用關鍵字oneway來標明遠端呼叫的行為屬性,使用了該關鍵字的遠端呼叫將僅僅是呼叫所需的資料傳輸過來並立即返回,而不會等待結果的返回,也即是說不會阻塞遠端執行緒的執行。

       一般實現兩個程序之間的AIDL通訊需要實現下面幾個步驟:

    (1)在android工程目錄下面建立.aidl副檔名的檔案,如果需要在AIDL中使用其他AIDL介面型別,即使是在相同包下也需要手動import。

    (2)在Eclipse上,如果aidl檔案符合規範,編譯器會在gen目錄下生成相對應的.java檔案。但是在Android Studio不會自動生成java檔案,需要在app/src/main目錄下建立一個aidl目錄,在其中建立.aidl檔案,才會在app/build/generated/source/aidl/debug目錄下生成對應的java檔案。

    (3)需要繼承一個服務類,在onBind()方法中返回Binder物件。

    (4)在Service端實現AIDL介面,將Binder物件返回給Client端。

    (5)在Activity中實現更新UI的回撥,註冊回撥。

       根據上面的實現步驟,我們通過AIDL一步步實現不同程序的Service通知Activity更新的功能。

       1、定義AIDL檔案。

       我們定義兩個aidl檔案,IClientCallback.aidl和IRegisterCallback.aidl,第一個用於Service呼叫更新UI,另外一個用於Activity將更新UI的回撥註冊到Service以及Activity通知Service停止更新UI。兩個檔案中的所有方法都不需要等待呼叫返回,所以使用oneway關鍵字修飾。

// IClientCallback.aidl
package com.viclee.aidltest.aidl;

interface IClientCallback {
    oneway void updateData(int num);
}
// IRegisterCallback.aidl
package com.viclee.aidltest.aidl;

import com.viclee.aidltest.aidl.IClientCallback;

interface IRegisterCallback {
    oneway void registerCallback(IClientCallback  callback);
    oneway void unregisterCallback(IClientCallback callback);
    oneway void start();
}

2、實現Service類

       Service類用到了一個RemoteCallbackList類,它用來管理一個Client註冊回撥介面的列表,方便對註冊的介面進行管理。它提供了register()和unregister()方法來註冊和登出介面。

public class AIDLService extends Service {
    private int count = 0;
    private RemoteCallbackList<IClientCallback> callbackList = new RemoteCallbackList<IClientCallback>();
    public AIDLService() {
    }

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

    private IBinder serviceBinder = new IRegisterCallback.Stub() {

        public boolean isRunning;

        @Override
        public void registerCallback(IClientCallback callback) throws RemoteException {
            callbackList.register(callback);
            isRunning = true;
        }

        @Override
        public void unregisterCallback(IClientCallback callback) throws RemoteException {
            callbackList.unregister(callback);
            isRunning = false;
        }

        @Override
        public void start() {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    while(isRunning) {
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        count ++;
                        int num = callbackList.beginBroadcast();
                        for (int i = 0; i < num; i++) {
                            try {
                                callbackList.getBroadcastItem(i).updateData(count);
                            } catch (RemoteException e) {
                                e.printStackTrace();
                            }
                        }
                        callbackList.finishBroadcast();
                    }
                }
            }).start();
        }
    };
}

       程式碼中實現了一個IRegisterCallback型別的Binder物件通過onBind方法返回給Client中的Activity,這個Binder物件中,實現了三個方法,前兩個為註冊和登出回撥介面,start()方法用於開始更新UI,可以看到實現中每隔一秒呼叫一次回撥介面中的updateData()方法更新UI。

3、Activity中實現callback介面,註冊callback介面

public class ServiceUpdateActivity extends Activity implements View.OnClickListener {

    private Button btn, btnStop;
    private TextSwitcher switcher;

    private int mCount = 0;
    Intent intent;

    private boolean isAIDLServiceConnected = false;

    IRegisterCallback registerCallback;

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

        btn = (Button) findViewById(R.id.btn);
        btnStop = (Button) findViewById(R.id.method_stop);

        btn.setOnClickListener(this);
        btnStop.setOnClickListener(this);

        switcher = (TextSwitcher) findViewById(R.id.number_switcher);
        switcher.setFactory(new ViewSwitcher.ViewFactory() {
            @Override
            public View makeView() {
                TextView textView = new TextView(ServiceUpdateActivity.this);
                textView.setGravity(Gravity.CENTER);
                textView.setTextSize(30);
                return textView;
            }
        });
        switcher.setInAnimation(AnimationUtils.loadAnimation(this, R.anim.switcher_text_in));
        switcher.setOutAnimation(AnimationUtils.loadAnimation(this, R.anim.switcher_text_out));

        updateCount();
        intent = new Intent(this, AIDLService.class);
    }

    @Override
    protected void onDestroy() {
        stopAll();
        super.onDestroy();
    }

    private void startAIDLMethod() {
        bindService(intent, AIDLServiceConnection, Service.BIND_AUTO_CREATE);
    }

    /**
     * 重新整理數字
     */
    private void updateCount() {
        //由於從binder呼叫回來是在子執行緒裡,需要post到主執行緒呼叫
        new Handler(Looper.getMainLooper()).post(new Runnable() {
            @Override
            public void run() {
                switcher.setText(Integer.toString(mCount));
            }
        });
    }

    private void stopAll() {
        if (isAIDLServiceConnected) {
            try {
                registerCallback.unregisterCallback(clientCallback);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
            unbindService(AIDLServiceConnection);
            isAIDLServiceConnected = false;
        }
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btn:
                startAIDLMethod();
                break;
            case R.id.method_stop:
                stopAll();
                break;
        }
    }

    private ServiceConnection AIDLServiceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            isAIDLServiceConnected = true;
            registerCallback = IRegisterCallback.Stub.asInterface(service);
            try {
                registerCallback.registerCallback(clientCallback);
                registerCallback.start();
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

    private IClientCallback clientCallback = new IClientCallback.Stub() {

        @Override
        public void updateData(int num) throws RemoteException {
            mCount = num;
            updateCount();
        }
    };
}

       在回撥介面中呼叫updateData()方法更新UI,並通過註冊介面的方法將回調介面註冊到Service中,然後呼叫start()方法啟動遠端Service中的執行緒。可以看到,Activity中的IClientCallback是Binder物件的Server端,而Service中卻是Binder的Client端。

       通過AIDL方式實現不同程序中的Service更新UI的方法就講解完了,下一節會講解另外一種方式:Messenger。