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 extends ViewDataBinding> T setContentView(Activity activity, int layoutId) { return setContentView(activity, layoutId, sDefaultComponent); }
可以看到这里直接return了一个setContentView,并且将我们传递的参数继续发送。
一起来看看这里干了什么鬼?
public static <T extends ViewDataBinding> 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 extends ViewDataBinding> 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 < childrenAdded; i++) { children[i] = parent.getChildAt(i + startChildren); } return bind(component, children, layoutId); } }
下面继续深入bind方法。
/** * Returns the binding for the given layout root or creates a binding if one * does not exist. * <p> * Prefer using the generated Binding's <code>bind</code> method to ensure type-safe inflation * when it is known that <code>root</code> 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 extends ViewDataBinding> 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 extends ViewDataBinding> T bind(DataBindingComponent bindingComponent, View[] roots, int layoutId) { return (T) sMapper.getDataBinder(bindingComponent, roots, layoutId); }static <T extends ViewDataBinding> 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 < 0 || id >= InnerBrLookup.sKeys.length) { return null; } return InnerBrLookup.sKeys[id]; } // 这里便是在Xml中应用的命名空间时,我们指定的别名 private static class InnerBrLookup { static String[] sKeys = new String[]{ "_all" ,"bean" ...}; } }
接着继续查看bind方法中具体执行了什么操作:
/** * 返回一个绑定后的根布局或者创建一个不存在的绑定结果 * 当已知<code>root</code>尚未绑定时,优先使用生成的Binding的<code>bind</code>方法来确保类型安全的范围 * @param root 根布局是引入的绑定的Layout * @param bindingComponent 用于数据绑定的DataBindingComponent * @return 指定根视图的ViewDataBinding。如果已经存在,现有的将被返回 * @throws 当引入Layout不输入绑定类型则抛出IllegalArgumentException * @see #getBinding(View) */@SuppressWarnings("unchecked")public static <T extends ViewDataBinding> 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这里又是什么鬼?
/** * 检索负责给定视图布局根的绑定。如果没有绑定,则返回<code>null</code>,否则将DataBindingComponent设置成默认:{@link #setDefaultComponent(DataBindingComponent)} * * @param view 具有绑定的布局中的根<code>View</code> * @return 如果不是bind视图或者没有进行关联绑定直接返回null */public static <T extends ViewDataBinding> 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)}. * * 除非<code>layoutId</code>是未知的,才使用此版本。否则,使用生成的绑定的引用方法来确保类型安全的引用 * * @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 <code>null</code> 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 extends ViewDataBinding> 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. * <p> * Use this version only if <code>layoutId</code> 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 <code>null</code> if * the layoutId wasn't for a binding layout. * @throws InflateException When a merge layout was used and attachToParent was false. */public static <T extends ViewDataBinding> 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 < roots.length; i++) { mapBindings(bindingComponent, roots[i], bindings, includes, viewsWithIds, true); } return bindings; }
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) { throw new IllegalArgumentException("The key must be an application-specific " + "resource id."); } setKeyedTag(key, tag); }private void setKeyedTag(int key, Object tag) { if (mKeyedTags == null) { mKeyedTags = new SparseArray<Object>(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 <= now) { scheduleFrameLocked(now); } else { Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action); msg.arg1 = callbackType; msg.setAsynchronous(true); mHandler.sendMessageAtTime(msg, dueTime); } } }// 调度Frame锁定private void scheduleFrameLocked(long now) { if (!mFrameScheduled) { mFrameScheduled = true; if (USE_VSYNC) { if (DEBUG_FRAMES) { Log.d(TAG, "Scheduling next frame on vsync."); } // If running on the Looper thread, then schedule the vsync immediately, // otherwise post a message to schedule the vsync from the UI thread // as soon as possible. if (isRunningOnLooperThreadLocked()) { scheduleVsyncLocked(); } else { Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC); msg.setAsynchronous(true); mHandler.sendMessageAtFrontOfQueue(msg); } } else { final long nextFrameTime = Math.max( mLastFrameTimeNanos / TimeUtils.NANOS_PER_MS + sFrameDelay, now); if (DEBUG_FRAMES) { Log.d(TAG, "Scheduling next frame in " + (nextFrameTime - now) + " ms."); } Message msg = mHandler.obtainMessage(MSG_DO_FRAME); msg.setAsynchronous(true); mHandler.sendMessageAtTime(msg, nextFrameTime); } } }
不行了,晕死了,太多太多不懂了。。。
MMP呦,得赶紧开启Android重走路了。。。
结束语
原本打算挨个源码解析下,没想到看的看的发现,我日,太多东西了,想一篇文章搞定?对于目前的LZ而言,太过于困难。与其继续死抠,不如赶紧加强基础,练好基本功,再来攻克Boss。
共同学习,写下你的评论
评论加载中...
作者其他优质文章