Android技术栈(四)Android-Jetpack-MVVM-完全实践

2.2 DataBinding的兼容性

先说一点,DataBinding风格的xml会有"奇怪"的东西入侵Android原生的xml格式,这种格式LayoutInfalter是无法理解,但是,当你对这些奇怪的xml使用LayoutInfalter#inflate时亦不会报错,并且布局也正常加载了,这是为什么呢?

这是因为在打包时,Gradle通过APT把你的DataBinding风格的xml全部翻译了一遍,让LayoutInfalter能读懂他们,正是因为这个兼容的实现,而使得我们可以在使用和不使用DataBinding间自由的切换.

2.3 DataBinding风格的XML

要想使用DataBinding,先在模块的build.gradle中添加

android{
//省略…
dataBinding {
enabled = true
}
}

来启用DataBinding支持.

DataBinding不需要额外的类库支持,它被附加在你的android插件中,它的版本号与你的android插件版本一致.

classpath ‘com.android.tools.build:gradle:3.3.2’

DataBinding风格的xml中,最外层必须是layout标签,并且不支持merge标签,编写xml就像下面这样







2.3.1 变量领域

data标签包裹的是变量领域,在这里你可以使用variable定义这个布局所要绑定的变量类型,使用name来指定变量名,然后用type来指定其类型.

如果一些类型比较长,而且由需要经常使用你可以像Java一样使用import导入他们(java.lang.*会被默认导入),然后就不用写出完全限定名了,就像这样


有必要时(比如名字冲突),你还可以用Action为一个类型指定一个别名,这样你就能在下文中使用这个别名.

2.3.2 转义字符

熟悉xml的同学可能都知道<>xml中是非法字符,那么要使用泛型的时候,我们就需要使用xml中的转义字符&lt;&gt;来进行转义

//↓错误,编译时会报错×

//↓正确,可以通过编译√

data标签结束后就是原本的布局编写的位置了,这部分基本和以前差不多,只是加入了DataBinding表达式

//......
2.3.3 DataBinding表达式

@{}包裹的位置被称为DataBinding表达式,DataBinding表达式几乎支持Java所有的运算符,并且增加了一些额外的操作,这允许我们在xml中有一定的Java编程体验,学过Java web的同学可能会觉得它很像JSP:

  • 不需要xml转义的二元运算+,-,/,*,%,||,|,^,==
  • 需要xml转义的二元运算&&,>> >>>,<<,>,<,>=,<=,与泛型一样运算符>=,>,<,<=等,也是需要转义的,&需要用&amp;转义,这确实有些蹩脚,但这是xml的局限性,我们无法避免,所以在DataBinding风格的xml中应该尽可能的少用这些符号.
  • lambda表达式@{()->persenter.doSomething()}
  • 三元运算?:
  • null合并运算符??,若左边不为空则选择左边,否则选择右边

android:text=“@{nullableString??This a string}”

  • 自动导入的context变量,你可以在xml中的任意表达式使用context这个变量,该Context是从该布局的根ViewgetContext获取的,如果你设置了自己的context变量,那么将会覆盖掉它
  • 若表达式中有字符串文本xml需要特殊处理

用单引号包围外围,表达式使用双引号
android:text=‘@{“This a string”}’
或者使用包围字符串,对,就Esc下面那个键的符号 android:text="@{This a string`}"

  • 判断类型instanceof
  • 括号()
  • 空值null
  • 方法调用,字段访问,以及GetterSetter的简写,比如User#getNameUser#setName现在都可以直接写成@{user.name},这种表达式也是最简单的表达式,属于直接赋值表达式
  • 默认值default,在xml

android:text="@{file.name, default=no name}"

  • 下标[],不只是数组,List,SparseArray,Map现在都可以使用该运算符
  • 使用@读取资源文件,如下,但是不支持读取mipmap下的文件

android:text=“@{@string/text}”
//或者把它作为表达式的一部分
android:padding=“@{large? @dimen/large : @dimen/small}”

有一些资源需要显示引用

类型正常情况DataBinding表达式引用
String[]@array@stringArray
int[]@array@intArray
TypedArray@array@typedArray
ColorStateList@animator@stateListAnimator
StateListAnimator@color@colorStateList

还有一些操作是DataBinding表达式中没有的,我们无法使用它们:

  • 没有this
  • 没有super
  • 不能创建对象new
  • 不能使用泛型方法的显示调用Collections.<String>emptyList()

编写简单的DataBinding表达式,就像下面这样

应该避免出现较为复杂的DataBinding表达式,以全部都是直接赋值表达式为佳,数据的处理应该交给布局控制器或者ViewModel来做,布局应该只负责渲染数据.

2.3.4 使用在Java中生成的ViewDataBinding

使用DataBindingAndroid Studio会为每个xml布局生成一个继承自ViewDataBinding的子类型,来帮助我们将xml文件中定义的绑定关系映射到Java中.

比如,如果你有一个R.layout.fragment_main的布局文件,那么他就会为你在当前包下生成一个,FragmentMainBindingViewDataBinding.

Java实化DataBinding风格xml布局与传统方式有所不同.

  • Actvity

private ActivityHostBinding mBinding;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mBinding = DataBindingUtil.setContentView(this, R.layout.activity_host);
}

  • 在自定义ViewFragment

private FragmentMainBinding mBinding;

@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
mBinding = DataBindingUtil.inflate(inflater,
R.layout.fragment_main,
container,
false);
return mBinding.getRoot();
}

  • 在已经使用普通LayoutInfalter实例化的View上(xml必须是DataBinding风格的,普通LayoutInflater实例化布局时不会触发任何绑定机制,DataBindingUtil#bind才会发生绑定)

View view = LayoutInflater.from(context).inflate(R.layout.item_view,null,false);
ItemViewBinding binding = DataBindingUtil.bind(view);

你在xml设置的变量他会在这个类中为你生成对应的GetterSetter.你可以调用它们给界面赋值,比如之前的我们定义的action.

//这里的代码是Java8的lambda
mBinding.setAction(v->{
//TODO
})

2.3.5 使用BR文件

它还会为你生成一个类似RBR文件,里面包含了你在DataBinding风格xml中定义的所有变量名的引用(由于使用的是APT生成,有时候需要Rebuild Project才能刷新),比如我们之前的action,它会为我们生成BR.action,我们可以这么使用它

mBinding.setVariable(BR.action,new View.OnClickListener(){
@Override
void onClick(View v){
//TODO
}
})

2.3.6 传递复杂对象

在之前给xml中的变量中赋值时,我们用的都是一些类似String的简单对象,其实我们也可以定义一些复杂的对象,一次性传递到xml布局中

//java
public class File
{
public File(String name,
String size,
String path)
{
this.name = name;
this.size = size;
this.path = path;
}
public final String name;
public final String size;
public final String path;
}
//xml







个人认为绑定到xml中的数据最好是不可变的,所以上面的字段中我使用了final,但这不是必须的,根据你自己的需求来进行定制

2.3.7 绑定并非立即发生

这里有一点值得注意的是,你给ViewDataBinding的赋值并不是马上生效的,而是在当前方法执行完毕回到事件循环后,并保证在下一帧渲染之前得到执行,如果需要立即执行,请调用ViewDataBinding#executePendingBindings

2.3.8 使用android:id

如果你使用了android:id,那么这个View就也可以当成一个变量在下文的DataBinding表达式中使用,就像写Java.它还会帮你View绑定到ViewDataBinding中,你可以这么使用它们

//xml


//在java中my_text被去掉下划线,更符合java的命名习惯
mBinding.myText.setText(“This is a new text”);

用过ButterKnife的同学可能都知道,ButterKnife出过一次与gradle版本不兼容的事故,但是DataBinding是与gradle打包在一起发布的,一般不会出现这种问题,如果你不想用ButterKnife但有不想让DataBinding的风格的写法入侵你的xml太狠的话,只使用android:id将会是一个不错的选择.

2.4 正向绑定

某些第三方View是肯定没有适配DataBinding的,业界虽然一直说MVVM好,但现在MVP的开发方式毕竟还是主流,虽然这种情况我们可以用android:id,然后在Activity/Fragment中解决,但有时候我们想直接在xml中配置,以消除一些样板代码,这时候就需要自定义正向绑定.

2.4.1 自定义正向绑定适配器

我们可以使用@BindingAdapter自定义在xml中可使用的View属性,名字空间是不需要的,加了反而还会给你警告.

@Target(ElementType.METHOD)
public @interface BindingAdapter {

/**

  • 与此绑定适配器关联的属性。
    */
    String[] value();

/**

  • 是否必须为每个属性分配绑定表达式,或者是否可以不分配某些属性。
  • 如果为false,则当至少一个关联属性具有绑定表达式时,将调用BindingaAapter。
    */
    boolean requireAll() default true;
    }

//@BindingAdapter需要一个静态方法,该方法的第一个参数是与该适配器兼容的View类型
//从第二个参数开始,依次是你自定义的属性传进来的值.
//使用requireAll来指定这些属性是全部需要,还是只要一个就可以
//如果requireAll = false,触发适配器绑定时,没有被设置的属性将获得该类型的默认值
//框架优先使用自定义的适配器处理绑定

@BindingAdapter(value = {“load_async”, “error_handler”},requireAll = true)
public static void loadImage(ImageView view, String url, String error) {
Glide.with(view)
.load(url)
.error(Glide.with(view).load(error))
.into(view);
}
//在xml中使用它(下面那两个网址都不是实际存在的)

2.4.2 第三方View适配

DataBinding风格的xml还能在一定程度上适配第三方View

//如果你的自定义View中有这么一个Setter↓
public class RoundCornerImageView extends AppCompatImageView{
//…
public void setRadiusDp(float dp){
//TODO
}
}
//那么你可以在xml中使用radiusDp来使用它
<org.kexie.android.ftper.widget.RoundCornerImageView
radiusDp=“@{100}”
android:id=“@+id/progress”
android:layout_width=“match_parent”
android:layout_height=“match_parent”
android:layout_gravity=“center”
android:scaleType=“centerCrop”
android:src=“@drawable/progress”/>
//它会自己为你去找名称为setRadiusDp并且能接受100为参数的方法.

2.4.3 xml中的属性重定向

使用@BindingMethod来将xml属性重定向:

@Target(ElementType.ANNOTATION_TYPE)
public @interface BindingMethod {
//需要重定向的View类型
Class type();
//需要重定向的属性名
String attribute();
//需要重定向到的方法名
String method();
}
//这是DataBinding源码中,DataBinding对于系统自带的TextView编写的适配器
//这是androidx.databinding.adapters.TextViewBindingAdapter的源码
@BindingMethods({
@BindingMethod(type = TextView.class, attribute = “android:autoLink”, method = “setAutoLinkMask”),
@BindingMethod(type = TextView.class, attribute = “android:drawablePadding”, method = “setCompoundDrawablePadding”),
@BindingMethod(type = TextView.class, attribute = “android:editorExtras”, method = “setInputExtras”),
//…
})
public class TextViewBindingAdapter {
//…
}
//这样就可以建立起xml中属性与View中Setter的联系

2.4.4 添加转换层

使用@BindingConversion为添加转换层

@BindingConversion
public static ColorDrawable toDrawable(int color) {
return new ColorDrawable(color);
}
//可以把color整形转换为android:src可接受的ColorDrawable类型
//但是转换只适用于直接的赋值
//如果你写了复杂的表达式,比如使用了?:这种三元运算符
//那就照顾不到你了

2.5 反向绑定

有正向绑定就一定有反向绑定,正向绑定和反向绑定一起构成了双向绑定.

在我们之前编写的DataBinding表达式中,比如TextViewandroid:text之类的属性我们都是直接赋值一个String过去的,这就是正向绑定,我们给View的值能够直接反应到View上,而反向绑定就是View值的变化和也能反应给我们.

2.5.1 使用双向绑定

所有使用之前所有使用@{}包裹的都是正向绑定,而双向绑定是@={},并且只支持变量,字段,Setter(比如User#setName,就写@={user.name})的直接编写并且不支持复杂表达式

2.5.2 兼容LiveData与ObservableField

实际上,android:text不只能接受String,当使用双向绑定时,它也能接受MutableLiveData<String>ObservableField<String>作为赋值对象,这种赋值会将TextViewandroid:text的变化绑定到LiveData(实际上是MutableLiveData)或者是ObservableField上,以便我们在View的控制层(Activity/Fragment)更好地观察他们的变化.

当然除了ObservableFieldandroidx.databinding包下还有不装箱的ObservableInt,ObservableFloat等等.

但是为了支持LiveData我们必须开启第二版的DataBinding APT.

在你的gradle.properties添加

android.databinding.enableV2=true

现在我们可以通过LiveData(实际上是MutableLiveData)android:text的变化绑定到Activity/Fragment

//xml




//然后在Activity/Fragment中
MutableLiveData liveText = new MutableLiveData();
mBinding.setLiveText(liveText);
liveText.observe(this,text->{
//TODO 观察View层变化
});

2.5.3 自定义反向绑定适配器

下面我们回到androidx.databinding.adapters.TextViewBindingAdapter的源码,继续对自定义反向绑定适配器进行分析.

//我们可以看到源码中使用了@InverseBindingAdapter自定义了一个反向绑定器
//指定了其属性以及相关联的事件
@InverseBindingAdapter(attribute = “android:text”, event = “android:textAttrChanged”)
public static String getTextString(TextView view) {
return view.getText().toString();
}
//并为这个事件添加了一个可接受InverseBindingListener的属性
//为了说明方便,下面的代码已简化,源码并非如此,但主要逻辑相同
@BindingAdapter(value = {“android:textAttrChanged”})
public static void setTextWatcher(TextView view , InverseBindingListener textAttrChanged){
view.addTextChangedListener(new TextWatcher(){
//…
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
textAttrChanged.onChange();
}
});
}
//至此android:text的反向绑定完成
//当你使用@={}时实际上是用android:textAttrChanged属性向TextView设置了TextWatcher
//传入的InverseBindingListener是反向绑定监听器
//当调用InverseBindingListener的onChange时
//会调用@BindingAdapter所注解的方法将获得数据并写回到变量中.

2.6 配合DataBinding打造通用RecyclerView.Adapter

下面进行一个小小的实战吧,我们可以站在巨人的肩膀上造轮子.

//导入万能适配器作为基类,可以大大丰富我们通用适配器的功能
implementation ‘com.github.CymChad:BaseRecyclerViewAdapterHelper:2.9.46’

由于基类很强大所以代码不多:

//X是泛型,可以是你在item中所使用的java bean
public class GenericQuickAdapter
extends BaseQuickAdapter<X, GenericQuickAdapter.GenericViewHolder> {
//BR中的变量名
protected final int mName;
//layoutResId是DataBinding风格的xml
public GenericQuickAdapter(int layoutResId, int name) {
super(layoutResId);
mName = name;
openLoadAnimation();
}

@Override
protected void convert(GenericViewHolder helper, X item) {
//触发DataBinding
helper.getBinding().setVariable(mName, item);
}

public static class GenericViewHolder extends BaseViewHolder {
private ViewDataBinding mBinding;
public GenericViewHolder(View view) {
super(view);
//绑定View获得ViewDataBinding
mBinding = DataBindingUtil.bind(view);
}

@SuppressWarnings(“unchecked”)
public T getBinding() {
return (T) mBinding;
}
}
}
//实例化
GenericQuickAdapter adapter = new GenericQuickAdapter<>(R.layout.item_file,BR.file);
//在xml中使用起来就像这样









3 Lifecycle

Android中,组件的管理组件的生命周期一直是一个比较麻烦的东西,而自Google推出Android Jetpack组件包以来,这个问题得到的比较妥善的解决,Lifecycle组件后来也成为Android Jetpack的核心。

3.1 导入

AndroidX为例,要使用Lifecycle组件,先在模块的build.gradle文件中添加依赖:

api ‘androidx.lifecycle:lifecycle-extensions:2.1.0-alpha02’

由于Lifecycle组件由多个包构成,使用api导入时即可将其依赖的包全部导入该模块,包括commonlivedataprocessruntimeviewmodelservice等。

如果要使用Lifecycle中的注解,你还需要添加如下注解处理器,以便在编译时,完成对相应注解的处理。

annotationProcessor ‘androidx.lifecycle:lifecycle-compiler:2.0.0’

对于一个App来说,使用Lifecycle组件是没有任何侵入性的,因为他已经天然的融合到Googleappcompat库中了,而如今无论是什么应用程序都几乎离不开appcompat,可以说集成Lifecycle只是启用了之前没用过的功能罢了。

3.2 LifecycleOwner

LifecycleOwnerLifecycle组件包中的一个接口,所有需要管理生命周期的类型都必须实现这个接口。

public interface LifecycleOwner
{
/**

  • Returns the Lifecycle of the provider.
  • @return The lifecycle of the provider.
    */
    @NonNull
    Lifecycle getLifecycle();
    }

但其实很多时候我们根本无需关心LifecycleOwner的存在。在Android中, FragmentActivityService都是具有生命周期的组件,但是Google已经让他们都实现了LifecycleOwner这个接口,分别是androdx.fragment.app.FragmentAppCompatActivityandroidx.lifecycle.LifecycleService.

在项目中,只要继承这些类型,可以轻松的通过LifecycleOwner#getLifecycle()获取到Lifecycle实例.这是一种解耦实现,LifecycleOwner不包含任何有关生命周期管理的逻辑,实际的逻辑都在Lifecycle实例中,我们可以通过传递Lifecycle实例而非LifecycleOwner来防止内存泄漏.

Lifecycle这个类的只有这三个方法:

@MainThread
public abstract void removeObserver(@NonNull LifecycleObserver observer);
@MainThread
@NonNull
public abstract State getCurrentState();
@MainThread
public abstract void addObserver(@NonNull LifecycleObserver observer);

getCurrentState()可以返回当前该LifecycleOwner的生命周期状态,该状态与LifecycleOwner上的某些回调事件相关,只会出现以下几种状态,在Java中以一个枚举类抽象出来定义在Lifecycle类中。

public enum State
{
DESTROYED,
INITIALIZED,
CREATED,
STARTED,
RESUMED;
}

  • DESTROYED,在组件的onDestroy调用前,会变成该状态,变成此状态后将不会再出现任何状态改变,也不会发送任何生命周期事件

  • INITIALIZED,构造函数执行完成后但onCreate未执行时为此状态,是最开始时的状态

  • CREATED,在onCreate调用之后,以及onStop调用前会变成此状态

  • STARTED,在onStart调用之后,以及onPause调用前会变成此状态

  • RESUMED,再onResume调用之后会变成此状态

addObserver,此方法可以给LifecycleOwner添加一个观察者,来接收LifecycleOwner上的回调事件。回调事件也是一个枚举,定义在Lifecycle类中:

public enum Event
{
/**

  • Constant for onCreate event of the {@link LifecycleOwner}.
    /
    ON_CREATE,
    /
    *
  • Constant for onStart event of the {@link LifecycleOwner}.
    /
    ON_START,
    /
    *
  • Constant for onResume event of the {@link LifecycleOwner}.
    /
    ON_RESUME,
    /
    *
  • Constant for onPause event of the {@link LifecycleOwner}.
    /
    ON_PAUSE,
    /
    *
  • Constant for onStop event of the {@link LifecycleOwner}.
    /
    ON_STOP,
    /
    *
  • Constant for onDestroy event of the {@link LifecycleOwner}.
    /
    ON_DESTROY,
    /
    *
  • An {@link Event Event} constant that can be used to match all events.
    */
    ON_ANY
    }

每种事件都对应着Fragment/Activity中的事件。

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
img

尾声

开发是需要一定的基础的,我是08年开始进入Android这行的,在这期间经历了Android的鼎盛时期,和所谓的Android”凉了“。中间当然也有着,不可说的心酸,看着身边朋友,同事一个个转前端,换行业,其实当时我的心也有过犹豫,但是我还是坚持下来了,这次的疫情就是一个好的机会,大浪淘沙,优胜劣汰。再等等,说不定下一个黄金浪潮就被你等到了。

  • 330页 PDF Android核心笔记

  • 几十套阿里 、字节跳动、腾讯、华为、美团等公司2020年的面试题

  • PDF和思维脑图,包含知识脉络 + 诸多细节

  • Android进阶系统学习视频

知识点,真正体系化!**

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
[外链图片转存中…(img-xM871q04-1711863313865)]

尾声

开发是需要一定的基础的,我是08年开始进入Android这行的,在这期间经历了Android的鼎盛时期,和所谓的Android”凉了“。中间当然也有着,不可说的心酸,看着身边朋友,同事一个个转前端,换行业,其实当时我的心也有过犹豫,但是我还是坚持下来了,这次的疫情就是一个好的机会,大浪淘沙,优胜劣汰。再等等,说不定下一个黄金浪潮就被你等到了。

  • 330页 PDF Android核心笔记

[外链图片转存中…(img-vAqalLsJ-1711863313866)]

  • 几十套阿里 、字节跳动、腾讯、华为、美团等公司2020年的面试题

[外链图片转存中…(img-GlvVzaCw-1711863313866)]

[外链图片转存中…(img-Ep7wm7Eg-1711863313867)]

  • PDF和思维脑图,包含知识脉络 + 诸多细节

[外链图片转存中…(img-yxMAP9ql-1711863313867)]

  • Android进阶系统学习视频

[外链图片转存中…(img-2JRmxgIv-1711863313867)]

本文已被CODING开源项目:《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》收录

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值