一場由Parcelable引發的血案

ui8p0q1j 7年前發布 | 17K 次閱讀 AIDL Android開發 移動開發

問題背景

前陣子接手了直播模塊,有個需求需要在已有的AIDL接口中增加多一個int類型的參數B。由于該AIDL接口中已經有了一個自定義類型的參數A(已經實現Parcelable接口),我便將參數B追加到A的后面。嗯,炒雞簡單的,只是運行之后有問題而已(微笑臉)

有一個詭異的問題: 無論B傳入什么值,另一方接收到都始終為0(int的默認值),而A接收到的值卻是正確的?!

Take it easy! 作為共產主義的接班人,我當然是有辦法的啦:

  1. 懷疑是 Freeline 不支持AIDL,改用AS重新build,問題依舊
  2. 懷疑是辣雞AS的問題,重新啟動再build,問題依舊
  3. 懷疑是工具鏈的版本問題,改為最新版本再次編譯,問題依舊
  4. 懷疑是int的問題?將B改為float, boolean等其他的基本類型,問題依舊,取到的始終是默認值
  5. 抱著希望在StackOverflow和Google上逛了一圈,無果
  6. 求助群里的小伙伴,答曰沒有遇到過此情況,并向我丟了一連竄的「233333」和一波表情
  7. 辭職

就在我一籌莫展的時候,突然腦子一抽,試著把接口定義中A參數和B參數位置調換。震驚地發現,居然可以了!!

嗯,此篇文章完結,撒花~

解決方案

將基本類型的參數放在自定義類型的參數前面,雖然解決了問題,但是治標不治本,只能算個workaround。

不知道大家有沒有發現:參數的定義順序會影響結果這一行為,是不是跟實現Parcelable接口的時候有點類似?Parcelable中如果read的順序和write的順序不同的話,產生的結果也不同。

基于這點,我們懷疑問題出在自定義類型的參數A,先來看下相關代碼:

//ILivePlayerService.aidl

interface ILivePlayerService { //參數A:config, 參數B:type void setPlayerConfig(in PlayerConfig config, int type) } </code></pre>

//PlayerConfig.java

public class PlayerConfig implements Parcelable { //省略其他代碼.... String streamUrl; int roomId; int anchorId;

public PlayerConfig() {
}

protected PlayerConfig(Parcel in) {
    //省略其他代碼....
    this.streamUrl = in.readString();
    this.roomId = in.readInt();
    this.anchorId = in.readInt();
}

@Override
public void writeToParcel(Parcel dest, int flags) {
    //省略其他代碼....
    dest.writeString(this.streamUrl);
    dest.writeInt(this.roomId);
    //下面注釋的這句,代碼中是沒有的。問題就出現在這里..
    //dest.writeInt(this.anchorId);
}

@Override
public int describeContents() {
    return 0;
}

public static final Creator<PlayerConfig> CREATOR = new Creator<PlayerConfig>() {
    @Override
    public PlayerConfig createFromParcel(Parcel source) {
        return new PlayerConfig(source);
    }

    @Override
    public PlayerConfig[] newArray(int size) {
        return new PlayerConfig[size];
    }
};

} </code></pre>

果然, PlayerConfig 沒有正確地實現 Parcelable 接口,在寫入的時候(詳見上面代碼中 writeToParcel 方法中的注釋)漏掉了變量 anchorId ,而讀取的時候卻有。

論接手別人的代碼是一種怎樣的體驗?

我們先把 writeToParcel 中漏掉的變量補上,再跑一下看問題是否解決了。

不出所料,那個詭異的問題沒有了,可是為什么呢?我們來看下AIDL生成的Java代碼:

//AIDL生成的 ILivePlayerService.java 
//為了方便查看,格式了一下代碼

@Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException { switch (code) { case INTERFACE_TRANSACTION: { reply.writeString(DESCRIPTOR); return true; } //setPlayerConfig方法 case TRANSACTION_setPlayerConfig: { data.enforceInterface(DESCRIPTOR); //參數A:config com.kk.model.PlayerConfig _arg0; if ((0 != data.readInt())) { //在解析的時候,會調用PlayerConfig的createFromParcel方法 _arg0 = com.kk.model.PlayerConfig.CREATOR.createFromParcel(data); } else { _arg0 = null; } //參數B:type int _arg1; _arg1 = data.readInt(); this.setPlayerConfig(_arg0, _arg1); reply.writeNoException(); return true; } } return super.onTransact(code, data, reply, flags); } </code></pre>

我們可以看到在解析數據的時候, 參數A和參數B都是從data中解析的(共用一個Parcel源) 其中,解析參數A config 的時候會將 data 傳入到自己實現的 createFromParcel 方法中進行處理,如下

//PlayerConfig.java 部分代碼

public static final Creator<PlayerConfig> CREATOR = new Creator<PlayerConfig>() { @Override public PlayerConfig createFromParcel(Parcel source) { //接受到aidl傳入的data return new PlayerConfig(source); } };

protected PlayerConfig(Parcel in) { //aidl傳入的data在這里解析 this.streamUrl = in.readString(); this.roomId = in.readInt(); this.anchorId = in.readInt(); }

@Override public void writeToParcel(Parcel dest, int flags) { //省略其他代碼.... dest.writeString(this.streamUrl); dest.writeInt(this.roomId); //下面注釋的這句,代碼中是沒有的。問題就出現在這里.. //dest.writeInt(this.anchorId); } </code></pre>

由于 PlayerConfig 沒有正確地實現 Parcelable ,只寫入了1個int類型,但是卻讀取了2個,這就導致了參數A多讀取了一個int… 等到參數B想從 data 中讀取的時候,就會讀取不到數值(返回默認值)…

這個涉及到了Parcel的內部機制,可以參考這篇文章

http://blog.csdn.net/qinjuning/article/details/6785517

小結

這次的坑是因為沒有正確實現Parcelable接口導致的。這很不應該,其實我們可以讓工具來做這種體力活,比如AS中有個插件叫做「Android Parcelable code generator」就可以一鍵生成Parcelable代碼,或者去Github搜搜Parcelable相關的注解庫也行~

程序猿要對自己好點,能用工具完成的事情盡量不要自己寫~

踩坑結束!

 

來自:http://andydev.me/2017/05/31/trap-of-android-aidl/

 

 本文由用戶 ui8p0q1j 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!