Android Study 之 如何透過Data Binding提升擼

airland發表於2021-09-09

LZ-Says:洗個澡,突然感覺爽到爆~~~ 又回來了哦~

圖片描述

前言

前幾天,終於完善了關於Data Binding基礎篇以及進階篇博文編寫,過程很是艱難哦~

下面附上鍊接地址:

而今天,整整行囊,準備開啟Data Binding高階篇,完成之後,也該開啟Android重新回味之路了,長線計劃,一定要縮短時間咯~~~

拖了好久咯~~~ MMP呦~

啟程

圖片描述

以下關於Data Binding分析基於Libary 1.3.1。

一、setContentView原始碼分析

首先我們來回顧下Data Binding最初的使用:

DataBindingUtil.setContentView

我們先深入進去,看看在這裡面,它到底幹了什麼?

    /**
     * 設定Activity內容View為給定佈局並返回關聯繫結集。
     * 這裡需要注意的是:給定的佈局資源型別必須為非Merge Layout!
     *
     * @param activity 當前Activity
     * @param layoutId 當資源被引入時,繫結並且設定Activity內容
     * @return 繫結並關聯引入Content View
     */
    public static  T setContentView(Activity activity, int layoutId) {        return setContentView(activity, layoutId, sDefaultComponent);
    }

可以看到這裡直接return了一個setContentView,並且將我們傳遞的引數繼續傳送。

一起來看看這裡幹了什麼鬼?

    public static  T setContentView(Activity activity, int layoutId,
            DataBindingComponent bindingComponent) {
        activity.setContentView(layoutId);
        View decorView = activity.getWindow().getDecorView();
        ViewGroup contentView = (ViewGroup) decorView.findViewById(android.R.id.content);        return bindToAddedViews(bindingComponent, contentView, 0, layoutId);
    }

首先,為我們的Activity設定Content View,也就是將要展示的UI;

接著,獲取了一個DecorView,那麼為什麼要獲取它呢?這裡LZ放置一張關於Activity佈局層級關係圖:

圖片描述

可以很清晰的看到,在我們的Activity的層級關係,因此,拿到Content View上級,也就可以理解為拿到了對Content View的控制權。

接下來,直接拿到ContentView的例項,透過:decorView.findViewById(android.R.id.content)。

而最後,則呼叫bindToAddedViews方法進行處理,之後將結果Return。那麼,鑑名其意,我們就可以知道這個方法主要的作用就是:繫結新增我們的View。

而在bindToAddedViews方法中,我們猜測下它會怎麼做?來來來,老鐵想想~

LZ這裡猜測,既然是直接呼叫bindToAddedViews將結果Return,那麼最終這裡面應該是遍歷當前Layout下所有的View,拿到之後進行繫結。

下面一起來看看到底進行了什麼操作吧!

    private static  T bindToAddedViews(DataBindingComponent component,
            ViewGroup parent, int startChildren, int layoutId) {        // 獲取子控制元件個數
        final int endChildren = parent.getChildCount();        // 獲取要新增繫結的子控制元件
        final int childrenAdded = endChildren - startChildren;        // 如果只有一個直接進行繫結
        if (childrenAdded == 1) {            final View childView = parent.getChildAt(endChildren - 1);            return bind(component, childView, layoutId);
        } else {            // 多個子控制元件時,迴圈遍歷獲取子控制元件,進行繫結
            final View[] children = new View[childrenAdded];            for (int i = 0; i 

下面繼續深入bind方法。

/**
 * Returns the binding for the given layout root or creates a binding if one
 * does not exist.
 * 

 * Prefer using the generated Binding's bind method to ensure type-safe inflation  * when it is known that root has not yet been bound.  *  * @param root The root View of the inflated binding layout.  * @param bindingComponent The DataBindingComponent to use in data binding.  * @return A ViewDataBinding for the given root View. If one already exists, the  * existing one will be returned.  * @throws IllegalArgumentException when root is not from an inflated binding layout.  * @see #getBinding(View)  */@SuppressWarnings("unchecked") // 執行了未檢查的轉換時的警告,例如當使用集合時沒有用泛型 (Generics) 來指定集合儲存的型別public static  T bind(View root,         DataBindingComponent bindingComponent) {     T binding = getBinding(root);    if (binding != null) {        return binding;     }     Object tagObj = root.getTag();    if (!(tagObj instanceof String)) {        throw new IllegalArgumentException("View is not a binding layout");     } else {         String tag = (String) tagObj;        int layoutId = sMapper.getLayoutId(tag);        if (layoutId == 0) {            throw new IllegalArgumentException("View is not a binding layout");         }        return (T) sMapper.getDataBinder(bindingComponent, root, layoutId);     } }@SuppressWarnings("unchecked")static  T bind(DataBindingComponent bindingComponent, View[] roots,        int layoutId) {    return (T) sMapper.getDataBinder(bindingComponent, roots, layoutId); }static  T bind(DataBindingComponent bindingComponent, View root,        int layoutId) {    return (T) sMapper.getDataBinder(bindingComponent, root, layoutId); }

這裡可以看出,bind方法有支援倆種繫結方式,一種是單個View也就是引數中的root,一種是多個View也就是引數中的roots。

bind方法Return型別為T extends ViewDataBinding,也就是說這裡處理後的結果就是我們在例項中實際使用的ActivityXXXBinding。

而sMapper又是什麼?

private static DataBinderMapper sMapper = new DataBinderMapper();

下面我們簡單看一下DataBinderMapper裡面內建了什麼內容?從命名上可以得出,這裡存放一些類似Mapper的東西,會不會是相關的配置檔案?或者說是生成的配置檔案呢?一起來看一下~

package android.databinding;import com.hlq.databindingdemo.BR;class DataBinderMapper  {    // 工程設定最低相容SDK版本
    final static int TARGET_MIN_SDK = 22;    
    // 無參構造
    public DataBinderMapper() {
    }    
    // return繫結後的Data Binding
    public android.databinding.ViewDataBinding getDataBinder(android.databinding.DataBindingComponent bindingComponent, android.view.View view, int layoutId) {        switch(layoutId) {                case com.hlq.databindingdemo.R.layout.item_love_history_show:                    return com.hlq.databindingdemo.databinding.ItemLoveHistoryShowBinding.bind(view, bindingComponent);                case com.hlq.databindingdemo.R.layout.activity_observable:                    return com.hlq.databindingdemo.databinding.ActivityObservableBinding.bind(view, bindingComponent);
                ...
        }        return null;
    }
    
    android.databinding.ViewDataBinding getDataBinder(android.databinding.DataBindingComponent bindingComponent, android.view.View[] views, int layoutId) {        switch(layoutId) {
        }        return null;
    }    
    // 獲取layout Id
    int getLayoutId(String tag) {        // 有效性校驗
        if (tag == null) {            return 0;
        }        // 獲取Layout ID Tag hashCode值
        final int code = tag.hashCode();        switch(code) {            case 813835775: {                if(tag.equals("layout/item_love_history_show_0")) {                    return com.hlq.databindingdemo.R.layout.item_love_history_show;
                }                break;
            }            case -1191836097: {                if(tag.equals("layout/activity_observable_0")) {                    return com.hlq.databindingdemo.R.layout.activity_observable;
                }                break;
            }
            ... 
        }        return 0;
    }    
    // 將ID幹成String
    String convertBrIdToString(int id) {        if (id = InnerBrLookup.sKeys.length) {            return null;
        }        return InnerBrLookup.sKeys[id];
    }    
    // 這裡便是在Xml中應用的名稱空間時,我們指定的別名
    private static class InnerBrLookup {        static String[] sKeys = new String[]{            "_all"
            ,"bean"
            ...};
    }
}

接著繼續檢視bind方法中具體執行了什麼操作:

/**
 * 返回一個繫結後的根佈局或者建立一個不存在的繫結結果
 * 當已知root尚未繫結時,優先使用生成的Binding的bind方法來確保型別安全的範圍
 * @param root 根佈局是引入的繫結的Layout
 * @param bindingComponent 用於資料繫結的DataBindingComponent
 * @return 指定根檢視的ViewDataBinding。如果已經存在,現有的將被返回
 * @throws 當引入Layout不輸入繫結型別則丟擲IllegalArgumentException
 * @see #getBinding(View)
 */@SuppressWarnings("unchecked")public static  T bind(View root,
        DataBindingComponent bindingComponent) {    // 獲取繫結
    T binding = getBinding(root);    // 如果不等於空,直接返回現有
    if (binding != null) {        return binding;
    }    // 獲取引入Layout tag
    Object tagObj = root.getTag();    if (!(tagObj instanceof String)) {        throw new IllegalArgumentException("View is not a binding layout");
    } else {
        String tag = (String) tagObj;        int layoutId = sMapper.getLayoutId(tag);        if (layoutId == 0) {            throw new IllegalArgumentException("View is not a binding layout");
        }        return (T) sMapper.getDataBinder(bindingComponent, root, layoutId);
    }
}

看一下getBinding這裡又是什麼鬼?

/**
 * 檢索負責給定檢視佈局根的繫結。如果沒有繫結,則返回null,否則將DataBindingComponent設定成預設:{@link #setDefaultComponent(DataBindingComponent)}
 *
 * @param view 具有繫結的佈局中的根View
 * @return 如果不是bind檢視或者沒有進行關聯繫結直接返回null
 */public static  T getBinding(View view) {    return (T) ViewDataBinding.getBinding(view);
}static ViewDataBinding getBinding(View v) {    if (v != null) {        // 校驗當前最低相容版本是否大於等於api 14 DataBinderMapper.TARGET_MIN_SDK >= 14
        if (USE_TAG_ID) { // 直接return獲取到的tag
            return (ViewDataBinding) v.getTag(R.id.dataBinding);
        } else { // return是ViewDataBinding型別的tag
            final Object tag = v.getTag();            if (tag instanceof ViewDataBinding) {                return (ViewDataBinding) tag;
            }
        }
    }    return null;
}/**
 * 返回與此檢視關聯的標記和指定的鍵。
 *
 * @param key tag對應key
 *
 * @return Object儲存在此檢視中作為標記,如果未設定,則返回null
 *
 * @see #setTag(int, Object)
 * @see #getTag()
 */public Object getTag(int key) {    if (mKeyedTags != null) return mKeyedTags.get(key);    return null;
}

簡單瞭解下關於getLayoutId方法:

int getLayoutId(String tag) {if (tag == null) {    return 0;
}final int code = tag.hashCode();switch(code) {    case 813835775: {        if(tag.equals("layout/item_love_history_show_0")) {            return com.hlq.databindingdemo.R.layout.item_love_history_show;
        }        break;
    }    case -1191836097: {        if(tag.equals("layout/activity_observable_0")) {            return com.hlq.databindingdemo.R.layout.activity_observable;
        }        break;
    }
    ...
}return 0;
}

根據Tag的hashCode返回對應layout。

到此,setContentView分析結束一個段落。

針對之前關於Data Binding的使用,結合我們剛剛分析的結果,我們簡單回顧下:

  • 首先,改造佈局,也就是新增layout,此處需要注意,不能是merge佈局;

  • 接著拿到當前Activity、給定的Layout以及預設的DataBindingComponent。獲取到Content View父級,也就是DecorView,目前取到控制權;

  • 而最後,則是透過遍歷ChildView新增並繫結即可。當然這裡面忽略了很多細節,例如我們的tag取值,具體繫結操作。有興趣可自行了解下,LZ這裡就不過多的闡述了。

二、inflate分析

上述講述了setContentView,這裡一起來看下關於infate原始碼,看看他們之間又何異同性?

/**
 * 引用一個繫結佈局並返回該佈局的新建立的繫結。
 * 這使用了設定的DataBindingComponent
 * {@link #setDefaultComponent(DataBindingComponent)}.
 * 
 * 除非layoutId是未知的,才使用此版本。否則,使用生成的繫結的引用方法來確保型別安全的引用
 *
 * @param inflater The LayoutInflater used to inflate the binding layout.
 * @param layoutId The layout resource ID of the layout to inflate.
 * @param parent Optional view to be the parent of the generated hierarchy
 *               (if attachToParent is true), or else simply an object that provides
 *               a set of LayoutParams values for root of the returned hierarchy
 *               (if attachToParent is false.)
 * @param attachToParent Whether the inflated hierarchy should be attached to the
 *                       parent parameter. If false, parent is only used to create
 *                       the correct subclass of LayoutParams for the root view in the XML.
 * @return The newly-created binding for the inflated layout or null if
 * the layoutId wasn't for a binding layout.
 * @throws InflateException When a merge layout was used and attachToParent was false.
 * @see #setDefaultComponent(DataBindingComponent)
 */public static  T inflate(LayoutInflater inflater, int layoutId,
        @Nullable ViewGroup parent, boolean attachToParent) {    return inflate(inflater, layoutId, parent, attachToParent, sDefaultComponent);
}/**
 * Inflates a binding layout and returns the newly-created binding for that layout.
 * 

 * Use this version only if layoutId is unknown in advance. Otherwise, use  * the generated Binding's inflate method to ensure type-safe inflation.  *  * @param inflater The LayoutInflater used to inflate the binding layout.  * @param layoutId The layout resource ID of the layout to inflate.  * @param parent Optional view to be the parent of the generated hierarchy  *               (if attachToParent is true), or else simply an object that provides  *               a set of LayoutParams values for root of the returned hierarchy  *               (if attachToParent is false.)  * @param attachToParent Whether the inflated hierarchy should be attached to the  *                       parent parameter. If false, parent is only used to create  *                       the correct subclass of LayoutParams for the root view in the XML.  * @param bindingComponent The DataBindingComponent to use in the binding.  * @return The newly-created binding for the inflated layout or null if  * the layoutId wasn't for a binding layout.  * @throws InflateException When a merge layout was used and attachToParent was false.  */public static  T inflate(         LayoutInflater inflater, int layoutId, @Nullable ViewGroup parent,         boolean attachToParent, DataBindingComponent bindingComponent) {    final boolean useChildren = parent != null && attachToParent;    final int startChildren = useChildren ? parent.getChildCount() : 0;    final View view = inflater.inflate(layoutId, parent, attachToParent);    if (useChildren) {        return bindToAddedViews(bindingComponent, parent, startChildren, layoutId);     } else {        return bind(bindingComponent, view, layoutId);     } }

而這裡的邏輯,則相對簡單一些判斷是否ContentView有內容,有則需要逐個獲取並新增繫結,無則直接繫結即可。

而下面的操作流程則與setContentView一樣了。

一個相當於直接引用佈局,直接拿到了佈局的控制權,而另一個則是需要去獲取上級,透過獲取上級拿到控制權進行操作。

三、生成Util類原始碼閱讀

怎麼看原始碼呢?

首先看一波LZ例項中的MainActivity,經過Data Binding轉化後如下:

//// Source code recreated from a .class file by IntelliJ IDEA// (powered by Fernflower decompiler)//package com.hlq.databindingdemo.databinding;import android.databinding.DataBindingComponent;import android.databinding.DataBindingUtil;import android.databinding.ViewDataBinding;import android.databinding.ViewDataBinding.IncludedLayouts;import android.support.annotation.NonNull;import android.support.annotation.Nullable;import android.util.SparseIntArray;import android.view.LayoutInflater;import android.view.View;import android.view.ViewGroup;import android.view.View.OnClickListener;import android.widget.Button;import android.widget.ScrollView;import com.hlq.databindingdemo.MainActivity.Presenter;// 繼承ViewDataBindingpublic class ActivityMainBinding extends ViewDataBinding {    
    @Nullable
    private static final IncludedLayouts sIncludes = null;    
    @Nullable
    private static final SparseIntArray sViewsWithIds = null;    
    @NonNull
    public final Button bindData;    
    @NonNull
    public final Button bingListener;    
    @NonNull
    public final Button imageView;
    ...    
    @NonNull
    private final ScrollView mboundView0;    
    @Nullable
    private Presenter mPersenter;    
    private ActivityMainBinding.OnClickListenerImpl mPersenterOnClickAndroidViewViewOnClickListener;    
    private long mDirtyFlags = -1L;    public ActivityMainBinding(@NonNull DataBindingComponent bindingComponent, @NonNull View root) {        super(bindingComponent, root, 0);        // 從map中讀取配置資訊 這裡麵包含View中的控制元件等
        Object[] bindings = mapBindings(bindingComponent, root, 13, sIncludes, sViewsWithIds);        // 例項化以及設定tag
        this.bindData = (Button)bindings[1];        this.bindData.setTag((Object)null);
        ...        this.setRootTag(root);        // 進行view的非同步重新整理
        this.invalidateAll();
    }    // 執行View非同步重新整理
    public void invalidateAll() {        synchronized(this) {            this.mDirtyFlags = 2L;
        }        this.requestRebind();
    }    // 判斷是否有Observable化的欄位資料被更新
    public boolean hasPendingBindings() {        synchronized(this) {            return this.mDirtyFlags != 0L;
        }
    }    // 設定xml中引用的viewModel
    public boolean setVariable(int variableId, @Nullable Object variable) {        boolean variableSet = true;        if (12 == variableId) {            this.setPersenter((Presenter)variable);
        } else {
            variableSet = false;
        }        return variableSet;
    }    // 設定事件
    public void setPersenter(@Nullable Presenter Persenter) {        this.mPersenter = Persenter;        // 設定同步鎖
        synchronized(this) {            this.mDirtyFlags |= 1L;
        }        this.notifyPropertyChanged(12);        super.requestRebind();
    }    @Nullable
    public Presenter getPersenter() {        return this.mPersenter;
    }    protected boolean onFieldChange(int localFieldId, Object object, int fieldId) {        return false;
    }    // 執行繫結
    protected void executeBindings() {        long dirtyFlags = 0L;        synchronized(this) {
            dirtyFlags = this.mDirtyFlags;            this.mDirtyFlags = 0L;
        }

        Presenter persenter = this.mPersenter;
        OnClickListener persenterOnClickAndroidViewViewOnClickListener = null;        if ((dirtyFlags & 3L) != 0L && persenter != null) {
            persenterOnClickAndroidViewViewOnClickListener = (this.mPersenterOnClickAndroidViewViewOnClickListener == null ? (this.mPersenterOnClickAndroidViewViewOnClickListener = new ActivityMainBinding.OnClickListenerImpl()) : this.mPersenterOnClickAndroidViewViewOnClickListener).setValue(persenter);
        }        if ((dirtyFlags & 3L) != 0L) {            this.bindData.setOnClickListener(persenterOnClickAndroidViewViewOnClickListener);            this.bingListener.setOnClickListener(persenterOnClickAndroidViewViewOnClickListener);            this.imageView.setOnClickListener(persenterOnClickAndroidViewViewOnClickListener);            this.normalRecyclerView.setOnClickListener(persenterOnClickAndroidViewViewOnClickListener);            this.observableFieldStudy.setOnClickListener(persenterOnClickAndroidViewViewOnClickListener);            this.showLoveHistory.setOnClickListener(persenterOnClickAndroidViewViewOnClickListener);            this.showLoveHistoryOnClick.setOnClickListener(persenterOnClickAndroidViewViewOnClickListener);            this.theWordForMe.setOnClickListener(persenterOnClickAndroidViewViewOnClickListener);            this.updateData.setOnClickListener(persenterOnClickAndroidViewViewOnClickListener);            this.useExpression.setOnClickListener(persenterOnClickAndroidViewViewOnClickListener);            this.useInclude.setOnClickListener(persenterOnClickAndroidViewViewOnClickListener);            this.useViewStub.setOnClickListener(persenterOnClickAndroidViewViewOnClickListener);
        }

    }    @NonNull
    public static ActivityMainBinding inflate(@NonNull LayoutInflater inflater, @Nullable ViewGroup root, boolean attachToRoot) {        return inflate(inflater, root, attachToRoot, DataBindingUtil.getDefaultComponent());
    }    @NonNull
    public static ActivityMainBinding inflate(@NonNull LayoutInflater inflater, @Nullable ViewGroup root, boolean attachToRoot, @Nullable DataBindingComponent bindingComponent) {        return (ActivityMainBinding)DataBindingUtil.inflate(inflater, 2131296288, root, attachToRoot, bindingComponent);
    }    @NonNull
    public static ActivityMainBinding inflate(@NonNull LayoutInflater inflater) {        return inflate(inflater, DataBindingUtil.getDefaultComponent());
    }    @NonNull
    public static ActivityMainBinding inflate(@NonNull LayoutInflater inflater, @Nullable DataBindingComponent bindingComponent) {        return bind(inflater.inflate(2131296288, (ViewGroup)null, false), bindingComponent);
    }    // 繫結檢視
    @NonNull
    public static ActivityMainBinding bind(@NonNull View view) {        return bind(view, DataBindingUtil.getDefaultComponent());
    }    // 驗證當前佈局是否為Binding指定,如果是則直接Return繫結後實體,反之丟擲異常
    @NonNull
    public static ActivityMainBinding bind(@NonNull View view, @Nullable DataBindingComponent bindingComponent) {        if (!"layout/activity_main_0".equals(view.getTag())) {            throw new RuntimeException("view tag isn't correct on view:" + view.getTag());
        } else {            return new ActivityMainBinding(bindingComponent, view);
        }
    }    // 直接實現系統點選事件
    public static class OnClickListenerImpl implements OnClickListener {        private Presenter value;        public OnClickListenerImpl() {
        }        // 搞了一個實現,用於設定Value,也就是value的初始化
        public ActivityMainBinding.OnClickListenerImpl setValue(Presenter value) {            this.value = value;            return value == null ? null : this;
        }        // 點選事件初始化
        public void onClick(View arg0) {            this.value.onClick(arg0);
        }
    }
}

下面針對其中幾個方法進行原始碼閱讀分析:

  • mapBindings:遍歷檢視層次結構

Object[] bindings = mapBindings(bindingComponent, root, 13, sIncludes, sViewsWithIds);

這裡為啥是13?是因為LZ介面中放置了1個TextView,12個Button按鈕。

/**
 * 在根下遍歷檢視層次結構,並將標記的檢視,包含檢視和帶有ID的檢視拖放到返回的Object[]中。這用於遍歷檢視層以查詢所有繫結和ID檢視。
 *
 * @param bindingComponent 用於此繫結的繫結元件
 * @param roots 檢視層次結構的根檢視層級。 這與合併標籤一起使用
 * @param numBindings ID'd檢視的總數,包含表示式的檢視和包含
 * @param includes 包含佈局資訊,由它們的容器索引索引
 * @param viewsWithIds 沒有標籤但擁有ID的檢視索引
 * @return 大小為numBindings的陣列包含層次結構中具有ID(在viewsWithIds中具有元素)的所有檢視,都被標記為包含表示式或包含的佈局的繫結。
 * @hide
 */protected static Object[] mapBindings(DataBindingComponent bindingComponent, View[] roots,        int numBindings, IncludedLayouts includes, SparseIntArray viewsWithIds) {
    Object[] bindings = new Object[numBindings];    for (int i = 0; i 
  • setRootTag:設定Root標籤

setTag版本相容:

protected void setRootTag(View view) {    if (USE_TAG_ID) { // DataBinderMapper.TARGET_MIN_SDK >= 14
        view.setTag(R.id.dataBinding, this);
    } else {
        view.setTag(this);
    }
}

SparseArray,看這個,果然,怪不得之前聽說推薦使用SparseArray。有時間得看看咯。

public void setTag(int key, final Object tag) {    // If the package id is 0x00 or 0x01, it's either an undefined package
    // or a framework id
    if ((key >>> 24) (2);
    }

    mKeyedTags.put(key, tag);
}
  • invalidateAll:執行View非同步重新整理

透過呼叫requestRebind,執行View非同步重新整理。

public void invalidateAll() {    synchronized(this) {        this.mDirtyFlags = 2L;
    }    this.requestRebind();
}
  • requestRebind:強制View非同步重新整理

protected void requestRebind() {    // 如果bind有值,直接執行非同步View更新
    if (mContainingBinding != null) {
        mContainingBinding.requestRebind();
    } else {        // 開啟同步鎖
        synchronized (this) {            if (mPendingRebind) {                return;
            }
            mPendingRebind = true;
        }        // 版本相容 SDK_INT >= 16
        if (USE_CHOREOGRAPHER) {            // 釋出幀回撥以在下一幀上執行 也就是View更新
            mChoreographer.postFrameCallback(mFrameCallback);
        } else {            // 將Runnable r被新增到訊息佇列中。Runnable將在該處理程式所連線的執行緒上執行。
            mUIThreadHandler.post(mRebindRunnable);
        }
    }
}
  • postFrameCallback:傳送Frame回撥

// Posts a callback to run on the next framepublic void postFrameCallback(FrameCallback callback) {
    postFrameCallbackDelayed(callback, 0);
}// 回撥型別:提交回撥。處理幀的繪製後操作。
    // 遍歷完成後執行。 報告的{@link #getFrameTime()幀時間}
    // 在此回撥期間可能會更新以反映在遍歷正在進行時發生的延遲,以防重型佈局操作導致某些幀被跳過。// 在此回撥期間報告的幀時間提供更好的估計動畫(以及檢視層次狀態的其他更新)實際生效的幀的開始時間。public static final int CALLBACK_COMMIT = 3;private static final int CALLBACK_LAST = CALLBACK_COMMIT;// 釋出回撥以在指定延遲後的下一幀上執行。回撥執行一次後自動刪除。public void postFrameCallbackDelayed(FrameCallback callback, long delayMillis) {    if (callback == null) {        throw new IllegalArgumentException("callback must not be null");
    }

    postCallbackDelayedInternal(CALLBACK_ANIMATION,
            callback, FRAME_CALLBACK_TOKEN, delayMillis);
}private void postCallbackDelayedInternal(int callbackType,
        Object action, Object token, long delayMillis) {    if (DEBUG_FRAMES) {
        Log.d(TAG, "PostCallback: type=" + callbackType
                + ", action=" + action + ", token=" + token
                + ", delayMillis=" + delayMillis);
    }    // 開啟同步鎖
    synchronized (mLock) {        // 獲取的是系統的時間
        final long now = SystemClock.uptimeMillis();        final long dueTime = now + delayMillis;        // Choreographer機制,用於同Vsync機制配合,實現統一排程介面繪圖.
        // 新增佇列鎖
        mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);        if (dueTime 

不行了,暈死了,太多太多不懂了。。。

MMP呦,得趕緊開啟Android重走路了。。。

結束語

原本打算挨個原始碼解析下,沒想到看的看的發現,我日,太多東西了,想一篇文章搞定?對於目前的LZ而言,太過於困難。與其繼續死摳,不如趕緊加強基礎,練好基本功,再來攻克Boss。

原文連結:http://www.apkbus.com/blog-904057-77930.html

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/964/viewspace-2806564/,如需轉載,請註明出處,否則將追究法律責任。

相關文章