美文网首页
DataBinding原理分析

DataBinding原理分析

作者: vpractical | 来源:发表于2019-10-16 17:32 被阅读0次

    [TOC]

    结论放在前面:
    整个过程就是将xml拆成2个xml,然后读取xml中带tag控件在ActivityMainBindingImpl创建时制作控件副本,User属性改变时notify了,会触发监听器重新获取属性值,前直接给副本设置属性,页面刷新

    1.简单Demo(GitHub)

    定义了一个user实体,不停变化user的属性和显示的内容

    1.1 MainActivity

    public class MainActivity extends AppCompatActivity {
        public static List<User> list = new ArrayList<>();
        static {
            list.add(new User("赛利亚",25,"https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=1911428589,1373321293&fm=26&gp=0.jpg"));
            list.add(new User("云幂",23,"https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=3231129193,917533735&fm=26&gp=0.jpg"));
            list.add(new User("莎兰",28,"https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=653736840,2449474724&fm=26&gp=0.jpg"));
        }
        int index = 0;
        User user = new User();
        Handler handler = new Handler();
        Runnable r = new Runnable() {
            @Override
            public void run() {
                User u = list.get(index++);
                user.setNick(u.getNick());
                user.setAge(u.getAge());
                user.setAvatar(u.getAvatar());
                if(index >= list.size()){
                    index = 0;
                }
                handler.postDelayed(r,6 * 1000);
            }
        };
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            r.run();
            ActivityMainBinding mainBinding = DataBindingUtil.setContentView(this,R.layout.activity_main);
            mainBinding.setUser(user);
            mainBinding.btnSec.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    handler.removeCallbacks(r);
                    r.run();
                }
            });
        }
    
    }
    

    1.2User

    public class User extends BaseObservable {
        private String nick;
        private int age;
        private String avatar;
        public User(){}
        public User(String nick, int age, String avatar) {
            this.nick = nick;
            this.age = age;
            this.avatar = avatar;
        }
        @Bindable
        public String getNick() {
            return nick;
        }
        public void setNick(String nick) {
            this.nick = nick;
            notifyPropertyChanged(BR.nick);
        }
        @Bindable
        public int getAge() {
            return age;
        }
        public void setAge(int age) {
            this.age = age;
            notifyPropertyChanged(BR.age);
        }
        @Bindable
        public String getAvatar() {
            return avatar;
        }
        public void setAvatar(String avatar) {
            this.avatar = avatar;
            notifyPropertyChanged(BR.avatar);
        }
        @BindingAdapter("avatar")
        public static void getAvatar(ImageView iv,String url){
            Picasso.with(iv.getContext()).load(url).into(iv);
        }
        public void changeUser(View view){
            setAge(++age);
        }
    }
    

    1.3布局文件activity-main

    <?xml version="1.0" encoding="utf-8"?>
    <layout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        >
        <data>
            <variable
                name="user"
                type="com.y.mj.User" />
        </data>
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical"
            android:gravity="center_horizontal"
            >
            <Button
                android:id="@+id/btnSec"
                android:layout_width="match_parent"
                android:layout_height="50dp"
                android:text="二级页面"
                />
            <Button
                android:id="@+id/btnUser"
                android:layout_width="match_parent"
                android:layout_height="50dp"
                android:text="改变User"
                android:onClick="@{user.changeUser}"
                />
            <ImageView
                android:layout_marginTop="30dp"
                android:layout_width="300dp"
                android:layout_height="300dp"
                android:scaleType="centerCrop"
                app:avatar="@{user.avatar}"
                />
    
            <TextView
                android:layout_marginTop="10dp"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@{user.nick + `   ` + user.age}"
                />
        </LinearLayout>
    </layout>
    

    2.源码分析

    2.1布局文件

    databinding是用layout标签,将数据data和原本的layout包起来,编译时会拆分成2个,以activity_main为例:

    • build/intermediates/data_binding_layout_info_type_merge/debug/mergeDebugResources/out/activity_main-layout.xml
    • incremental/mergeDebugResources/stripped.dir/layout/activity_main.xml

    先看后者,几乎是原layout布局,在跟布局和使用了databinding代码的控件里都增加了一个tag属性

    <?xml version="1.0" encoding="utf-8"?>
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical"
            android:gravity="center_horizontal"
             android:tag="layout/activity_main_0" 
    xmlns:android="http://schemas.android.com/apk/res/android" 
    xmlns:app="http://schemas.android.com/apk/res-auto">
    
            <Button
                android:id="@+id/btnSec"
                android:layout_width="match_parent"
                android:layout_height="50dp"
                android:text="二级页面"
                />
            <Button
                android:id="@+id/btnUser"
                android:layout_width="match_parent"
                android:layout_height="50dp"
                android:text="改变User"
                android:tag="binding_1"             
                />
    
            <ImageView
                android:layout_marginTop="30dp"
                android:layout_width="300dp"
                android:layout_height="300dp"
                android:scaleType="centerCrop"
                android:tag="binding_2"    
                />
    
            <TextView
                android:layout_marginTop="10dp"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:tag="binding_3"                       
                />
        </LinearLayout>
    

    再看前者,Layout+Variables+Target记录了所有databinding代码

    <?xml version="1.0" encoding="utf-8"?>
    
    <Layout layout="activity_main" modulePackage="com.y.mj" absoluteFilePath="E:\GitHub\MVVMjetpack\app\src\main\res\layout\activity_main.xml" directory="layout" isMerge="false">
        <Variables declared="true" type="com.y.mj.User" name="user">
            <location startLine="7" startOffset="8" endLine="9" endOffset="34"/>
        </Variables>
        <Targets>
            <Target tag="layout/activity_main_0" view="LinearLayout">
                <Expressions/>
                <location startLine="12" startOffset="4" endLine="47" endOffset="18"/>
            </Target>
            <Target id="@+id/btnUser" tag="binding_1" view="Button">
                <Expressions>
                    <Expression text="user.changeUser" attribute="android:onClick">
                        <Location startLine="30" startOffset="12" endLine="30" endOffset="47"/>
                        <TwoWay>false</TwoWay>
                        <ValueLocation startLine="30" startOffset="31" endLine="30" endOffset="45"/>
                    </Expression>
                </Expressions>
                <location startLine="25" startOffset="8" endLine="31" endOffset="13"/>
            </Target>
            <Target tag="binding_2" view="ImageView">
                <Expressions>
                    <Expression text="user.avatar" attribute="app:avatar">
                        <Location startLine="38" startOffset="12" endLine="38" endOffset="38"/>
                        <TwoWay>false</TwoWay>
                        <ValueLocation startLine="38" startOffset="26" endLine="38" endOffset="36"/>
                    </Expression>
                </Expressions>
                <location startLine="33" startOffset="8" endLine="39" endOffset="13"/>
            </Target>
            <Target tag="binding_3" view="TextView">
                <Expressions>
                    <Expression text="user.nick + `   ` + user.age" attribute="android:text">
                        <Location startLine="45" startOffset="12" endLine="45" endOffset="57"/>
                        <TwoWay>false</TwoWay>
                        <ValueLocation startLine="45" startOffset="28" endLine="45" endOffset="55"/>
                    </Expression>
                </Expressions>
                <location startLine="41" startOffset="8" endLine="46" endOffset="13"/>
            </Target>
            <Target id="@+id/btnSec" view="Button">
                <Expressions/>
                <location startLine="19" startOffset="8" endLine="24" endOffset="13"/>
            </Target>
        </Targets>
    </Layout>
    

    2.2User

    image.png

    User中使用的BR.attr,BR是编译器生成的类似R文件的一个东西,属性名作为id名

    public class BR {
      public static final int _all = 0;
      public static final int nick = 1;
      public static final int avatar = 2;
      public static final int user = 3;
      public static final int age = 4;
    }
    

    2.3MainActivity

        ActivityMainBinding mainBinding = DataBindingUtil.setContentView(this,R.layout.activity_main);
        mainBinding.setUser(user);
    

    从DataBindingUtil的setContentView方法一直找进去,会看到

        public static <T extends ViewDataBinding> T setContentView(@NonNull Activity activity,
                int layoutId, @Nullable 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的setContentView,然后调用

    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);
            }
        }
    ...
        static <T extends ViewDataBinding> T bind(DataBindingComponent bindingComponent, View root,
                int layoutId) {
            return (T) sMapper.getDataBinder(bindingComponent, root, layoutId);
        }
    ...
    private static DataBinderMapper sMapper = new DataBinderMapperImpl();
    

    然后从DataBinderMapperImpl找getDataBinder方法

      @Override
      public ViewDataBinding getDataBinder(DataBindingComponent component, View view, int layoutId) {
        int localizedLayoutId = INTERNAL_LAYOUT_ID_LOOKUP.get(layoutId);
        if(localizedLayoutId > 0) {
          final Object tag = view.getTag();
          if(tag == null) {
            throw new RuntimeException("view must have a tag");
          }
          switch(localizedLayoutId) {
            case  LAYOUT_ACTIVITYMAIN: {
              if ("layout/activity_main_0".equals(tag)) {
                return new ActivityMainBindingImpl(component, view);
              }
              throw new IllegalArgumentException("The tag for activity_main is invalid. Received: " + tag);
            }
          }
        }
        return null;
      }
    

    if里layout/activity_main_0这个标签看起来眼熟吧,就在拆分后的2个xml中后者根布局的tag属性,找到这个,MainActivity就得到它的ActivityMainBinding对象。

    public class ActivityMainBindingImpl extends ActivityMainBinding  {
    
       @Nullable
       private static final androidx.databinding.ViewDataBinding.IncludedLayouts sIncludes;
       @Nullable
       private static final android.util.SparseIntArray sViewsWithIds;
       static {
           sIncludes = null;
           sViewsWithIds = new android.util.SparseIntArray();
           sViewsWithIds.put(R.id.btnSec, 4);
       }
       @NonNull
       private final android.widget.LinearLayout mboundView0;
       @NonNull
       private final android.widget.ImageView mboundView2;
       @NonNull
       private final android.widget.TextView mboundView3;
       private OnClickListenerImpl mUserChangeUserAndroidViewViewOnClickListener;
       private ActivityMainBindingImpl(androidx.databinding.DataBindingComponent bindingComponent, View root, Object[] bindings) {
           this.btnUser.setTag(null);
           this.mboundView0 = (android.widget.LinearLayout) bindings[0];
           this.mboundView0.setTag(null);
           this.mboundView2 = (android.widget.ImageView) bindings[2];
           this.mboundView2.setTag(null);
           this.mboundView3 = (android.widget.TextView) bindings[3];
           this.mboundView3.setTag(null);
           setRootTag(root);
           // listeners
           invalidateAll();
       }
    ...
    

    ActivityMainBindingImpl 中,制作了每个tag控件的副本,并移除了控件的tag属性,这里也是mvvm非常耗内存的原因

    ActivityMainBindingImpl 继承自ViewDataBinding,这里面有个static代码块

        static {
            if (VERSION.SDK_INT < VERSION_CODES.KITKAT) {
                ROOT_REATTACHED_LISTENER = null;
            } else {
                ROOT_REATTACHED_LISTENER = new OnAttachStateChangeListener() {
                    @TargetApi(VERSION_CODES.KITKAT)
                    @Override
                    public void onViewAttachedToWindow(View v) {
                        // execute the pending bindings.
                        final ViewDataBinding binding = getBinding(v);
                        binding.mRebindRunnable.run();
                        v.removeOnAttachStateChangeListener(this);
                    }
                    @Override
                    public void onViewDetachedFromWindow(View v) {
                    }
                };
            }
        }
    

    这里生成了一个监听器ROOT_REATTACHED_LISTENER,在User中setNick()时调用的notifyPropertyChanged(BR.nick)会触发这个监听器,从runnable方法找到executePendingBindings(),然后一路找到ActivityMainBindingImpl的executeBindings()。
    这里获取到notify后改变的User中的属性值设置给控件的副本

    @Override
        protected void executeBindings() {
            long dirtyFlags = 0;
            synchronized(this) {
                dirtyFlags = mDirtyFlags;
                mDirtyFlags = 0;
            }
            java.lang.String userAvatar = null;
            java.lang.String userNickJavaLangString = null;
            int userAge = 0;
            com.y.mj.User user = mUser;
            java.lang.String userNick = null;
            java.lang.String userNickJavaLangStringUserAge = null;
            android.view.View.OnClickListener userChangeUserAndroidViewViewOnClickListener = null;
            if ((dirtyFlags & 0x1fL) != 0) {
                if ((dirtyFlags & 0x13L) != 0) {
                        if (user != null) {
                            // read user.avatar
                            userAvatar = user.getAvatar();
                        }
                }
                if ((dirtyFlags & 0x1dL) != 0) {
    
                        if (user != null) {
                            // read user.age
                            userAge = user.getAge();
                            // read user.nick
                            userNick = user.getNick();
                        }
                        // read (user.nick) + ("   ")
                        userNickJavaLangString = (userNick) + ("   ");
                        // read ((user.nick) + ("   ")) + (user.age)
                        userNickJavaLangStringUserAge = (userNickJavaLangString) + (userAge);
                }
                if ((dirtyFlags & 0x11L) != 0) {
                        if (user != null) {
                            // read user::changeUser
                            userChangeUserAndroidViewViewOnClickListener = (((mUserChangeUserAndroidViewViewOnClickListener == null) ? (mUserChangeUserAndroidViewViewOnClickListener = new OnClickListenerImpl()) : mUserChangeUserAndroidViewViewOnClickListener).setValue(user));
                        }
                }
            }
            // batch finished
            if ((dirtyFlags & 0x11L) != 0) {
                // api target 1
    
                this.btnUser.setOnClickListener(userChangeUserAndroidViewViewOnClickListener);
            }
            if ((dirtyFlags & 0x13L) != 0) {
                // api target 1
                com.y.mj.User.getAvatar(this.mboundView2, userAvatar);
            }
            if ((dirtyFlags & 0x1dL) != 0) {
                // api target 1
                androidx.databinding.adapters.TextViewBindingAdapter.setText(this.mboundView3, userNickJavaLangStringUserAge);
            }
        }
    

    关于点击事件,也是编译器生成的代码实现的,自己定义一个监听器,触发时通过user对象调用成员方法

        private OnClickListenerImpl mUserChangeUserAndroidViewViewOnClickListener;
    
        public static class OnClickListenerImpl implements android.view.View.OnClickListener{
            private com.y.mj.User value;
            public OnClickListenerImpl setValue(com.y.mj.User value) {
                this.value = value;
                return value == null ? null : this;
            }
            @Override
            public void onClick(android.view.View arg0) {
                this.value.changeUser(arg0); 
            }
        }
    
        userChangeUserAndroidViewViewOnClickListener = (((mUserChangeUserAndroidViewViewOnClickListener == null) ? 
            (mUserChangeUserAndroidViewViewOnClickListener = new OnClickListenerImpl()) : 
            mUserChangeUserAndroidViewViewOnClickListener).setValue(user));
    
        this.btnUser.setOnClickListener(userChangeUserAndroidViewViewOnClickListener);
    

    关于imageview加载图片

        app:avatar="@{user.avatar}"
    
        @BindingAdapter("avatar")
        public static void getAvatar(ImageView iv,String url){
            Picasso.with(iv.getContext()).load(url).into(iv);
        }
    
        com.y.mj.User.getAvatar(this.mboundView2, userAvatar);
    

    相关文章

      网友评论

          本文标题:DataBinding原理分析

          本文链接:https://www.haomeiwen.com/subject/vktumctx.html