Android Jetpack—LiveData

news2025/1/13 7:55:29

1.LiveData

LiveData是Android Jetpack包提供的一种可观察的数据存储器类,它可以通过添加观察者被其他组件观察其变更。不同于普通的观察者,LiveData最重要的特征是它具有生命周期感知能力,它遵循其他应用组件(如 Activity、Fragment 或 Service)的生命周期。这种感知能力可确保LiveData仅更新处于活跃生命周期状态的应用组件观察者。如在Activity中如果数据更新了但Activity已经处于destroy状态,则LiveData就不会通知Activity(observer)了。此外,LiveData还有许多优点,比如不会完成内存泄露等。

LiveData有两个子类:MutableLiveData和MediatorLiveData。MutableLiveData针对单个需要观察的数据进行了封装,而MediatorLiveData则可以观察其它的LiveData。开发过程中通常使用LiveData的子类,而不是去继承LiveData。

LiveData通常会配合ViewModel一起使用,ViewModel负责触发数据的更新,更新会通知单LiveData,然后LiveData再通知活跃状态的观察者。当然,LiveData也可以单独使用。

比如MutableLiveData的使用方法:

private void liveDataTest(){

    MutableLiveData<String> mutableLiveData = new MutableLiveData<>();

    mutableLiveData.observe(this, new Observer<Integer>() {

        @Override

        public void onChanged(Integer integer) {

           //如果数据有变化的话,这里会接收到通知,可以在这里可以做一些反应,比如更新界面

        }

     });

    mutableLiveData.setValue("我的值变化了");

}

分为三步:

①创建对应数据类型的LiveData

②添加观察者与Lifecycle建立联系

③发送对应的数据进行更新

 

2.LiveData使用

①首先引入依赖

implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'

②布局文件activity_live_data_test.xml

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

    android:layout_width="match_parent"

    android:layout_height="match_parent"

    android:orientation="vertical"

    tools:context=".LiveDataTestActivity">

    <TextView

        android:id="@+id/tv_text"

        android:layout_width="match_parent"

        android:layout_height="50dp"

        android:layout_marginTop="100dp"

        android:gravity="center"

        android:textSize="30sp" />

    <LinearLayout

        android:layout_width="match_parent"

        android:layout_height="wrap_content"

        android:layout_marginTop="200dp"

        android:gravity="center"

        android:orientation="horizontal">

        <ImageButton

            android:id="@+id/img_button_add"

            android:layout_width="70dp"

            android:layout_height="70dp"

            android:layout_marginRight="20dp"

            app:srcCompat="@drawable/up_24"

        />

        <ImageButton

           android:id="@+id/img_button_subtract"

            android:layout_width="70dp"

            android:layout_height="70dp"

            android:layout_marginLeft="20dp"

            app:srcCompat="@drawable/down_24"

        />

    </LinearLayout>

</LinearLayout>

③创建ViewModelWithLiveData类继承于ViewModel,在这个类里面声明变量

public class ViewModelWithLiveData extends ViewModel {

    private MutableLiveData<Integer> LikedNumber;  

    public MutableLiveData<Integer> getLikedNumber() {

        if (LikedNumber == null) {

            //LikedNumber是对象类型,不是基本数据类型,所以要保证变量不是空的

            LikedNumber = new MutableLiveData<>();       

            LikedNumber.setValue(0);  //初始化为0

        }

        return LikedNumber;

    }

    public void addLikedNumber(int n) {

        LikedNumber.setValue( LikedNumber.getValue() + n);

    }

}

④给viewModelWithLiveData里面的变量添加一个观察,点击按钮实现+1、-1操作

public class LiveDataTestActivity extends AppCompatActivity {

    private ViewModelWithLiveData viewModelWithLiveData;

    private TextView tv_text;

    private ImageButton img_button_add;

    private ImageButton img_button_subtract;

    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView( R.layout.activity_live);

        tv_text = findViewById(R.id.tv_text);

        img_button_add = findViewById(R.id.img_button_add);

        img_button_subtract = findViewById(R.id.img_button_subtract);

        viewModelWithLiveData = new ViewModelProvider(this).get(ViewModelWithLiveData.class);

        //给viewModelWithLiveData里面的变量添加一个观察,观察它自己,如果数据发生变化则呼叫下面的函数

        //observe()的第一个参数:需要具有LifeCycle管理功能的一些对象。Activity就是具有管理LifeCycle功能的对象,系统已经帮我们做好了,不用在其他地方取消观察,只需要添加观察就可以了       

        viewModelWithLiveData.getLikedNumb er().observe(this, new Observer<Integer>() {

          @Override

          public void onChanged(Integer integer) { //当数据发生改变的时候 呼叫这个函数

              tv_text.setText( String.valueOf(integer));

            }

        });

        img_button_add.setOnClickListener(new View.OnClickListener() {

            @Override

            public void onClick(View view) {

                viewModelWithLiveData.addLikedN umber(1);

            }

        });

        img_button_subtract.setOnClickListener( new View.OnClickListener() {

            @Override

            public void onClick(View view) {

                viewModelWithLiveData.addLikedN umber(-1);

            }

        });

    }

}

ac6e22946cbb422a94eec0ed51489137.gif

 并且,旋转屏幕,切换系统语言等配置时,数据不会丢失。

 

LiveData的好处:

①监听与其绑定的界面的生命周期。因此,使用LiveData就不需要手动管理它的生命周期了。

②组件能及时响应LiveData的数据变化,组件总能拿到LiveData的最新数据。当然,被绑定的组件响应LiveData是有一定的前提的,那就是LiveData数据发生变化,且组件处于活跃状态。也就是说,LiveData数据即使发生了变化,也不一定会响应onChanged函数,因为它必须要求LiveData数据所在的界面处于活跃状态,才会响应onChanged函数。

③生命周期自动解绑,它能够在组件销毁的时候自动解绑,大大降低了应用产生内存泄露的概率。

LiveData确实能解决内存泄漏问题,但是如果使用不当,其实还是会出现内存泄漏的。例如,有一个Activity,Activity包含了一个Fragment,Fragment中有一个LiveData,因此Fragment引用了LiveData。然后LiveData通过observe方法把Activity当作owner进行了绑定,那么这时候,LiveData的生命周期将和Activity一样。如果这时候因为某种原因,Fragment被销毁了,那么LiveData将不会被销毁,因为它被Activity引用着。LiveData本该回收却无法被回收,那么LiveData就发生内存泄漏了。

 

3.LiveData源码

①observe方法

LiveData使用observe()方法监听与其绑定的界面的生命周期。

LiveData.java:

@MainThread

public void observe(LifecycleOwner owner, Observer<? super T> observer) {

    assertMainThread("observe"); //只能在主线程调用这个方法

    if (owner.getLifecycle().getCurrentState() == DESTROYED) {

        return; //如果owner的生命周期已经是DESTROYED状态,则不再往下执行

    }

    //将外部传进的observer对象封装成一个LifecycleBoundObserver对象

    LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);

    //将observer和wrapper存放到map中

    ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);

    if (existing != null && !existing.isAttachedTo(owner)) {

        throw new IllegalArgumentException( "Cannot add the same observer with different lifecycles"); //同一个观察者不能观察不同的LifecycleOwner

    }

    if (existing != null) {

        return;

    }

    owner.getLifecycle().addObserver(wrapper);

}

首先,将外部传进的observer对象封装成LifecycleBoundObserver对象,对象名称为wrapper,其实LifecycleBoundObserver就是一个可以监听生命周期的类,可以看看LifecycleBoundObserver类的构造方法:

LiveData.LifecycleBoundObserver.java:

class LifecycleBoundObserver extends ObserverWrapper implements GenericLifecycleObserver {

    final LifecycleOwner mOwner;

    LifecycleBoundObserver(LifecycleOwner owner, Observer<? super T> observer) {

        super(observer);

        mOwner = owner;

    }

    // …………………………

}

public interface GenericLifecycleObserver extends LifecycleEventObserver { }

public interface LifecycleEventObserver extends LifecycleObserver {

    void onStateChanged(LifecycleOwner source, Lifecycle.Event event);

}

可以看到,LifecycleBoundObserver实现了GenericLifecycleObserver这个接口,而实际上这个接口最终继承自LifecycleObserver接口,LifecycleObserver就是使用lifecycle的时候调用addObserver时需要传递的参数。

所以,通过new LifecycleBoundObserver( owner, observer)方法得到的wrapper实际上就是一个LifecycleObserver。

在LifecycleBoundObserver类构造方法中,将observer传给其父类使用,而其父类是ObserverWrapper,看看ObserverWrapper的构造方法:

LiveData.ObserverWrapper.java:

private abstract class ObserverWrapper {

    final Observer<? super T> mObserver;

    boolean mActive;

    int mLastVersion = START_VERSION;

    ObserverWrapper(Observer<? super T> observer) {

        mObserver = observer;

    }

    // ………………………………

}

可以看到,实际上只是把observer赋值给ObserverWrapper类的mObserver成员变量罢了。

继续回到LiveData的observe方法:

LiveData.java:

private SafeIterableMap<Observer<? super T>, ObserverWrapper> mObservers = new SafeIterableMap<>();

@MainThread

public void observe(LifecycleOwner owner, Observer<? super T> observer) {

    assertMainThread("observe");

    //如果owner的生命周期为DESTROYED,直接return

    if (owner.getLifecycle().getCurrentState() == DESTROYED) {

        return;

    }

    //将外部传进的observer对象封装成一个LifecycleBoundObserver对象

    LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);

    //将observer和wrapper存放到mObservers中

    ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);

    if (existing != null && !existing.isAttachedTo(owner)) {

        throw new IllegalArgumentException( "Cannot add the same observer with different lifecycles");

    }

    if (existing != null) {

        return;

    }

    owner.getLifecycle().addObserver(wrapper);

}

mObservers.putIfAbsent(observer, wrapper)这行代码,将observer作为键,wrapper作为值存放到mObservers中,并且会返回一个ObserverWrapper对象。

mObservers是一个由链表实现的支持键值对存储的数据结构,同时,它支持在遍历的过程中删除任意元素,我们可以暂且把它理解为保存observer对象的集合。

如果ObserverWrapper不为null,则observe这个方法会直接return。ObserverWrapper在什么情况不为null呢?

如果观察者observer已经在mObservers这个列表中,并且observer已经有另一个所有者owner,就会不为null。

这是为了防止同一个observer被多个LifecycleOwner绑定,即一个LiveData中的同一个observer只能跟一个LifecycleOwner绑定。否则会抛出“Cannot add the same observer with different lifecycles”的异常。

在observe方法的最后,调用了owner.getLifecycle().addObserver(wrapper)方法,使得wrapper和owner的生命周期进行了绑定。也就是说,observer此时就可以监听到owner对应的界面的生命周期的变化了。

②setValue方法

setValue方法是用来更新LiveData数据的。

LiveData.java:

@MainThread

protected void setValue(T value) {

    assertMainThread("setValue"); //setValue方法要求在主线程调用,在其他线程使用postValue

    mVersion++; 辅助做数据是否有更新的判断

    mData = value;

    dispatchingValue(null);

}

首先,setValue方法有个@MainThread注解,说明这个方法只能在主线程中使用,然后在setValue中,将value赋值给mData变量,同时mVersion变量加1,mVersion就是数据的版本,它是用来标记数据是否变化的。

方法最后调用了dispatchingValue(null)方法实现数据分发,进入该方法:

LiveData.java:

void dispatchingValue(ObserverWrapper initiator) {

    //如果当前正在做数据分发,则不做重复处理

    if (mDispatchingValue) {

        mDispatchInvalidated = true;

        return;

    }

    mDispatchingValue = true; //标记事件分发状态为true

    do {

        mDispatchInvalidated = false;

        if (initiator != null) {

            //observe方法被调用时,从这里触发分发

            considerNotify(initiator);

            initiator = null;

        } else {

            //setValue触发遍历观察该LiveData的所有观察者,分发setValue事件

            for (Iterator<Map.Entry<Observer<? super T>, ObserverWrapper>> iterator = mObservers.iteratorWithAdditions(); iterator.hasNext(); ) {

                considerNotify( iterator.next().getValue());

                if (mDispatchInvalidated) {

                    break;

                }

            }

        }

    } while (mDispatchInvalidated);

    mDispatchingValue = false;

}

可以看到,dispatchingValue方法中有个while循环,在while循环里面,由于initiator为null,所有走else分支的代码,这里又有个for循环,这个for循环是用来遍历mObservers中的observer对象的,然后通过considerNotify方法将遍历到的observer对象进行处理。

看看considerNotify方法,该方法针对每个观察者做处理,判断是否需要分发和完成分发事件:

LiveData.java:

private void considerNotify(ObserverWrapper observer) {

    //如果观察者不活跃就放弃分发,观察者活跃的逻辑是处于STARTED和RESUMED之间

    if (!observer.mActive) {

        return;

    }

    if (!observer.shouldBeActive()) {

        observer.activeStateChanged(false);

        return;

    }

    //如果该观察者已经接受过数据了,也不再进行分发

    if (observer.mLastVersion >= mVersion) {

        return;

    }

    observer.mLastVersion = mVersion; //对观察者进行分发版本记录

    observer.mObserver.onChanged((T) mData); //调用观察者的onChanged事件完成分发

}

首先做了一个判断,外部传入的observer对象是否处于活跃状态,不处于活跃状态则直接return这个方法。从这里就可看出,即使数据更新了,但如果界面不处于活跃状态,也不会对数据更新做响应。

然后是mVersion这个变量以及mLastVersion变量,判断observer.mLastVersion变量如果小于mVersion,会把mVersion的值赋值给observer.mLastVersion变量,这种情况就说明数据是新的数据,就可以执行observer的onChanged方法把mData回调出去了。

③postValue方法

LiveData.java:

protected void postValue(T value) {

    boolean postTask;

    synchronized (mDataLock) {

        postTask = mPendingData == NOT_SET;

        mPendingData = value;

    }

    if (!postTask) {

        return;

    }

    ArchTaskExecutor.getInstance().postToMai nThread(mPostValueRunnable);

}

postValue方法中,外部传进来的value会赋值给mPendingData,方法的最后会调用postToMainThread方法,postToMainThread,看这个方法的命名应该也能看出来一些东西,就是将这个runnable发布到主线程处理,进入postToMainThread详细看看:

ArchTaskExecutor.java:

@Override

public void postToMainThread(Runnable runnable) {

    mDelegate.postToMainThread(runnable);

}

它通过调用mDelegate的postToMainThread方法,mDelegate实际上就是DefaultTaskExecutor这个类,那么再进入这个类的postToMainThread看看:

DefaultTaskExecutor.java:

@Override

public void postToMainThread(Runnable runnable) {

    if (mMainHandler == null) {

        synchronized (mLock) {

            if (mMainHandler == null) {

                mMainHandler = new Handler(Looper.getMainLooper());

            }

        }

    }

    mMainHandler.post(runnable);

}

在这个方法中,先判断mMainHandler对象是否为null,如果为null,就创建一个,注意,它是通过Looper.getMainLooper()参数来创建的,说明这个这个mMainHandler就是主线程的handler对象。

最后调用mMainHandler.post(runnable)方法,将runnable对象交给主线程的handler来处理。

那么runnable执行了什么东西呢?

回到postValue方法,发现runnable就是mPostValueRunnable,看mPostValueRunnable是怎么定义的:

LiveData.java:

private final Runnable mPostValueRunnable = new Runnable() {

    @Override

    public void run() {

        Object newValue;

        synchronized (mDataLock) {

            newValue = mPendingData;

            mPendingData = NOT_SET;

        }

        setValue((T) newValue);

    }

};

看到它的run方法,先定义了个newValue变量,然后加锁对newValue赋值为mPendingData变量,mPendingData是在postValue方法内第三行赋值的,前面已经看到过了,然后将mPendingData置为NOT_SET。最后调用setValue方法。

所以,postValue方法实际上就是将线程切到主线程去处理setValue方法。

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/468276.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

软件测试—进阶篇

软件测试—进阶篇 &#x1f50e;根据测试对象划分界面测试可靠性测试容错性测试文档测试兼容性测试易用性测试安装卸载测试安全性测试性能测试内存泄漏测试 &#x1f50e;根据是否查看代码划分黑盒测试白盒测试灰盒测试 &#x1f50e;根据开发阶段划分单元测试集成测试系统测试…

mulesoft MCIA 破釜沉舟备考 2023.04.27.25 (易错题)

@[TOC](mulesoft MCIA 破釜沉舟备考 2023.04.27.25 (易错题)) 1. According to MuleSoft, which deployment characteristic applies to a microservices application architecture? A. Services exist as independent deployment artifacts and can be scaled independently…

ABeam Insight | 智能制造系列(6):虚拟/增强现实(VR/AR)×智能制造

虚拟现实&#xff08;VR&#xff09;和增强现实&#xff08;AR&#xff09;的概念早在20世纪60年代就被提出&#xff0c;但由于当时的技术水平无法满足相关应用的需求&#xff0c;这些概念并没有引起广泛关注。直到近年来随着计算机技术的飞速发展&#xff0c;虚拟现实和增强现…

python+nodejs+php+springboot+vue高校教室自习室预约管理系统

建立的自习室预约管理系统用户使用浏览器就可以对其进行访问&#xff0c;管理员在操作上面能够方便管理&#xff0c;因此用户和管理员能够方便对这个系统进行操作。论文全面介绍系统数据库&#xff0c;功能设计和业务流程设计。数据库能够存储自习室预约管理系统需要的数据。 …

Leanback(1)-播放控制栏下添加新的行

我们要在播放控制栏下面加入下面一行。 这个就是标准的row。 leanback的原理 Android Leanback结构源码简析 - 简书 我们知道Row用来提供数据&#xff0c;row可以通过一个ObjectAdapter来管理和提供数据 我们知道presenter是一个负责将数据绑定到视图上的对象&#xff0c;它可以…

基于STM32的智能语音垃圾桶设计

一. 系统设计及框图&#xff1a; 本设计整体功能如下&#xff1a; 1. 超声波感应到有人靠近时语音提示“垃圾放置请分类”。 2. 检测垃圾筒时是否满&#xff0c;当满时语音提示“垃圾桶已满”。 3. 光传感器检测&#xff0c;指示灯指示。 4. 语音识别不同的垃圾类型。 二.…

前端程序员的职业发展规划与路线——ChatGPT的回答

文章目录 一、前端程序员的职业规划是&#xff1f;回答1&#xff1a; 作为一个前端开发程序员&#xff0c;您的职业发展路线可能如下&#xff1a;回答2&#xff1a;作为前端开发程序员&#xff0c;您的职业发展路线可能如下&#xff1a;回答3&#xff1a; 你的职业发展路线可能…

ASEMI代理ADI亚德诺ADM3051CRZ-REEL7车规级芯片

编辑-Z ADM3051CRZ-REEL7芯片参数&#xff1a; 型号&#xff1a;ADM3051CRZ-REEL7 显性状态&#xff1a;78 mA 隐性状态&#xff1a;10 mA 待命状态&#xff1a;275μA CANH输出电压&#xff1a;4.5V CANL输出电压&#xff1a;2V 差动输出电压&#xff1a;3V 输入电压…

【AI生产力工具】Upscale.media:用AI技术提升照片质量,让你的作品更出色

文章目录 简介一、Upscale.media是什么&#xff1f;二、如何使用Upscale.media&#xff1f;三、总结 简介 在如今的数字时代&#xff0c;图片已经成为我们日常生活中不可或缺的一部分&#xff0c;从社交媒体到电子商务网站&#xff0c;从广告宣传到个人生活&#xff0c;都需要…

璀璨盛启·焕美升级 上颜集团杭州医学旗舰中心盛大启幕

2023年4月26日&#xff0c;「璀璨盛启焕美升级」上颜杭州医学旗舰中心启幕盛典在杭州滨江钱龙大厦耀新启幕。上颜用审美、匠心构建城市的活力与色彩&#xff0c;致力于为客户带来全新的美丽方式和一流的品质服务。 &#xff08;上台剪彩嘉宾从左至右依次为&#xff09;上颜集团…

CSS3小可爱亲吻表白特效,给你的五一假期增添点小乐趣

马上五一假期了&#xff0c;小伙伴们是不是都准备出去旅游呢&#xff0c;或者回老家陪陪父母。今天我用CSS3制作一个小可爱亲吻表白的特效&#xff0c;来给你即将到来的五一假期增添点小小的乐趣。 目录 实现思路 左边小可爱的实现 右边小可爱的实现 左右摇摆动效的实现 右…

LeetCode43. 字符串相乘(Java解法)

0 题目描述 给定两个以字符串形式表示的非负整数 num1 和 num2&#xff0c;返回 num1 和 num2 的乘积&#xff0c;它们的乘积也表示为字符串形式。 注意&#xff1a;不能使用任何内置的 BigInteger 库或直接将输入转换为整数。 示例 1: 输入: num1 “2”, num2 “3” 输出…

【三十天精通 Vue 3】 专栏内容介绍

在这个专栏中&#xff0c;我们将带你深入了解 Vue 3 的各个方面。首先&#xff0c;我们将带你了解 Vue 3 的新特性和改进&#xff0c;包括 Composition API、Provide/Use Case、Vuex 3 等。然后&#xff0c;我们将详细介绍 Vue 3 的组件化开发、路由、状态管理等方面的内容。 …

LineSegmentIntersector::Intersections中ratio含义及LineSegmentIntersector相交点说明

osg用osgUtil库中的LineSegmentIntersector、IntersectionVisitor类来求线段和三维模型的交点 如下代码&#xff1a; #include <QtCore/QCoreApplication> #include <osgViewer/Viewer> #include <osgViewer/ViewerEventHandlers> #include <osgViewer…

Duboo介绍与入门

文章目录 1、Dubbo的前世今生2、Dubbo的快速入门2.1、Dubbo的基本架构2.2、Nacos2.3、管理后台2.4、入门案例2.4.1、服务提供者搭建环境代码实现配置文件 2.4.2、服务消费者搭建环境代码实现配置文件 最后说一句 1、Dubbo的前世今生 2011年10月27日&#xff0c;阿里巴巴开源了…

Vue项目解决跨域问题

跨域&#xff08;Cross-Origin&#xff09;是指在浏览器中运行的 JavaScript 代码试图访问不同源的资源时所遇到的一类限制问题。不同源是指协议&#xff08;http、https&#xff09;、域名&#xff08;example.com、google.com&#xff09;、端口号&#xff08;80、8080&#…

双时间维度,你的时间管理大师丨三叠云

双时间维度 路径 表单 >> 表单设计 功能简介 「甘特图视图」新增 双时间维度设置 的配置项。 为了解决日常任务管理中对于“计划时间”维度的时间设置问题&#xff0c;【时间设置】项增加了【双时间维度设置】的配置项&#xff0c;用户开启后可以设置 计划开始时间 …

Matlab 牛顿迭代法(1)牛顿法

一、牛顿迭代公式 1、定义 2、原理推导 泰勒公式&#xff1a; 常用的8个泰勒公式&#xff1a; 推导&#xff1a; 将f(x)f(x)在Xk 处的泰勒公式展开&#xff1a; f(x)f(Xk)f(Xk)(X-Xk) f(Xk)/2 *(x-Xk)^2.......... 我们吧线性的一部分先拿出来&#xff1a;f(x)f(Xk)f(X…

【python知识】容器对象的推导式

一、说明 Python 推导式&#xff0c;是针对容器对象&#xff08;列表,字典&#xff0c;集合&#xff0c;元组&#xff09;的产生方式的语句。它可以从一个数据序列构建另一个新的数据序列的结构体。 Python 支持各种数据结构的推导式&#xff1a; 列表(list)推导式字典(dict)推…

【Java|golang】1048. 最长字符串链

给出一个单词数组 words &#xff0c;其中每个单词都由小写英文字母组成。 如果我们可以 不改变其他字符的顺序 &#xff0c;在 wordA 的任何地方添加 恰好一个 字母使其变成 wordB &#xff0c;那么我们认为 wordA 是 wordB 的 前身 。 例如&#xff0c;“abc” 是 “abac”…