AIDL使用學習(二):跨程式回撥以及RemoteCallbackList

weixin_34292287發表於2017-09-12

前言#

五一假期終於結束了,回來一直也是在面試,今天先把之前的AIDL的內容講完,再整理一下面試總結。

正文#

上一篇我們已經瞭解了AIDL的基本使用方法,一般服務內都是要做耗時操作的,等處理結束之後在回撥給呼叫方,首先我們需要定義一個callback

// IOnCallbackListener.aidl
package com.lzp.aidlstudy.callback;

interface IOnCallbackListener{

    void callback(int result);
}

有些朋友可能有疑問了:這不是跟之前定義的Service一樣嗎?

恭喜你答對了,他倆就是一樣的,這個時候在好好的體會一下AIDL的定義:Android 介面定義語言。也就是說他本身就是定義介面的,只不過他自動生成了內部的Binder機制,我們就以這個IOnCallbackListener為例,看看到底生成了什麼東西:


/*
 * This file is auto-generated.  DO NOT MODIFY.
 * Original file: /Users/li504799868/Desktop/AIDLStudy/app/src/main/aidl/com/lzp/aidlstudy/callback/IOnCallbackListener.aidl
 */
package com.lzp.aidlstudy.callback;

public interface IOnCallbackListener extends android.os.IInterface {
    /**
     * Local-side IPC implementation stub class.
     */
    public static abstract class Stub extends android.os.Binder implements com.lzp.aidlstudy.callback.IOnCallbackListener {
        private static final java.lang.String DESCRIPTOR = "com.lzp.aidlstudy.callback.IOnCallbackListener";

        /**
         * Construct the stub at attach it to the interface.
         */
        public Stub() {
            this.attachInterface(this, DESCRIPTOR);
        }

        /**
         * Cast an IBinder object into an com.lzp.aidlstudy.callback.IOnCallbackListener interface,
         * generating a proxy if needed.
         */
        public static com.lzp.aidlstudy.callback.IOnCallbackListener asInterface(android.os.IBinder obj) {
            ...
        }

        @Override
        public android.os.IBinder asBinder() {
            return this;
        }

        @Override
        public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
            ...
        }

        private static class Proxy implements com.lzp.aidlstudy.callback.IOnCallbackListener {
            private android.os.IBinder mRemote;

            Proxy(android.os.IBinder remote) {
                mRemote = remote;
            }

            @Override
            public android.os.IBinder asBinder() {
                return mRemote;
            }

            public java.lang.String getInterfaceDescriptor() {
                return DESCRIPTOR;
            }

            @Override
            public void callback(int result) throws android.os.RemoteException {
                ...
            }
        }

        static final int TRANSACTION_callback = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
    }

    public void callback(int result) throws android.os.RemoteException;
}


由於這個檔案有點太長了,所以一些方法的程式碼就省略了,更為具體的大家可以自己執行去看一下原始檔,簡單的分析一下:

1、IOnCallbackListener裡面除了定義了Stub內部類以外,只有callback()方法。
2、在Stub裡面有很多熟悉的方法,例如獲取代理,返回binder之類的,內部還有代理類Proxy。
3、Proxy中跨程式通訊的Binder機制實現,這個是最核心的程式碼。

ok,總結也就是說,系統為我們自定義的介面實現了Binder機制,這樣就可以實現跨進行,只不過這個Binder沒有我們之前繫結服務使用的那麼明顯。

然後在之前的ITestInterface.aidl檔案中新定義一個方法:

// aidl  定義實現的Service方法
package com.lzp.aidlstudy;

import com.lzp.aidlstudy.bean.TestBean;
import com.lzp.aidlstudy.callback.IOnCallbackListener;

interface ITestInterface {

    // 定義一個計算方法
    int getCalculateResult(in TestBean bean);

    // 通過執行緒回撥的方式返回計算結果
    void getCalculateResultByThread(in TestBean bean, IOnCallbackListener callback);

}

接下來去實現getCalculateResultByThread方法,開啟TestService檔案:

@Override
public void getCalculateResultByThread(final TestBean bean, final IOnCallbackListener callback) throws RemoteException {
            new Thread() {
                @Override
                public void run() {
                    callback.callback(bean.getX() + bean.getY());
                }
            }.start();
        }

最後在MainActivity中新增一個按鈕:

findViewById(R.id.button2).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                try {
                    TestBean testBean = new TestBean();
                    testBean.setX(100);
                    testBean.setY(250);
                    // 這裡要使用IOnCallbackListener.Stub
                    binder.getCalculateResultByThread(testBean, new IOnCallbackListener.Stub() {
                        @Override
                        public void callback(final int result) {
                            runOnUiThread(new Runnable() {
                                @Override
                                public void run() {
                                    Toast.makeText(MainActivity.this, result + "", Toast.LENGTH_SHORT).show();
                                }
                            });
                        }
                    });

                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        });

首先強調一下,跨程式callback回撥是在子執行緒中,千萬不要忘記。

帶著無比激動的心情,趕緊執行一下,完美!

5615762-1acff299fe3dd461
這裡寫圖片描述

擴充套件#

很多朋友在網上檢視資料的時候發現,還有一個RemoteCallbackList,他的主要作用是可以把多個callback儲存到列表裡,在合適的時機同時回撥,也可以防止重複的呼叫相同的任務,只保證你需要的一個結果回撥,這個就厲害了,他的原始碼也非常的簡單:

package android.os;

import android.util.ArrayMap;

/**
 * 擅長 簡單的持續性的一系列的遠端介面的使用,尤其是Service對他的客戶端的回撥。
 * 需要注意的是:
 * 使用的時候,請確保每一個註冊的callback唯一性,這樣可以在程式停止的時候,清空這些callback。
 * 多執行緒請注意鎖的問題。
 * 
 * 使用這個類只要在Service使用單例模式就可以了,使用register和unregister方法來新增客戶端的回撥,使用時,先beginBroadcast,在getBroadcastItem,最後finishBroadcast。
 * 
 * 如果一個註冊的會滴啊程式結束了,這個類將自動從列中中移除,如果你想做一些額外的工作,就通過繼承來實現onCallbackDied方法。
 * 
/
public class RemoteCallbackList<E extends IInterface> {
    /*package*/ ArrayMap<IBinder, Callback> mCallbacks
            = new ArrayMap<IBinder, Callback>();
    private Object[] mActiveBroadcast;
    private int mBroadcastCount = -1;
    private boolean mKilled = false;

    private final class Callback implements IBinder.DeathRecipient {
        final E mCallback;
        final Object mCookie;
        
        Callback(E callback, Object cookie) {
            mCallback = callback;
            mCookie = cookie;
        }

        public void binderDied() {
            synchronized (mCallbacks) {
                mCallbacks.remove(mCallback.asBinder());
            }
            onCallbackDied(mCallback, mCookie);
        }
    }

    /**
     * 註冊方法
     */
    public boolean register(E callback) {
        return register(callback, null);
    }
    /**
     *註冊的具體實現方法,註冊的callback,只有在呼叫unregister或者程式結束才會被解綁,返回新增到集合中的結果(true、false)
     */
    public boolean register(E callback, Object cookie) {
        synchronized (mCallbacks) {
            if (mKilled) {
                return false;
            }
            IBinder binder = callback.asBinder();
            try {
                Callback cb = new Callback(callback, cookie);
                binder.linkToDeath(cb, 0);
                mCallbacks.put(binder, cb);
                return true;
            } catch (RemoteException e) {
                return false;
            }
        }
    }

    /**
     * 解綁callback
     */
    public boolean unregister(E callback) {
        synchronized (mCallbacks) {
            Callback cb = mCallbacks.remove(callback.asBinder());
            if (cb != null) {
                cb.mCallback.asBinder().unlinkToDeath(cb, 0);
                return true;
            }
            return false;
        }
    }

    /**
     * 清空之前所有註冊的callback
     */
    public void kill() {
        synchronized (mCallbacks) {
            for (int cbi=mCallbacks.size()-1; cbi>=0; cbi--) {
                Callback cb = mCallbacks.valueAt(cbi);
                cb.mCallback.asBinder().unlinkToDeath(cb, 0);
            }
            mCallbacks.clear();
            mKilled = true;
        }
    }

    /**
     * 老版本的onCallbackDied
     */
    public void onCallbackDied(E callback) {
    }
    
    /**
     * 預設呼叫的是onCallbackDied(E callback)r
     */
    public void onCallbackDied(E callback, Object cookie) {
        onCallbackDied(callback);
    }

    /**
     * 開始對儲存的集合進行回撥,返回目前回撥集合的大小
     */
    public int beginBroadcast() {
        synchronized (mCallbacks) {
            if (mBroadcastCount > 0) {
                throw new IllegalStateException(
                        "beginBroadcast() called while already in a broadcast");
            }
            
            final int N = mBroadcastCount = mCallbacks.size();
            if (N <= 0) {
                return 0;
            }
            Object[] active = mActiveBroadcast;
            if (active == null || active.length < N) {
                mActiveBroadcast = active = new Object[N];
            }
            for (int i=0; i<N; i++) {
                active[i] = mCallbacks.valueAt(i);
            }
            return N;
        }
    }

    /**
     * 獲取指定索引的callback
     */
    public E getBroadcastItem(int index) {
        return ((Callback)mActiveBroadcast[index]).mCallback;
    }
    
    /**
     * 回去指定索引的callback的Cookie
     */
    public Object getBroadcastCookie(int index) {
        return ((Callback)mActiveBroadcast[index]).mCookie;
    }

    /**
     * 清楚之前的beginBroadcast的初始狀態,當處理結束請呼叫這個方法。
     *
     * @see #beginBroadcast
     */
    public void finishBroadcast() {
        synchronized (mCallbacks) {
            if (mBroadcastCount < 0) {
                throw new IllegalStateException(
                        "finishBroadcast() called outside of a broadcast");
            }

            Object[] active = mActiveBroadcast;
            if (active != null) {
                final int N = mBroadcastCount;
                for (int i=0; i<N; i++) {
                    active[i] = null;
                }
            }

            mBroadcastCount = -1;
        }
    }

    /**
     * 返回當前的的要處理的callback數量
     * beginBroadcast 返回的是當前註冊的數量
     * getRegisteredCallbackCount返回的是處理的數量
     * 兩者的返回結果可能不同
     */
    public int getRegisteredCallbackCount() {
        synchronized (mCallbacks) {
            if (mKilled) {
                return 0;
            }
            return mCallbacks.size();
        }
    }
}

他的程式碼不多,但是英文註釋是真心的多,我就直接在註釋裡面給大家簡單的翻譯一下了,具體大家可以自己去看原始碼註釋。

下面就簡單的修改一下我們的程式碼:

// 新增RemoteCallbackList
private final RemoteCallbackList<IOnCallbackListener> mCallbacks
            = new RemoteCallbackList<>();

// 修改後的getCalculateResultByThread
 @Override
public void getCalculateResultByThread(final TestBean bean, final IOnCallbackListener callback) throws RemoteException {
            new Thread() {
                @Override
                public void run() {
                //註冊callback
                    mCallbacks.register(callback);
                    callback(bean.getX() + bean.getY(), callback);
                }
            }.start();
        }
        
/**
* 處理callback
*/
void callback(int result, IOnCallbackListener callback) {
        final int N = mCallbacks.beginBroadcast();
        try {
            for (int i = 0; i < N; i++) {
                IOnCallbackListener ibc = mCallbacks.getBroadcastItem(i);
                ibc.callback(result);
                // 處理結束,解綁callback
                mCallbacks.unregister(ibc);
            }
        } catch (RemoteException e) {
            e.printStackTrace();
        }
        mCallbacks.finishBroadcast();
    }

執行結果也是一樣的,就不貼出來了。

總結#

這樣AIDL的跨程式回撥我們也瞭解了,那麼他具體的呼叫過程是怎麼樣的,系統為我們生成的程式碼都起到了什麼作用呢?這些我們在下一篇在深入的討論。

Demo下載地址

相關文章