Android架构之LiveData组件

article/2025/11/10 9:06:26

Jetpack组件系列文章
Android架构之LifeCycle组件
Android架构之Navigation组件(一)
Android架构之Navigation组件(二)
Android架构之Navigation组件(三)
Android架构之Navigation组件(四)
Android架构之ViewModel组件
Android架构之LiveData组件
Android架构之Room组件(一)
Android架构之Room组件(二)
Android架构之WorkManager组件
Android架构之DataBinding(一)
Android架构之DataBinding(二)
Android架构之Paging组件(一)
Android架构之Paging组件(二)
Jetpack与MVVM架构

前言

在上一节中,我们学习了ViewModel,我们使用的是接口来完成ViewModel与页面之间的通信,其实这并不是好的方案。 这篇博客,就让我们来了解LiveData与ViewModel是如何配合工作的。

LiveData介绍

LivaData是一个可被观察的数据容器类。具体来说,可以将LiveData理解为一个数据的容器,它将数据包装起来,使数据成为观察者,当该数据发生变化时,观察者能够获得通知。与常规的可观察类不同,LiveData可以感知(如Activity、Fragment或Service)的生命周期。

简单来说,LiveData具有如下优势

  1. LiveData 遵循观察者模式。当生命周期状态发生变化时,LiveData 会通知 Observer 对象,可以在这些Observer对象中更新界面
  2. 不会发送内存泄露
  3. 如果观察者的生周期处于非活跃状态(如返回栈中的Activity),则它不会接收任何LivaData事件,但是,当非活跃状态变成活跃状态时会立刻接收最新的数据(后台的Activity返回前台时)
  4. 当config导致Activity/Fragment重建时,不需要再手动的管理数据的存储与恢复。

LiveData和ViewModel的关系

ViewModel用于存放页面所需要的各种数据,对页面来说,它并不关心ViewModel中的业务逻辑,它只关心需要展示的数据是什么,并且希望再数据发送变化时,能及时得到通知并做出更新。LiveData的作用就是,在ViewModel中的数据发生变化时通知页面,用于包装ViewModel中那些需要被外界观察的数据。在这里插入图片描述

LiveData基本使用

在上篇博客中的ViewModel的计时器案例的基础上,我们使用LiveData对接口进行改写

1.LiveData是一个抽象类,不能直接使用。我们通常使用的是它的直接子类MutableLiveData,代码如下

public class LiveDataViewModel extends ViewModel {private MutableLiveData<Integer> currentSecond;private Timer timer;private  int current;@Overrideprotected void onCleared() {super.onCleared();//释放资源timer.cancel();}public LiveData<Integer> getCurrentSecond(){if(currentSecond == null){currentSecond = new MutableLiveData<>();}return  currentSecond;}//开始定时器public  void startTiming(){if(timer == null){current = 0;timer = new Timer();TimerTask timerTask = new TimerTask() {@Overridepublic void run() {if(currentSecond!=null){currentSecond.postValue(current++);}}};timer.schedule(timerTask,1000,1000);}}//关闭定时器public  void stopTiming(){timer.cancel();}
}

当开始定时器的时候,也就是我们数据资源发生变化的时候,我们需要调用livedata.postvalue方法,通知页面我们数据源已经发生了改变。至于为什么不用livedata.setValue方法,等下我们会说到。

2.接着我们在Activity中创建ViewModel,并监听ViewModel里面currentSecond数据的变化。

public class LiveDataActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_live_data);iniComponent();}private void iniComponent() {//通过ViewModelProvider得到ViewModelfinal LiveDataViewModel viewModel = new ViewModelProvider(this).get(LiveDataViewModel.class);//得到ViewModel中的LiveDatafinal MutableLiveData<Integer> liveData = (MutableLiveData<Integer>) viewModel.getCurrentSecond();//通过liveData.observer()观察ViewModel中数据的变化liveData.observe(this, new Observer<Integer>() {@Overridepublic void onChanged(Integer integer) {//收到回调后更新UI界面TextView tv = findViewById(R.id.tv_texts);tv.setText("小鑫啊"+integer);}});//关闭定时器findViewById(R.id.btnReset).setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {//通过LiveData.setValue()/LiveData.postValue()//完成对ViewModel中数据的更新liveData.setValue(0);//关闭定时器viewModel.stopTiming();}});//计时开始viewModel.startTiming();}
}

在页面中,通过LiveData.observe()方法对LivaData所包装的数据进行观察。当我们数据源发生变化了(也就是我们想修改LivaData所包装的数据时),就可以通过LiveData.postValue/LiveData.setValue()来完成,然后onChanged方法就会收到我们修改之后的数据,我们就可以对UI进行更改了.

需要注意的是:postValue()方法用在非UI线程中,而setValue()方法用在UI线程中,这就是为什么我们在开始定时器的时候,需要调用postVaule()发送数据了(因为定时器是运行在非UI线程的).
在这里插入图片描述

运行结果如下:
在这里插入图片描述
LivaData的基本使用就到这里,是不是很简单啊! 接下来,就让我们来探讨下LiveData的原理吧!!!

LiveData的原理

我们知道LiveData是通过观察者模式实现的。当数据发送改变的时候,会回调Observer的onChanged(),接下来就让我们深入Observer方法的源码一探究竟

observe源码

 @MainThreadpublic void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {assertMainThread("observe");if (owner.getLifecycle().getCurrentState() == DESTROYED) {// ignorereturn;}LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);//判断当前wapper已经添加过,如果添加过就直接返回,否则返回nullObserverWrapper 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;}//没有添加过,则添加wrapperowner.getLifecycle().addObserver(wrapper);}

从源码可以看出,Observer()方法接收的第一个参数是一个LifecleOwner对象,我们传入的是this,因为this的祖父类实现了这个接口,也正是LifecleOwner对象,LiveData才会具体生命周期感知能力。

首先, 通过owner.getLifecycle().getCurrentState()获取当前页面的状态,如果当前页面被销毁了,就直接返回,也就是说LiveData会自动清除与页面的关联。

LifecycleBoundObserver 源码

 class LifecycleBoundObserver extends ObserverWrapper implements LifecycleEventObserver {@NonNullfinal LifecycleOwner mOwner;LifecycleBoundObserver(@NonNull LifecycleOwner owner, Observer<? super T> observer) {super(observer);mOwner = owner;}@Overrideboolean shouldBeActive() {return mOwner.getLifecycle().getCurrentState().isAtLeast(STARTED);}@Overridepublic void onStateChanged(@NonNull LifecycleOwner source,@NonNull Lifecycle.Event event) {if (mOwner.getLifecycle().getCurrentState() == DESTROYED) {removeObserver(mObserver);return;}activeStateChanged(shouldBeActive());}

当调用 LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer),本质是通过 ObserverWrapper将observer包装起来,得以LiveData能对生命周期状态得以进行监听,是通过onStateChanged和shouldBeActive方法

  1. shouldBeActive 这里调用LiftCycle的方法,表达如果当前生命周期的状态为onStart,onResume,onPause时 返回true,也就是说只有这三个状态可以接收数据更新。
  2. onStateChanged 是LifecycleEventObserver接口的方法,当生命周期发送变化的时候会回调它,如果当前生命周期状态是destory,就会直接移除观察者,否则就会调用activeStateChanged(shouldBeActive());方法激活观察者.

方法中的最后一行代码将observer与Activity的生命周期关联在一起。因此,LivaData能够感知页面的生命周期。

observer方法小结

  1. 判断是否已经销毁,如果当前页面销毁,LiveData自动清除与页面的关联
  2. 用LifecycleBoundObserver 对observer进行一个包装
  3. 判断当前observer是否已经添加过,添加过就直接返回
  4. 将observer方法与Activity的生命周期进行关联

setValue方法

 @MainThreadprotected void setValue(T value) {assertMainThread("setValue");mVersion++;mData = value;dispatchingValue(null);}

setValue()中,首先 断言是主线程,这里的关键是dispatchingValue(null)方法

void dispatchingValue(@Nullable ObserverWrapper initiator) {if (mDispatchingValue) {mDispatchInvalidated = true;return;}mDispatchingValue = true;do {mDispatchInvalidated = false;if (initiator != null) {considerNotify(initiator);initiator = null;} else {for (Iterator<Map.Entry<Observer<? super T>, ObserverWrapper>> iterator =mObservers.iteratorWithAdditions(); iterator.hasNext(); ) {considerNotify(iterator.next().getValue());if (mDispatchInvalidated) {break;}}}} while (mDispatchInvalidated);mDispatchingValue = false;}

只有处于active(激活)状态的观察者,这个方法就会把数据发送给它们。由于每次dispathchingValue传入的null,所以会走else这一部分代码, 这时候就会遍历所有的observer,最后通过调用considerNotify()将数据进行分发给所有的observer

private void considerNotify(ObserverWrapper observer) {if (!observer.mActive) {return;}// Check latest state b4 dispatch. Maybe it changed state but we didn't get the event yet.//// we still first check observer.active to keep it as the entrance for events. So even if// the observer moved to an active state, if we've not received that event, we better not// notify for a more predictable notification order.//如果当前observer不是激活状态,也就是当前页面被destory,直接return.if (!observer.shouldBeActive()) {observer.activeStateChanged(false);return;}if (observer.mLastVersion >= mVersion) {return;}observer.mLastVersion = mVersion;observer.mObserver.onChanged((T) mData);}

只有出于活跃状态且数据是数据是最新的,才会去分发数据,最后回调到我们熟悉的onChanged()方法。

postValue方法

 protected void postValue(T value) {boolean postTask;synchronized (mDataLock) {postTask = mPendingData == NOT_SET;mPendingData = value;}if (!postTask) {return;}ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable);}

postValue方法是可以在子线程(非UI线程)发送数据的,但是onChanged()方法始终是在主线程? 答案就在postToMainThread(mPostValueRunnable)方法中;

private final Runnable mPostValueRunnable = new Runnable() {@SuppressWarnings("unchecked")@Overridepublic void run() {Object newValue;synchronized (mDataLock) {newValue = mPendingData;mPendingData = NOT_SET;}setValue((T) newValue);}};

创建一个Handler将子线程中的任务发送到主线程去执行,其本质还是调用了setValue()方法

LiveData.observeForever()方法

如果你想无论页面处于何种生命周期,setValue/postValue之后立刻回到数据。那么可以使用observerForever()方法,使用起来与observer()没有太大差别. 因为AlwaysActiveObserver没有实现GenericLifecycleObserver 接口,不能感应生命周期。

但是需要注意的是,在用完之后,一定要记得在onDestroy()方法中调用removeObserver()方法来停止对LiveData的观察,否则LiveData会一直处于激活状态,Activity则永远不会被系统自动回收,会造成内存泄露。

ViewModel+LiveData实现Fragment间的通信

我们已经知道,ViewModel能够将数据从Activity中剥离出来。只要Activity不被销毁,ViewModel会一直存储,并且独立于Activity的配置变化。

Fragment可以被看作Activty的子页面,即一个Activity中可以包含多个Fragment.这些Fragment彼此独立,但是又都属于同一个Activity.

基于ViewModel和Fragment组件的这些特性,我们可以利用LiveData,实现同一个Activity中的不同Fragment间的通信,因为不同的Fragment得到的都是同一个LiveData;

在这里插入图片描述

定义ViewModel和LiveData

public class SharedViewModel extends ViewModel {private MutableLiveData<String> content;@Overrideprotected void onCleared() {super.onCleared();//释放资源content=  null;}public LiveData<String> getContent(){if(content == null){content = new MutableLiveData<>();}return  content;}
}

初始化Fragment

Fragment之间的跳转我们使用导航图来进行跳转

share_graph.xml

<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:id="@+id/share_graph"app:startDestination="@id/masterFragment"><fragmentandroid:id="@+id/masterFragment"android:name="com.example.jetpack.MasterFragment"android:label="fragment_master"tools:layout="@layout/fragment_master" ><actionandroid:id="@+id/action_masterFragment_to_detailFragment"app:destination="@id/detailFragment" /></fragment><fragmentandroid:id="@+id/detailFragment"android:name="com.example.jetpack.DetailFragment"android:label="fragment_detail"tools:layout="@layout/fragment_detail" />
</navigation>

MasterFragment 布局

我们使用EditText输入框,输入内容后,点击跳转到DetailFragment后,DetailFragment获取到输入框的内容,并显示在TextView上

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"tools:context=".MasterFragment"android:gravity="center"android:orientation="vertical"><EditTextandroid:id="@+id/edit_text"android:layout_width="match_parent"android:layout_height="wrap_content"android:hint="输入内容"android:textSize="20sp"/><Buttonandroid:id="@+id/toDetail"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="toDetailFragment"/></LinearLayout>

MasterFragment 代码

  private SharedViewModel model;@Overridepublic View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState) {View view =  inflater.inflate(R.layout.fragment_master, container, false);//设置数据model = new ViewModelProvider(getActivity()).get(SharedViewModel.class);final MutableLiveData<String> mutableLiveData = (MutableLiveData<String>) model.getContent();final EditText editText = view.findViewById(R.id.edit_text);view.findViewById(R.id.toDetail).setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {//获取EditText中的数据,并通知livaData进行更新String text = editText.getText().toString().trim();mutableLiveData.setValue(text);Navigation.findNavController(v).navigate(R.id.action_masterFragment_to_detailFragment);}});return view;}

detailFragment布局

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"tools:context=".DetailFragment"android:gravity="center"><TextViewandroid:id="@+id/tv_text1"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="哈哈哈"android:textSize="30sp"android:textStyle="bold"/></LinearLayout>

detailFragment代码

@Overridepublic View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState) {View view =inflater.inflate(R.layout.fragment_detail, container, false);final TextView textView = view.findViewById(R.id.tv_text1);SharedViewModel model = new ViewModelProvider(getActivity()).get(SharedViewModel.class);MutableLiveData<String> mutableLiveData = (MutableLiveData<String>) model.getContent();//对LiveData进行监听mutableLiveData.observe(getActivity(), new Observer<String>() {@Overridepublic void onChanged(String s) {//显示在UI上textView.setText(s);}});return view;}

Activity布局

<?xml version="1.0" encoding="utf-8"?>
<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"xmlns:app="http://schemas.android.com/apk/res-auto"android:layout_width="match_parent"android:layout_height="match_parent"tools:context=".viewmodel.ShareActivity"android:gravity="center"><fragmentandroid:id="@+id/nav_host_fragment"android:layout_width="match_parent"android:layout_height="wrap_content"android:name="androidx.navigation.fragment.NavHostFragment"app:defaultNavHost= "true"app:navGraph="@navigation/share_graph"/></LinearLayout>

只是定义一个Fragment来显示两个Fragment而已,代码文件没有进行任何的更改

运行程序:请添加图片描述
请添加图片描述
事实证明,两个Fragment获取到的是同一个LiveData, 在MasterFragment对LiveData数据进行更改,在DetailFragment对LiveData进行监听,并将监听到的数据显示在TextView上面。 是不是也非常简单啦

总结

  1. 本节中,我们学习了LiveData+ViewModel的基本使用。
  2. 并对LiveData源码,进行了一个大概的分析。知道了LiveData为什么能感知组件的生命周期
  3. LiveData的本质是观察者模式,可以感知页面的生命周期,当然你也可以使用observeForver()方法让LiveData忽略页面的生命周期,但是需要注意,用完之后要在onDestroy()方法用removeObserver()方法移除监听,否则会造成内存泄露。
  4. LiveData大部分是在ViewModel中使用的,但是它的作用不止于此,下节,我们就来说,LiveData如果搭配Room数据库组件进行使用.

好了,LiveData到这里就结束了,不足之处,望大家指出来,谢谢。

参考:

  1. LiveData解析
  2. LiveData源码解析

http://chatgpt.dhexx.cn/article/VQYDBgBN.shtml

相关文章

LiveData的基本使用

我们在《ViewModel的基本使用》这篇文章中提到了&#xff0c;ViewModel的主要作用是存放页面所需要的各种数据&#xff0c;而当这些数据发生变化时&#xff0c;我们采用接口的方式实现对页面的通知。 这样做是可行的&#xff0c;但如果要观察的数据很多&#xff0c;则需要定义大…

DataBinding与LiveData

DataBinding 一、添加配置二、使用修改布局文件具体使用单向绑定、方法绑定双向绑定、加载网络图片例子Adapter中使用使用 ObservableField 一、添加配置 如果需要使用databinding 需要在gradle中添加如下配置 defaultConfig {......//开启dataBindingdataBinding {enabled …

ViewModel+LiveData+DataBinding

1 ViewModel ViewModel要解决的问题 瞬态数据的丢失异步调用的内存泄露类膨胀提高维护难度和测试难度 ViewModel是介于视图和数据模型之间的桥梁&#xff0c;使数据和视图分离的同时还能通信。 2 LiveData ViewModel中可以存储普通数据&#xff0c;LiveDate数据 普通数据…

【Jetpack】LiveData 架构组件 ( LiveData 简介 | LiveData 使用方法 | ViewModel + LiveData 示例 )

文章目录 一、LiveData 简介二、LiveData 使用方法三、ViewModel LiveData 简单示例1、ViewModel LiveData 代码2、Activity 组件代码3、运行效果展示 四、ViewModel LiveData Fragment 通信示例1、ViewModel LiveData 代码2、Activity 组件代码Activity 代码布局文件 3、…

LiveData详细分析

目录介绍 01.LiveData是什么东西02.使用LiveData的优势03.使用LiveData的步骤04.简单使用LiveData05.observe()和observerForever()06.LiveData原理介绍07.observe订阅源码分析08.setValue发送源码分析09.observeForever源码10.LiveData源码总结 00.使用LiveData实现bus事件总…

LiveData+Room

文章目录 LiveData1.LiveData的作用2.LiveData的特点3.LiveData与ViewModel(无参数)的结合4.LiveData与ViewModel(有参数)的结合5.map()和switchMap()5.0 map()和switchMap()的作用5.1map()5.11没带ViewModel版5.12带ViewModel版 5.2switchMap() Room1.Room的定义2.Room的三个组…

Android架构组件(二)——LiveData

Android架构组件&#xff08;二&#xff09;——LiveData 上一篇文章讲到了Android架构组件之一Lifecycle组件&#xff08;Android 架构组件&#xff08;一&#xff09;——Lifecycle-Aware Components&#xff0c;现在我们再来看看另一个成员LiveData。 定义 简单地说&#xf…

Android LiveData 使用详解

说在前面 本次推出 Android Architecture Components 系列文章&#xff0c;目前写好了四篇&#xff0c;主要是关于 lifecycle&#xff0c;livedata 的使用和源码分析&#xff0c;其余的 Navigation&#xff0c; Paging library&#xff0c;Room&#xff0c;WorkMannager 等春节…

由浅入深,详解 LiveData 的那些事

引言 关于LiveData,在2022尾声的今天&#xff0c;从事 Android 开发的小伙伴一定不会陌生。相应的&#xff0c;关于 LiveData 解析与使用的文章更是数不胜数&#xff0c;其中不乏优秀的创作者&#xff0c;在众多的文章以及前辈面前&#xff0c;本篇也不敢妄谈能写的多么深入,易…

LiveData的原理和使用

LiveData Livedata是什么&#xff1f;它的作用是什么&#xff1f;我们能用它来干什么&#xff1f; 首先&#xff0c;LiveData是一种可观察的数据存储类。这句话可以看成两个部分&#xff0c;一个是可观察的类&#xff0c;另一个是数据存储的类。 LiveData 是可以被观察的&am…

python电子书合集

Python电子书合集 小编最早接触的编程语言是C&#xff0c;决心走上编程之路的是JavaScript&#xff0c;后来入职靠的Java&#xff0c;后来发现还有一门短小精悍的语言叫——Python。 本来以为是一门新语言&#xff0c;结果才发现是自己out了&#xff0c;没想到这门语言比小编还…

《Python Cookbook》(中文第三版):电子书

《Python Cookbook&#xff08;第3版&#xff09;中文版》介绍了Python应用在各个领域中的一些使用技巧和方法&#xff0c;其主题涵盖了数据结构和算法&#xff0c;字符串和文本&#xff0c;数字、日期和时间&#xff0c;迭代器和生成器&#xff0c;文件和I/O&#xff0c;数据编…

python编程入门电子书-《Python编程 从入门到实践》高清电子书免费下载

今天给大家分享一本书 获取方式 公众号后台回复 Python基础 获取百度网盘下载链接 书籍简介 本书旨在让你成为优秀的程序员&#xff0c;具体地说&#xff0c;是优秀的Python程序员。通过阅读本书&#xff0c;你将迅速掌握编程概念&#xff0c;打下坚实的基础&#xff0c;并…

【小沐学Python】Python实现在线电子书制作(MkDocs + readthedocs + github + Markdown)

文章目录 1、简介2、安装3、创建新项目4、添加页面5、编辑导航页6、设置主题7、更改图标图标8、构建网站9、部署9.1 准备github项目9.2 注册登录Read the Docs9.3 导入github项目到 Read the Docs 10、Markdown语法10.1 横线10.2 标题10.3 段落10.4 文字高亮10.5 换行10.6 斜体…

超全的Python完全版电子书——从基础到爬虫、分析等高级应用,限时下载

python3.11即将于下半年发布&#xff0c;新的版本速度提升2倍&#xff0c;以弥补与其他编程语言在速度上的缺陷。可以预见Python语言在未来的应用范围会越来越广。 python学习方向建议&#xff1a; 如果你是本科及以下学历&#xff0c;建议你学习以下两个方向 1、爬虫。简单的…

【小沐学Python】Python实现在线电子书制作(Sphinx + readthedocs + github + Markdown)

文章目录 1、简介2、安装3、创建测试工程4、项目文件结构5、编译为本地文件6、编译为http服务7、更改样式主题8、支持markdown9、修改文档显示结构10、项目托管到github11、部署到ReadtheDocs结语 1、简介 Sphinx 是一个 文档生成器 &#xff0c;您也可以把它看成一种工具&…

python编程入门电子书-Python3零基础教材电子书合集

Python3零基础教材电子书合集&#xff0c;传送门&#xff1a;https://www.52pojie.cn/thread-676318-1-1.html 一、《Python编程从入门到实践》 链接&#xff1a;https://pan.baidu.com/s/1o9wJq0y 密码&#xff1a;12od 这书楼主现在也在看&#xff0c;讲的很细&#xff0c;…

学习Python必看的经典书籍(附电子书)

哈喽&#xff0c;我是牙儿 今天给大家推荐几本经典的Python书籍 一起来看看都有哪些吧~ 1 《Python学习手册&#xff08;第4版&#xff09;》 这本书全面、深入地介绍了 Python 语言&#xff0c;不管你是编程新手还是 Python 初学者&#xff0c;它将帮助你快速实现使用 Pyt…

超全的Python完全版电子书.pdf !从基础到爬虫、分析等高级应用,限时下载

python3.11即将于下半年发布&#xff0c;新的版本速度提升2倍&#xff0c;以弥补与其他编程语言在速度上的缺陷。可以预见Python语言在未来的应用范围会越来越广。 python学习方向建议&#xff1a; 如果你是本科及以下学历&#xff0c;建议你学习以下两个方向 1、爬虫。简单…

python入门经典电子书-推荐6本学习Python的免费电子书

便宜并不是没好货&#xff0c;这里的一些书籍已经被很多大学作为课本来使用&#xff0c;比如麻省理工的计算机科学与编程入门课程&#xff0c;加利福尼亚大学的编程思想课程都用到了下面的某(几)本书籍。 简明 简明 Python 教程是Swaroop C.H. 教授为Python初学者写的一本书。…