安卓项目如何做单元测试

article/2025/10/9 8:27:55

前言

先说一下创建篇文章的目的,近期负责搭建公司的单元测试框架,于是查阅了网上的很多文章,以及参考了github上很多的项目例子,并且也进行了相当多的尝试。这其中花费了很多的精力,大约有两三周的时间,远远超乎我的预期。这其中有很多原因,比如安卓的单元测试项目依赖性太高,相关文章太老旧,github上项目复杂度不够等等。所以写一篇安卓如何去做单元测试的文章,来分享给有同样需求的小伙伴,避免踩到我踩过的坑。
另外就是很多人都把单元测试和功能测试搞混了,往往把单元测试做成了验证某些功能。一开始,我也被这样的问题所误导了,甚至于某些互联网大厂文章中讲解的单元测试,也是基于功能来验证的。
对于简单的项目,基于功能来验证确实可以实现,但是如果项目比较负责,整个功能流程很长的话,就会发现这样做的弊端:
1.需要梳理整个流程的功能,功能点多,单元测试代码就会很长,而且整个流程的相关依赖都需要mock。
2.耦合严重,因为涉及到了很多环节,任意一个环节的改动,都会导致单元测试代码失效。
所以,单元测试,更正确的方式应该是针对方法的级别,而方法中的所有依赖项,就是需要我们通过mock来解决的。

一.单元测试介绍

安卓单元测试介绍

目前单元测试主要分为两大类:
1.基于JVM虚拟机的单元测试,对应项目中的test目录,缺点是不支持安卓的环境。
2.基于真机的单元测试,对应项目中的androidTest目录,由于跑在真机上,自然是支持安卓环境的。缺点是不方便自动化集成。

考虑到后面准备把单元测试搞成一种可验证的规范,所以最终决定把方向定在了基于JVM虚拟机的这种方式。因为后续我们打算,提交代码后触发jenkins检查,只有通过单元测试的代码才允许merge,而这种方式必须依赖自动化集成。

单元测试优势:

1.项目重构时。单元测试会帮助我更好的识别出影响点。
2.让项目结构更合理。如果项目结构不合理,单元测试会很难写。
3.说明功能。单元测试用例会比注释更加清晰,告诉我们一个方法实现了哪些功能。
4.寻找冗余的点。写单元测试的时候,会帮助我们发现一些项目中一些冗余的点,比如某个方法多调用了1次。
5.方法的客观评价。通过单元测试的测试方法行数,可以客观评价项目中的方法复杂度(方法复杂度不易过高,过高的复杂度建议拆成多个方法)。

单元测试初步调研:

由于没有安卓的环境,所以如果跑在JVM上,就需要主动去模拟安卓的各种环境,而目前能够满足mock安卓环境的框架中,最好的无疑就是Robolectric了,并且这也是官方推荐的框架。解决了安卓环境的的mock问题后,我们还要解决方法中依赖项的问题,这样的框架有mockito和powermock。
mockito:基于cglib的动态代理实现的,可以mock掉替换掉类中的方法,构造方法,静态方法。
powermock:mockito的增强版本,基于JDK动态代理的方式,所以可以实现任何的替换。但是缺点是需要替换原有的运行注解,会导致跑单测有问题,而且很久不维护了。
虽然powermock的功能更大的的强大,但是由于不怎么维护,而且其框架中的一些特性和Robolectric是冲突的导致跑单测会有一些问题,所以最终选择使用mockito来解决我们所遇到的mock的问题。

二.简单的单元测试例子

相信大多数的读者,之前对单元测试的经验并不多,所以如果直接上来就讲我们实际项目中的使用,效果一定不会很好。所以,我们先来通过一些最简单的例子来讲解一下单元测试的用法。
先讲解最基本的三个例子:
1.最基本的单元测试验证;
2.验证某个方法是否被执行;
3.模拟各种不同的输入值。

1.最基本的单元测试验证

public class UnitTestDemo {public int add(int a, int b) {return a + b;}
}

我们的验证点为:a+b=c
则单元测试代码如下:

public class UnitTestDemoTest {UnitTestDemo demo;@Beforepublic void init() {demo = new UnitTestDemo();}@Testpublic void testAdd() {int add = demo.add(3, 5);assertEquals(add, 8);}
}

2.验证某个方法是否被执行

public class UnitTestDemo {public ThirdPartySource source = new ThirdPartySource();public void showMessage() {source.thirdShowMessage();}
}

我们的验证点为:thirdShowMessage方法是否执行
则单元测试代码如下:

@RunWith(RobolectricTestRunner.class)
public class UnitTestDemoTest {UnitTestDemo demo;@Beforepublic void init() {demo = new UnitTestDemo();}@Testpublic void testShowMessage() {ThirdPartySource mockSource = Mockito.mock(ThirdPartySource.class);demo.source = mockSource;demo.showMessage();Mockito.verify(mockSource).thirdShowMessage();Mockito.verify(mockSource, times(1)).thirdShowMessage();}
}

3.模拟各种不同的输入值

public class UnitTestDemo {public ThirdPartySource source = new ThirdPartySource();public String getMessage() {int num = source.getNum();if (num < 10) {return "0";}if (num == 10) {return "10";}return "100";}
}

我们的验证点有三个:
1.输入小于10的数,返回值是否为字符串"0";
2.输入10,返回值是否为字符串"10";
3.输入大于10的数,返回值是否为字符串"100"。
则单元测试代码如下:

@RunWith(RobolectricTestRunner.class)
public class UnitTestDemoTest {UnitTestDemo demo;@Beforepublic void init() {demo = new UnitTestDemo();}@Testpublic void getMessage() {ThirdPartySource mockSource = Mockito.mock(ThirdPartySource.class);demo.source = mockSource;Mockito.when(mockSource.getNum()).thenReturn(1);String message = demo.getMessage();assertEquals("0", message);Mockito.when(mockSource.getNum()).thenReturn(10);message = demo.getMessage();assertEquals("10", message);Mockito.when(mockSource.getNum()).thenReturn(101);message = demo.getMessage();assertEquals("100", message);}
}

4.稍稍总结一下
整理一下上面的方法,以及其所对应的验证点,如下:

方法验证点
add输入两个参数之和是否等于目标值
showMessagethirdShowMessage方法是否执行
getMessage1.输入小于10的数,返回值是否为字符串"0";2.输入10,返回值是否为字符串"10",3.输入大于10的数,返回值是否为字符串"100"

三.基于现有项目做单元测试

有了对单元测试最基本的了解,我们就可以开始结合我们的实际项目,来讲解如何做单元测试了。
首先我们介绍一下基本的单元测试规范;
然后再讲一下gradle的配置问题,因为不同版本gradle版本的兼容问题会有很大问题。
最后,我们介绍下如何基于现有项目去做单元测试的。

3.1 单测规范

1.类对类,一一对应
如果需要对MainActivity写单元测试,则需要在test文件夹下,相同包名,创建MainActivityTest的单元测试类。
单元测试类的类名为:原类名+Test。
在这里插入图片描述

2.方法对方法,多对多
2.方法对方法。针对某个方法写代码单测,推荐使用一对一的场景。
比如针对updateErrorIv方法写单元测试,则单元测试的方法为:testIvErrorState()
当然,涉及到某些具体相关的业务,会出现多个方法对应一个单元测试方法的情况,这种情况也是完全可以的。

3.2 配置流程

1.gradle版本

gradle版本并不是强制要求,只是方便读者更方便的运行本项目,主要配置两块即可。
gradle版本设置为:6.7.1-all,tools版本甚至为4.2.2。

1.gradle-wrapper.properties中配置gradle-6.7.1-all版本。
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-all.zip

2.项目中build.gradle配置版本如下:

buildscript {dependencies {classpath "com.android.tools.build:gradle:4.2.2"}
}

2.依赖配置

app目录下的build.gradle下,配置如下依赖:

    testImplementation('junit:junit:4.13.2') {exclude group: 'org.hamcrest', module: 'hamcrest-core'}testImplementation "io.mockk:mockk:1.12.2"testImplementation "org.assertj:assertj-core:3.22.0"testImplementation "org.robolectric:robolectric:4.9.2"testImplementation('org.mockito:mockito-core:3.6.28') {exclude group: 'net.bytebuddy', module: 'byte-buddy'exclude group: 'net.bytebuddy', module: 'byte-buddy-agent'}testImplementation 'org.mockito:mockito-inline:5.2.0'testImplementation "androidx.test:core:1.3.0"testImplementation("org.hamcrest:hamcrest-core:1.3")testImplementation("org.assertj:assertj-core:2.6.0")testImplementation 'android.arch.core:core-testing:1.0.0-alpha3'testImplementation('org.bouncycastle:bcprov-jdk15on:1.65') {force = true}

3.创建单元测试类

创建单元测试类,@Before代表执行前的初始化操作。@Test代表执行单元测试操作。

@RunWith(RobolectricTestRunner.class)
public class MainActivityTest {@Beforepublic void init() {}@Testpublic void testAny() {}
}

3.3.单测样例介绍

MVP模式类型

页面主要功能介绍:
1.进入页面,注册自定义监听,退出页面,取消注册自定义监听。
2.首次进入或者回到页面的时候,请求数据并刷新页面,并且只请求一次。
3.展示fragment。
4.点击图标,会弹出dialog。

Activity单元测试介绍

相关代码:

public class MVPActivity extends FragmentActivity implements IMVPActivityContract.IMainActivityView, View.OnClickListener {MVPPresenter presenter;ImageView imgeView;TextView textDesc;boolean isFirst;AdapterListener listener;@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);initView();initListener();init();}private void initView() {imgeView = findViewById(R.id.image_view);textDesc = findViewById(R.id.text_desc);presenter = new MVPPresenter();imgeView.setOnClickListener(this);}private void initListener() {if (listener == null) {listener = new AdapterListener();DataAdapaterClient.getInstance().registerDataNotifyListener("key", listener);}}private void init() {new Handler().post(() -> {presenter.onAttach(this);//refresh Activity pagepresenter.requestInfo();//show fragmentMVPFragment fragment = new MVPFragment();getSupportFragmentManager().beginTransaction().replace(R.id.frame_layout, fragment).commitAllowingStateLoss();});}@Overrideprotected void onResume() {super.onResume();//If the first time,refresh dataif (isFirst) {presenter.requestInfo();} else {isFirst = true;}}@Overrideprotected void onDestroy() {super.onDestroy();if (listener != null) {DataAdapaterClient.getInstance().unRegisterDataNotifyListener(listener);}}@Overridepublic void refreshPage(boolean isShow, String message) {imgeView.setVisibility(isShow ? View.VISIBLE : View.GONE);textDesc.setText(message);}@Overridepublic void onClick(View v) {AlertDialog.Builder builder = new AlertDialog.Builder(this);builder.setTitle("title");builder.setMessage("this is detail message!");builder.create().show();}public AdapterListener getAdapterListener() {return listener;}public class AdapterListener implements DataAdapaterClient.DataChangedListener {@Overridepublic void onDataChanged(String var1, String var2) {//do somethind}}
}

页面代码逻辑梳理:
1.initView方法中,主要为初始化presenter,以及给成员变量中的view赋值;
2.initListener时,初始化listener,并且注册监听。onDestory时,取消注册;
3.init中,请求数据,并且注册fragment;
4.onResume中,非首次进入时要请求数据来刷新页面;
5.refreshPage中,根据所传数据,刷新当前页面;
6.onClick中,点击按钮,展示弹框。

汇总整理后,方法和验证点如下:
在这里插入图片描述
所以根据上面的汇总,我们最终的单元测试代码如下:
MVPActivityTest

presenter单元测试介绍

相关代码:

public class MVPPresenter implements IMVPActivityContract.IMainActivityPresenter {IMVPActivityContract.IMainActivityView mView;public void onAttach(IMVPActivityContract.IMainActivityView view) {this.mView = view;}@Overridepublic void requestInfo() {//请求数据,订阅,并显示Consumer<InfoModel> consumer = this::processInfoAndRefreshPage;Flowable<InfoModel> observable = DataSource.getInstance().getDataInfo();Disposable disposable = observable.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(consumer);}/*** action*/public void processInfoAndRefreshPage(InfoModel infoModel) {int status = infoModel.status;String statusDesc = infoModel.statusDesc;mView.refreshPage(status != 200, statusDesc);}
}

presenter逻辑梳理:
1.onAttach方法进行页面绑定,验证点为:mView不为空。
2.requestInfo方法发起请求,返回值会调用processInfoAndRefreshPage方法进行刷新。
3.processInfoAndRefreshPage方法根据传参不同,执行不同的逻辑判断,然后刷新界面。

汇总整理后,方法和验证点如下:
在这里插入图片描述
所以根据上面的汇总,我们最终的单元测试代码如下:
MVPPresenterTest

四.项目常见问题解决

实际运行项目的时候,往往会遇到各种各样的问题。下面,我们列举一些经常遇到的问题,来讲解下我们是如何解决的。

4.1常见问题汇总

1.类中引用SO问题

因为单元测试基于JVM虚拟机,执行的是java的流程,并不会执行相关的安卓打包流程,所以引用的SO文件不会打包进最终产物当中,因此,直接运行项目,会提示SO找不到。
所以,更合适的方式是把相关的类进行mock替换掉。

如果遇到业务使用到了SO文件,则需要进行对这个对象进行mock,解除依赖。
比如,我们Model层获取数据时,使用到了SO。

	public class MVPPresenter{@Overridepublic void requestInfo() {//请求数据,订阅,并显示Consumer<InfoModel> consumer = this::processInfoAndRefreshPage;Flowable<InfoModel> observable = DataSource.getInstance().getDataInfo();Disposable disposable = observable.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(consumer);}	}public class DataSource{public Flowable<InfoModel> getDataInfo() {//this us jniJava2CJNI java2CJNI = new Java2CJNI();Log.i("SoView", java2CJNI.java2C());return Flowable.create(emitter -> {InfoModel infoModel = new InfoModel();infoModel.status = 100;infoModel.statusDesc = "fail";emitter.onNext(infoModel);}, BackpressureStrategy.BUFFER);}}

则我们可以通过mock对象MVPPresenter或DataSource来实现,从而避免请求到真正的getDataInfo()方法。
相关单测代码如下:

    MVPPresenter mockPresenter = mock(MVPPresenter.class);Mockito.doAnswer(invocation -> {//do nothingreturn null;}).when(mockPresenter).requestInfo();

2.解决XML中View类引用SO问题

如果XML引用了某个View的类,并且这个类使用到了SO,则需要整体这个View类。
比如:SoView中使用到了SO文件。
则会产生如下报错,因为

Caused by: java.lang.UnsatisfiedLinkError: no Java2C in java.library.path: [/Users/xxxx/Library/Java/Extensions, /Library/Java/Extensions, /Network/Library/Java/Extensions, /System/Library/Java/Extensions, /usr/lib/java, .]at java.base/java.lang.ClassLoader.loadLibrary(ClassLoader.java:2670)

我们要整体替换掉这个使用SoView的类。
首先,创建SoViewMock类,参考:

@Implements(com.xt.unittestdemo.view.SoView.class)
public class SoViewMock extends ShadowView {@Beforepublic void setUp() {}@Implementationpublic void __constructor__(Context context) {__constructor__(context, null);}@Implementationpublic void __constructor__(Context context, AttributeSet attrs) {}@Implementationprotected void onAttachedToWindow() {}@Implementationprotected void onDetachedFromWindow() {}@Implementationpublic void stop() {}
}

其次,单元测试类中进行响应的配置,替换掉SoView类。相关代码如下:

@Config(shadows = {SoViewMock.class}, manifest = Config.NONE, sdk = Build.VERSION_CODES.P)
@RunWith(RobolectricTestRunner.class)
public class MVPActivityTest {

3.解决Activity中成员变量mock问题

我们Activity对象,一般要使用Robolectric构造的,而不能直接new,否则会不走Activity的生命周期。
但是Activity中的成员变量,则需要使用mock的,因为只有mock的才能进行执行次数以及其它相关的验证,所以,如何替换Activity中的成员变量,就是一个我们要解决的问题。
经过反复的尝试,最终发现了一个可行的方案,即MainActivity的onCreate()中经过handler转发后再使用成员变量进行相关操作,我们在观察到执行完onCreate()方法后,使用mock对象替换原始对象。

//原始类
public class MVPActivity extends Activity{protected void onCreate(@NonNull Bundle savedInstanceState) {new Handler().post(() -> {...使用成员变量进行相关操作presenter.requestInfo();};}
}//单元测试类
@RunWith(RobolectricTestRunner.class)
public class MVPActivityTest {@Testpublic void testMethod() {ActivityController<MVPActivity> controller = Robolectric.buildActivity(MVPActivity.class);MVPPresenter mockPresenter = mock(MVPPresenter.class);MVPActivity mainActivity = controller.create().start().get();//替换presenter操作mainActivity.presenter = mockPresenter;controller.postCreate(null).resume().visible().topActivityResumed(true);//避免主线程looper阻塞ShadowLooper shadowLooper = ShadowLooper.getShadowMainLooper();shadowLooper.runToEndOfTasks();//验证操作verify(mockPresenter, times(1)).requestInfo();}
}

这样,我们通过使用mock的presenter替换原有Activity中的presenter,从而方便我们对presenter中的相关方法进行验证,并且还不影响Activity的生命周期。

4.解决单例类的mock问题

虽然我们可以使用第一种的方案去mock整个单例类,但是这种mock的类,是不方便替换其中的方法。所以,对于单例类,我们可以使用替换getInstance方法的方式来进行替换。
这里以DataAdapaterClient为例,相关类结构如下:

public class DataAdapaterClient {Map<String, DataChangedListener> listenerMap = new HashMap<>();public static DataAdapaterClient getInstance() {return DataAdapaterClient.SingletonHolder.SINGLETON;}private static class SingletonHolder {private static final DataAdapaterClient SINGLETON = new DataAdapaterClient();private SingletonHolder() {}}}

我们可以通过mock生成DataAdapaterClient对象,然后通过hook掉静态方法getInstance(),实现每次返回的都是我们mock后的ataAdapaterClient对象。相关代码如下:

    DataAdapaterClient mockClient = mock(DataAdapaterClient.class);try (MockedStatic<DataAdapaterClient> ignored2 = mockStatic(DataAdapaterClient.class)) {when(DataAdapaterClient.getInstance()).thenReturn(mockClient);}}

5.解决异步的问题

比如我们presenter中,requestInfo方法中通过方法getDataInfo异步发送服务去请求。

    @Overridepublic void requestInfo() {//请求数据,订阅,并显示Consumer<InfoModel> consumer = this::processInfoAndRefreshPage;Flowable<InfoModel> observable = DataSource.getInstance().getDataInfo();Disposable disposable = observable.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(consumer);}

由于我们是mock的手段,并不会真的去发送请求,所以,我们需要mock掉getDataInfo的返回值或者回调。

    @Testpublic void testRequestInfo() {Flowable<InfoModel> noNetWorkFlowable = Flowable.create(emitter -> {InfoModel baseEntity = new InfoModel();InfoModel accountInfoEntity = new InfoModel();accountInfoEntity.status = 200;accountInfoEntity.statusDesc = "success";emitter.onNext(baseEntity);}, BackpressureStrategy.BUFFER);DataSource mock = mock(DataSource.class);try (MockedStatic<DataSource> ignored = mockStatic(DataSource.class)) {when(DataSource.getInstance()).thenReturn(mock);when(mock.getDataInfo()).thenReturn(noNetWorkFlowable);        presenter.requestInfo();...//进行相关验证}}

4.2 常用排查手段

1.使用GPT
2.百度/google
3.参照网上现有的项目:
比如:Anki-Android
以及本项目:RobolectricDemo

开源项目RobolectricDemo欢迎大家fork/PR,补充更多场景的单元测试场景和解决方案。

五.参考文档

地址介绍
https://github.com/ankidroid/Anki-Androidgithub上单测覆盖率较高的项目
https://github.com/mockito/mockitogithub上mockito项目
https://github.com/robolectric/robolectricgithub上robolectric项目
https://github.com/powermock/powermockgithub上powermock项目
https://developer.android.com/reference/androidx/test/core/app/ActivityScenario官方关于ActivityScenario的介绍ActivityScenario用于替代ActivityController

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

相关文章

android单元测试作用,关于 Android 单元测试

本文不会用各种高大上的理由试图去说服你写单元测试&#xff0c;只是描述笔者在单元测试这条路上一路走来的思考和简单的示例&#xff0c;如果顺便能让你觉得单元测试其实也没那么遥远、回头也在实际项目中尝试一下&#xff0c;估计就是本文最大的收获了。 一、提起单元测试&am…

分享基于安卓项目的单元测试总结

前言&#xff1a; 负责公司的单元测试体系的搭建&#xff0c;大约有一两个月的时间了&#xff0c;从最初的框架的调研&#xff0c;到中期全员的培训&#xff0c;以及后期对几十个项目单元测试的引入和推进&#xff0c;也算是对安卓的单元测试有了一些初步的收获以及一些新的认…

java 线程 js_js javascript 实现多线程

在讲之前&#xff0c;大家都知道js是基于单线程的&#xff0c;而这个线程就是浏览器的js引擎。 首先来看一下大家用的浏览器都具有那些线程吧。 假如我们要执行一些耗时的操作&#xff0c;比如加载一张很大的图片&#xff0c;我们可能需要一个进度条来让用户进行等待&#xff0…

js创建多线程

我们知道js是单线程的&#xff0c;如果把占用大量计算资源的代码&#xff0c;或者获取大量数据&#xff0c;耗时较多的请求分离出去&#xff0c;用一个单独的线程去处理&#xff0c;会不会提升页面的性能的&#xff0c;答案是肯定的&#xff0c;尤其是现在我们开发大多是单页应…

javascript-js实现多线程

在讲之前&#xff0c;大家都知道js是基于单线程的&#xff0c;而这个线程就是浏览器的js引擎。 首先来看一下大家用的浏览器都具有那些线程吧。 假如我们要执行一些耗时的操作&#xff0c;比如加载一张很大的图片&#xff0c;我们可能需要一个进度条来让用户进行等待&#xff…

html5多线程例子,javascript的单线程事件循环及多线程介绍

前言 其实我前面文章对于改变js的执行顺序及多线程都有相关介绍&#xff01;例如&#xff0c;我们可以用setTimeout(fn,0)改变代码执行循序&#xff0c;文章最后也提及了Event Loop(事件循环)。同时&#xff0c;js的Worker可以模拟实现多线程&#xff0c;我前面文章也有类似的应…

js 实现多线程

2019独角兽企业重金招聘Python工程师标准>>> 一、多线程理解 首先,我们要理解什么是多线程,百度百科上说:多线程(英语:multithreading),是指从软件或者硬件上实现多个线程并发执行的技术。具有多线程能力的计算机因有硬件支持而能够在同一时间执行多于一个线…

javascript多线程

一、什么是多线程 二、Concurrent.Thread.js <meta charset"utf-8" /> <script src"Concurrent.Thread.js"></script> <script src"https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script><style&…

js 多线程Worker

任务执行优先级&#xff0c;微任务&#xff08;Promise&#xff09;>宏任务&#xff08;定时器&#xff09;>线程任务&#xff08;Worker&#xff09; 多线程处理多个任务&#xff0c;这里假设5个任务循环了80亿&#xff0c;如果按之前js单线程执行是不是5*80亿&#xf…

Javascript的单线程与多线程

目录 一、浏览器的线程和进程 1.浏览器的线程 2.浏览器是多进程的 二、Javascript是单线程的 1.异步Ajax也是单线程的 2.setInterval和setTimeout本质上并不是多线程 三、Web Worker支持多线程 1.多线程间数据交互 2.Web Worker的兼容性 3.Web Worker的使用限制 3.1同…

JavaScript多线程编程

浏览器端JavaScript是以单线程的方式执行的&#xff0c;也就是说JavaScript和UI渲染占用同一个主线程&#xff0c;那就意味着&#xff0c;如果JavaScript进行高负载的数据处理&#xff0c;UI渲染就很有可能被阻断&#xff0c;浏览器就会出现卡顿&#xff0c;降低了用户体验。 …

JavaScript多线程初步学习

一、多线程理解 首先&#xff0c;我们要理解什么是多线程&#xff0c;百度百科上说&#xff1a;多线程&#xff08;英语&#xff1a;multithreading&#xff09;&#xff0c;是指从软件或者硬件上实现多个线程并发执行的技术。具有多线程能力的计算机因有硬件支持而能够在同一时…

JavaScript如何实现多线程?

今天看到一道面试题&#xff0c;问js如何实现多线程?下面来总结一下&#xff1a; 因为 JS是一种单线程语言&#xff0c;即使是一些异步的事件也是在JS的主线程上运行的。像setTimeout、ajax的异步请求&#xff0c;或者是dom元素的一些事件&#xff0c;都是在JS主线程执行的&a…

JS多线程

JS是多线程的吗&#xff1f; 多线程编程相信大家都很熟悉&#xff0c;比如在界面开发中&#xff0c;如果一个事件的响应需要较长时间&#xff0c;那么一般做法就是把事件处理程序写在另外一个线程中&#xff0c;在处理过程中&#xff0c;在界面上面显示类似进度条的元素。这样…

at24c16如何划分出多个读写区_mega32数组、内存以及AT24C16读写相关

主控&#xff1a;mega32 编译器&#xff1a;iar2.31E 这两天折腾一个模块程序&#xff0c;一个温度补偿参数&#xff0c;本来是72个字节&#xff0c;现在扩展了三倍&#xff0c;变成288个&#xff0c;然后各种问题出现了。 第一次修改时想当然&#xff0c;直接把两个用到的全局…

STC8H开发(十二): I2C驱动AT24C08,AT24C32系列EEPROM存储

目录 STC8H开发(一): 在Keil5中配置和使用FwLib_STC8封装库(图文详解)STC8H开发(二): 在Linux VSCode中配置和使用FwLib_STC8封装库(图文详解)STC8H开发(三): 基于FwLib_STC8的模数转换ADC介绍和演示用例说明STC8H开发(四): FwLib_STC8 封装库的介绍和使用注意事项STC8H开发(五…

STM32的硬件I2C与AT24C16

刚学STM32的时候就听闻STM32的硬件I2C存在重大bug&#xff0c;会导致运行卡死在等待ACK的过程中&#xff0c;所以一直以来对其避而远之&#xff0c;转而以模拟I2C取代之。最近这段时间一直在用STM32 CubeMX&#xff0c;图形化设置界面屡试不爽&#xff0c;连USB这种复杂外设都能…

STM32F030 硬件I2C驱动 AT24C16

网络上很多F1系列的ATC24的读写程序&#xff0c;但F0几乎没有。由于F0完全重写了I2C&#xff0c;所以以往的代码并不能直接使用&#xff0c;修改事件、接口上会浪费很多时间&#xff0c;特别是对于使用F0系列进行入门的新手。 在此十分感谢 畅学电子网 的对于AT24C16的资料&am…

EEPROM 之 AT24C16 - 备忘录

因为论坛里看到STM的I2C有点小bug&#xff0c;所以这里采用的是模拟I2C时序 【注】m0.6us表示的是这一段时间最小不能小于为0.6us&#xff0c;M0.6us表示的是这一段时间最大为0.6us 对AT24C16的操作有读和写&#xff0c;读又分为CURRENT ADDRESS READ、RANDOM READ、SEQUENTIAL…

S32K144:12.LPI2C驱动AT24C16

1.打开官方例程 2.修改引脚配置 3.时钟可按照实际情况修改&#xff0c;也可不用更改&#xff0c;本例时钟不做更改 4.配置LPI2C模块 设置从机地址&#xff1a;从机地址如下图所示&#xff0c;低三位表示为AT24C16的块地址&#xff0c;AT24C16将2KB的内存空间分为8个块&…