Fragment全文详解(由浅入深_源码分析)

news2024/11/15 21:56:17

相信android开发者们一定或多或少的用过Fragment,但是对于其更深层次的原理我猜可能大部分应该都没有了解过,今天这里就由浅入深,整体对Fragment做一个全面解析。

基础介绍

Fragment是什么以及为什么要有Fragment呢?

Fragment直译为碎片,是Android 3.0引入的组件,Fragment不能单独使用,必须要依赖于Activity。 Fragment引入之前几乎所有的逻辑都被放置在Activity中,使得Activity臃肿而混乱,Fragment作为一个微小的Activity诞生。

另外Fragment还有以下几个特点

  • 模块化:我们可以按照模块将代码写入到不同的Fragment中,之后在根据需要嵌入到Activity。
  • 可重用:多个Activiy可以复用同一个Fragment。
  • 适配性:根据不同的布局样式,组合不同的Fragment。

Fragment生命周期

Fragment作为管理一组View的组件,也是有自己的生命周期的,和Activity类似,但又不完全一样。基本生命周期如下图所以:

在这里插入图片描述

  • onAttach:在Fragment和Activity关联时调用,仅仅只调用一次。我们可以对传递给Fragment的参数进行解析。
  • onCreate:Fragment创建时调用,仅调用一次。
  • onCreateView:构建Fragment View的时候进行调用,返回Fragment要绘制布局的根视图。
  • onActivityCreated:在Activity执行完成onCreate之后进行调用。
  • onStart:Fragment对用户可见的时候进行调用。
  • onResume:Fragment可以和用户交互的时候进行调用。
  • onPause:Fragment对用户不可交互的时候进行调用。
  • onStop:Fragment对用户不可见时进行调用。
  • onDestroyView:Fragment需要移除视图时进行调用。
  • onDestroy:Fragment自身销毁时进行调用。
  • onDetach:Fragment和Activity解除关系时进行调用。

Fragment常用类以及方法解析

FragmentManager:通过getSupportFragmentManager函数获取,用来管理Activity中的Fragment。

FragmentTransaction:事务。可以通过FragmentManager调用beginTransaction()方法来开启一个事务。通过事务,我们可以对Fragment进行一系列的操作。如下:

  1. add:添加一个Fragment到Activity中。

  2. remove:从Activity中移除一个Fragment。如果Fragment没有被加入回退栈中,则该Fragment会进行销毁。

  3. replace:用另一个Fragment替换当前的Fragment,本质上时先remove再add。

  4. hide:隐藏当前的Fragment,设置为不可见,不会销毁,与之对应的是show。本质是对View进行调用了View.GONE

  5. show:显示之前隐藏的Fragment,与之对应的是hide。本质是对View进行调用了View.VISIBLE

  6. detach:将视图进行移除,但是并不销毁Fragment,Fragment依旧由FragmentManager进行管理。

  7. addToBackStack:将Fragment加入返回栈。当移除或替换一个Fragment并向返回栈添加事务时,系统会停止(而非销毁)移除的Fragment。如果没有addToBackStack,则被替换的Fragment会直接进行销毁

    //示例,如果用户执行回退操作进行Fragment的恢复,被替换的Fragment将重新启动。
    ft.replace(R.id.fl_content, LearnFragment()).addToBackStack(null).commitAllowingStateLoss()
    
  8. setMaxLifecycle:设定Fragment生命周期最高上限。如果设置的生命周期上限低于当前的生命周期上限,则会进行回退设定的生命周期。

    比如,当前生命周期为RESUME,如果设置MaxLifecycle为START,则生命周期则会回到RESUME。

    说一个例子,在调用hideshow函数的时候,Frgament的生命周期并不会进行变化。但是我就是想要在show的时候让Fragment走到RESUME呢?此时可以进行如下操作。

    //hide
    ft.setMaxLifecycle(fragment,Lifecycle.State.STARTED).hide(fragment)
    //show
    ft.setMaxLifecycle(fragment,Lifecycle.State.RESUMED).show(fragment)
    
  9. commit/commitAllowingStateLoss:提交一个事务。commit如果状态丢失则会抛异常,commitAllowingStateLoss则不会。

源码流程分析

接下来站在源码角度对Fragment进行一波分析,看看Fragment是如何实现的。相信可以对Fragment有更深层次的认识。

我们需要添加一个Fragment到Activity中时,一般会进行如下操作:

val fragmentManager = getSupportFragmentManager() // 获取fragmentManager
val ft = fragmentManager.beginTransaction() // 开启事务
ft.add(R.id.fl_content, LearnFragment(), tag) // 添加
ft.commitAllowingStateLoss() // 提交事务

我相信上面的代码,大家可能都非常熟悉了,接下来就根据上述代码,一步一步进行源码跟踪分析。

获取到FragmentManager。

getSupportFragmentManager()方法,在FragmentActivity类中。

FragmentActivity
final FragmentController mFragments = FragmentController.createController(new HostCallbacks());
public FragmentManager getSupportFragmentManager() {
    //通过mFragments 获取了FragmentManager  而mFragments即为 FragmentController
	return mFragments.getSupportFragmentManager();
}

FragmentController
//FragmentController 通过createController函数创建,其实即为new了FragmentController对象,传入了HostCallbacks
public static FragmentController createController(@NonNull FragmentHostCallback<?> callbacks) {
    return new FragmentController(checkNotNull(callbacks, "callbacks == null"));
}
public FragmentManager getSupportFragmentManager() {
    //mHost即为传入的HostCallbacks,即通过HostCallbacks获取到了FragmentManager
    return mHost.mFragmentManager;
}

//HostCallbacks 继承自 FragmentHostCallback,而mFragmentManager在FragmentHostCallback中进行了初始化。
FragmentHostCallback
final FragmentManager mFragmentManager = new FragmentManagerImpl();

所以在FragmentActivity中会持有FragmentController对象,而FragmentController其实是暴露在外的代理类,相关的逻辑均交由HostCallbacks完成,而HostCallbacks内部则构建了FragmentManagerImpl即我们所需要的FragmentManager对象。而HostCallbacks也提供了一些Fragment一些常用的参数,比如获取ActivityContextHandler等,该对象会在Fragment进行attach的时候传递给Fragment。

Activity getActivity() {
	return mActivity;
}
Context getContext() {
	return mContext;
}
Handler getHandler() {
	return mHandler;
}

beginTransaction干了什么事情

接下来看beginTransaction做了什么事情。

FragmentManager
public FragmentTransaction beginTransaction() {
	//仅仅返回了BackStackRecord对象
	return new BackStackRecord(this);
}

BackStackRecord
BackStackRecord(@NonNull FragmentManager manager) {
    super(manager.getFragmentFactory(), manager.getHost() != null
    ? manager.getHost().getContext().getClassLoader()
    : null);
    mManager = manager;
}

beginTransaction是FragmentManager的代码,所以自然在FragmentManager中寻找。但是我们发现很简单,beginTransaction其实就是简单的返回了一个BackStackRecord对象,同时它持有了FragmentManager对象。

ADD操作

我们接下来看一下add操作。通过上面我们知道FragmentTransaction其实是BackStackRecord对象。我们跟一下add操作。

FragmentTransaction
public FragmentTransaction add(@IdRes int containerViewId, @NonNull Fragment fragment,
@Nullable String tag) {
    //调用doAddOp注意次数传入的最后一个参数为 OP_ADD
    doAddOp (containerViewId, fragment, tag, OP_ADD);
    return this;
}
void doAddOp(int containerViewId, Fragment fragment, @Nullable String tag, int opcmd) {
    ...
    //tag 赋值
    fragment.mTag = tag;
    ...
    //容器Id 赋值
    fragment.mContainerId = fragment.mFragmentId = containerViewId
    ...
    //将OP_ADD和fragment 放入Op对象中,调用addOp
    addOp(new Op(opcmd, fragment));
}
ArrayList<Op> mOps = new ArrayList<>();
void addOp(Op op) {
	//放入数组中
    mOps.add(op);
    op.mEnterAnim = mEnterAnim;
    op.mExitAnim = mExitAnim;
    op.mPopEnterAnim = mPopEnterAnim;
    op.mPopExitAnim = mPopExitAnim;
}

总结add操作即将当前的命令和Fragment放到了集合里面存了起来。而Op则是最终存的对象,我们可以看一下其的数据结构。

 static final class Op {
        int mCmd;//代表当前具体是什么操作,比如add、replace、remove、hide.....
        Fragment mFragment;
        int mEnterAnim;
        int mExitAnim;
        int mPopEnterAnim;
        int mPopExitAnim;
        Lifecycle.State mOldMaxState;
        Lifecycle.State mCurrentMaxState;
}

COMMIT干了什么

好了,不知不觉我们就到了最后一步,接下来看看commit干了什么吧。上源码。

我相信现在经常用的是commitAllowingStateLoss吧,所以我们直接看它。

    BackStackRecord
    public int commitAllowingStateLoss() {
    	//调用commitInternal allowStateLoss = true
        return commitInternal(true);
    }
    int commitInternal(boolean allowStateLoss) {
        ...
        //调用到FragmentManager 的enqueueAction方法
        mManager.enqueueAction(this, allowStateLoss);
        return mIndex;
    }
    
    FragmentManager
    void enqueueAction(@NonNull OpGenerator action, boolean allowStateLoss) {
        if (!allowStateLoss) {
        	//由于我们使用的是commitAllowingStateLoss 所以allowStateLoss = true,所以不会校验下面的东西,即不会抛出下面的异常。
            if (mHost == null) {
                if (mDestroyed) {
                    throw new IllegalStateException("FragmentManager has been destroyed");
                } else {
                    throw new IllegalStateException("FragmentManager has not been attached to a "
                            + "host.");
                }
            }
            checkStateLoss();
        }
        synchronized (mPendingActions) {
            ...
            //加入数组
            mPendingActions.add(action);
            //继续调用
            scheduleCommit();
        }
    }
    void scheduleCommit() {
        synchronized (mPendingActions) {
            ...
            //核心 将mExecCommit 调度到主线程执行
            mHost.getHandler().post(mExecCommit);
        }
    }
	
    private Runnable mExecCommit = new Runnable() {
        @Override
        public void run() {
            execPendingActions(true);
        }
    };

    boolean execPendingActions(boolean allowStateLoss) {
        ...
        boolean didSomething = false;
        //将 mPendingActions 的BackStackRecord 对象放到了mTmpRecords数组中
        while (generateOpsForPendingActions(mTmpRecords, mTmpIsPop)) {
            mExecutingActions = true;
            try {
                //执行
                removeRedundantOperationsAndExecute(mTmpRecords, mTmpIsPop);
            } finally {
                cleanupExec();
            }
            didSomething = true;
        }
        ...
        return didSomething;
    }

Commit的操作比较多一点,到这里首先总结一下。

  1. 最终commit操作会交给FragmentManager进行统一执行。
  2. 最终的执行操作被调度到了主线程进行。
  3. commitAllowingStateLosscommit的本质区别是,不会对一些意外的状态进行校验而抛出异常,比如mHost==null或者已经destroy或者activity 已经调用过onSaveInstanceState了。

接下来继续调用到了removeRedundantOperationsAndExecute,中国调用链也比较复杂一点,所以这里直接放调用链了。

FragmentManager removeRedundantOperationsAndExecute 
👇
FragmentManager executeOpsTogether
👇
FragmentManager executeOps
👇
BackStackRecord executeOps // 注意此时执行到了BackStackRecord的executePopOps

接下来比较重要一点,我们看一下BackStackRecord里面的executePopOps是如何进行实现的

void executeOps() {
...
 for (int opNum = 0; opNum < numOps; opNum++) {
            final Op op = mOps.get(opNum);
            final Fragment f = op.mFragment;
            if (f != null) {
                f.setPopDirection(false);
                f.setNextTransition(mTransition);
                f.setSharedElementNames(mSharedElementSourceNames, mSharedElementTargetNames);
            }
            switch (op.mCmd) {
                case OP_ADD:
                    f.setAnimations(op.mEnterAnim, op.mExitAnim, op.mPopEnterAnim, op.mPopExitAnim);
                    mManager.setExitAnimationOrder(f, false);
                    //添加 内部fragment持有了当前的FragmentManager,同时构建了FragmentStateManager 将fragment放入到了FragmentStore的add和active中 fragment.removing 设置为了false
                    mManager.addFragment(f);
                    break;
                case OP_REMOVE:
                    f.setAnimations(op.mEnterAnim, op.mExitAnim, op.mPopEnterAnim, op.mPopExitAnim);
                    mManager.removeFragment(f);
                    break;
             ...
             //重点  状态移动
             mManager.moveToState(mManager.mCurState, true);

还记得之前在add时候看到的数组吗?在BackStackRecord中被拿出来进行循环获取需要执行的命令。比如add,则执行了addFragment。其他的命令也一样在这个地方执行,就不一一列举了。

	FragmentManager
    FragmentStateManager addFragment(@NonNull Fragment fragment) {
        FragmentStateManager fragmentStateManager = createOrGetFragmentStateManager(fragment);
        //持有FragmentManager
        fragment.mFragmentManager = this;
        //fragmentStateManager 放入store的active数组
        mFragmentStore.makeActive(fragmentStateManager);
        if (!fragment.mDetached) {
            //将fragment放入store
            mFragmentStore.addFragment(fragment);
            //设置mRemoving 为false
            fragment.mRemoving = false;
            if (fragment.mView == null) {
                //设置mHiddenChanged为false
                fragment.mHiddenChanged = false;
            }
            if (isMenuAvailable(fragment)) {
                mNeedMenuInvalidate = true;
            }
        }
        return fragmentStateManager;
    }

接下来看一下moveToState函数

FragmentManager
void moveToState(@NonNull Fragment f, int newState) {
        FragmentStateManager fragmentStateManager = mFragmentStore.getFragmentStateManager(f.mWho);
        ...
        newState = Math.min(newState, fragmentStateManager.computeExpectedState());
        if (f.mState <= newState) {
         // If we are moving to the same state, we do not need to give up on the animation.
            if (f.mState < newState && !mExitAnimationCancellationSignals.isEmpty()) {
                // The fragment is currently being animated...  but!  Now we
                // want to move our state back up.  Give up on waiting for the
                // animation and proceed from where we are.
                cancelExitAnimation(f);
            }
            switch (f.mState) {
                case Fragment.INITIALIZING:
                    if (newState > Fragment.INITIALIZING) {
                        //attach
                        fragmentStateManager.attach();
                    }
                    // fall through
                case Fragment.ATTACHED:
                    if (newState > Fragment.ATTACHED) {
                        //create
                        fragmentStateManager.create();
                    }
                    // fall through
                case Fragment.CREATED:
                    // We want to unconditionally run this anytime we do a moveToState that
                    // moves the Fragment above INITIALIZING, including cases such as when
                    // we move from CREATED => CREATED as part of the case fall through above.
                    if (newState > Fragment.INITIALIZING) {
                        fragmentStateManager.ensureInflatedView();
                    }

                    if (newState > Fragment.CREATED) {
                        fragmentStateManager.createView();
                    }
                    ....
        }else{
        	switch (f.mState) {
                case Fragment.RESUMED:
                    if (newState < Fragment.RESUMED) {
                        fragmentStateManager.pause();
                    }
                    // fall through
                case Fragment.STARTED:
                    if (newState < Fragment.STARTED) {
                        fragmentStateManager.stop();
                    }
                    ....
        }
        f.mState = newState;
}

找到了,最终在FragmentmoveToState函数中,对Fragment状态进行了流转。期望的状态通过fragmentStateManagercomputeExpectedState进行计算。并且各个生命周期均调用到了FragmentStateManager进行处理。

那么Activity的生命周期如何分发给Fragment了呢?还记得之前在Activity中的FragmentController了吗?其代理了FragmentManager

比如要告知Activity当前已经Create完成了,则是如下流程:

    FragmentActivity
    protect void start(){
        ...
        mFragments.dispatchActivityCreated();
    }
    //👇
    //FragmentController
    public void dispatchActivityCreated() {
        mHost.mFragmentManager.dispatchActivityCreated();
    }
    //👇
    //FragmentManager
    void dispatchActivityCreated() {
    	...
        dispatchStateChange(Fragment.ACTIVITY_CREATED);
    }
    private void dispatchStateChange(int nextState) {
    	...
    	//最终执行到了moveToState函数
        moveToState(nextState, false);
    }

都到这里了,那我们简单看一个Fragment的生命周期执行函数吧,比如我们看CreateView,则源代码在FragmentStateManager中进行执行。

   void createView() {
       ...
        LayoutInflater layoutInflater = mFragment.performGetLayoutInflater(
                mFragment.mSavedFragmentState);
        ViewGroup container = null;
        ...
        //获取到Fragment的容器
        mFragment.mContainer = container;
       //调用到createView,由开发者创建View
        mFragment.performCreateView(layoutInflater, container, mFragment.mSavedFragmentState);
        ...
        //将view添加到Fragment容器 其实是调用了container.addView
        addViewToContainer();
        ...
        mFragment.mState = Fragment.VIEW_CREATED;
    }

看吧,其实Fragment核心无非就是对View进行管理,让其有生命周期。最核心的一句还的是addView

总结

简单总结一下:

  1. FragmentActivity 支持fragment功能的最底层的activity。
  2. FragmentActivity周期的分发,通过fragmentController最终分发给了FragmentManager,调用moveToState
  3. moveToState的一些状态管理操作则交由了FragmentStateManager进行统一处理。
  4. FragmentTransition是抽象类,具体实现为BackStackRecord,同时beginTransaction即为返回了BackStackRecord对象。
  5. 一个BackStackRecord对象可以进行多个操作,封装为Op对象存在数组中。最终通过FragmentManager相关的操作还是调度由自己的executeOps操作进行执行,比如add、remove、hide等等。
  6. 操作比较多,但是大同小异,可以根据自身需要查看不同的代码执行流程。比如hide、show等等。

🙆‍♀️。欢迎技术探讨噢!

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

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

相关文章

长沙烟火气回来了,颐而康客流回暖为什么这么快?

随着一大批阳康的人们走出家门&#xff0c;长沙这座消费之城也逐步恢复了往日的活力。车多起来了、路堵起来了、线下店铺恢复营业了、长沙的烟火气息又回来了。 在颐而康万家丽西子店的大厅里&#xff0c;等候休息区已经坐满了顾客&#xff0c;他们有的在等待&#xff0c;有的…

Centos6从零开始安装mysql和tomcat后台环境,并成功部署Tomcat项目

最近因为搞定了一些环境的搭建因为项目过于老旧的缘故我从centosstream9一直改换7一直到6都没有成功一直到改成6.5的32位版本才算是成功搭建完成所以特地来写一篇文章记录一下。 首先我的liunx使用版本是 centos6.5 32位 java版本&#xff1a;jdkCentos6从零开始安装mysql和tom…

7-6 整除光棍

这里所谓的“光棍”&#xff0c;并不是指单身汪啦~ 说的是全部由1组成的数字&#xff0c;比如1、11、111、1111等。传说任何一个光棍都能被一个不以5结尾的奇数整除。比如&#xff0c;111111就可以被13整除。 现在&#xff0c;你的程序要读入一个整数x&#xff0c;这个整数一定…

【Kuangbin数论】阿拉丁和飞毯

4577. 阿拉丁和飞毯 - AcWing题库 题意&#xff1a; 思路&#xff1a; 就是去求x和y 使得 1.x!y 2.x*ya 3.min(x,y)b 一开始想的是去根号n地枚举a的约数 &#xff0c;然后直接统计 但是这样肯定T&#xff0c;所以换成dfs枚举约数去了 但是也T了 首先a*a<b的话直接特…

前端 | 手把手教你装饰你的github profile(github 首页)

1.创建存储库 您可以创建一个与您的 github 帐户名同名的存储库 添加README文件 2.编辑README.md 现在&#xff0c;可以根据自己的喜好修改 repo 中的自述文件&#xff0c;但我在考虑包含哪些信息时查看了其他开发人员的资料。通常包括简短的介绍、使用的技术堆栈和联系方式…

Buildroot编译hisi平台根文件系统

Buildroot编译hisi平台根文件系统 文章目录1. 下载Buildroot源码2. Menuconfig配置3. 编译Buildroot3.1 手动下载软件包3.2 kernel header 报错3.3 arm-hisiv300-linux-gcc-ar&#xff1a;cannot find plugin liblto_plugin.so3.4 /media/data/hisi/buildroot-2022.02.8/output…

C++类的多种构造函数

目录默认构造函数普通构造函数拷贝构造函数转换构造函数移动构造函数举例两个场景下面以Complex 复数类来学习C类中的各种构造函数; #include <iostream> using namespace std;//复数类 class Complex{friend ostream & operator<<(ostream &out, Complex…

2022年终结——人生中最美好的一站

文章目录前言回顾2022工作上学习上投资上生活上展望2023工作学习投资生活总结有一种责任与压力&#xff0c;叫做上有老下有小&#xff0c;但有一种幸福也叫做上有老下有小&#xff0c;当你遭遇挫折与困难时&#xff0c;这些“老小”以及那个同龄的“她”是你坚实的后盾&#xf…

Redisson中的“琐事”

文章目录前言锁分类Redisson可重入锁&#xff08;Reentrant Lock&#xff09;公平锁&#xff08;Fair Lock&#xff09;联锁&#xff08;MultiLock&#xff09;红锁&#xff08;RedLock&#xff09;读写锁&#xff08;ReadWriteLock&#xff09;信号量&#xff08;Semaphore&am…

【C++】左值、右值、语义移动和完美转发

右值引入的目的是为了对象移动&#xff1a; 因为在很多情况下&#xff0c;对象拷贝会经常发生&#xff0c;但是很多对象在拷贝后就直接被销毁了。这对性能是一个很大损耗。在重新分配内存的时候&#xff0c;从旧的内存将元素拷贝到新的内存中是不必要的。更好的方法是移动元素。…

论文投稿指南——中文核心期刊推荐(天文、测绘学)

【前言】 &#x1f680; 想发论文怎么办&#xff1f;手把手教你论文如何投稿&#xff01;那么&#xff0c;首先要搞懂投稿目标——论文期刊 &#x1f384; 在期刊论文的分布中&#xff0c;存在一种普遍现象&#xff1a;即对于某一特定的学科或专业来说&#xff0c;少数期刊所含…

使用Kalibr问题汇总:ModuleNotFoundError: No module named ‘wx‘

问题1&#xff1a; 报错&#xff1a;/kalibr_ws/src/Kalibr/Schweizer-Messer/sm_python/python/sm/PlotCollection.py", line 4, in import wx ModuleNotFoundError: No module named ‘wx’ 解决&#xff1a; sudo apt-get install python3-wxgtk4.0问题2&#xff1…

MySQL补齐函数LPAD和RPAD之SQLite解决方案

工作中经常需要对数据进行清洗&#xff0c;并对个别字段进行格式化处理&#xff0c;像 字符串左右补齐。MySQL数据库自带有LPAD()、RPAD()&#xff0c;而SQLite数据库没有的相应函数&#xff0c;需要自己转换。 目录 1、MySQL数据库 1.1、MySQL左右补全函数 1.2、实践验证 …

阶段性回顾(5)与一些题目实例(数组合并,有序判断,删除元素,进制问题等)

tips 1. 内存栈区的使用习惯是先使用高地址&#xff0c;再使用低地址。并且你还要清楚&#xff1a;随着数组下标的增大&#xff0c;其元素的地址也是在不断变高&#xff1b;对于一个占多个内存单元的变量进行取地址&#xff0c;取出来的是其所占内存空间最低地址的内存单元的地…

Python内存机制 -- = 赋值操作

Python内存机制 python的万物皆对象可不只是说说而已。 1. 预备知识&#xff1a; id()&#xff1a;可以将id()理解为C语言中的*&#xff0c;其返回当前对象在内存中的地址。 int p id(object) # id函数返回对象object在其生命周期内位于内存中的地址&#xff0c;id函数的参数…

Python数据分析案例18——化学分子数据模型(机器学习分类问题全流程)

1. 引言 1.1设计背景 对分子进行分类&#xff0c;对于筛选特定疾病的候选药物是至关重要的。传统的机器学习算法可以对分子进行分类&#xff0c;但是分子不能直接作为机器学习模型的输入&#xff0c;需要进行大量的实验从分子中得到一系列的分子特性。将分子特征使用数字化进…

47-Jenkins-终止构建并设置构建结果

终止构建并设置构建结果前言获取构建结果终止构建并设置构建状态权限问题解决前言 本篇来学习Jenkins终止构建的方法&#xff0c;使用场景&#xff1a;根据前一个构建状态&#xff0c;判断当前构建是否运行 获取构建结果 上次构建结果&#xff1a;currentBuild.getPreviousB…

【LeetCode题目详解】(五)144.二叉树的前序遍历、94.二叉树的中序遍历、145.二叉树的后序遍历、104.二叉树的最大深度、110.平衡二叉树

目录 一、力扣第144题&#xff1a;二叉树的前序遍历 1.解题思路 2.解题代码 二、力扣第94题&#xff1a;二叉树的中序遍历 三、力扣第145题&#xff1a;二叉树的后序遍历 四、力扣第104题&#xff1a;二叉树的最大深度 1.解题思路 2.解题代码 五、力扣第110题&#xff1…

抖音直播间弹幕rpc学习

目标url 随便找个直播间即可。 https://live.douyin.com/198986091107 接口分析 首先并没有在xhr下找到对应的接口 因为采用了websocket来传输信息。切换到ws即可看到 消息下&#xff0c;可以看到16进制的数据在源源不断地增加。 那么我们只要找到反序列化后的数据&…

在wsl下开发T113的主线linux(4)-编译kernel

接下来编译kernel&#xff0c;编译过程可能会出现缺少命令的报错&#xff0c;大概是下面这几个 sudo apt update sudo apt install flex bison bc libncurses-dev 目前linux主线的最新版本并没有适配t113的相关外设驱动&#xff0c;虽然能启动并串口打印&#xff0c;但其他的…