深度刨析Android ANR触发原理

2024-03-19 15:52

本文主要是介绍深度刨析Android ANR触发原理,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

一、概述

ANR(Application Not responding),是指应用程序未响应,Android系统对于一些事件需要在一定的时间范围内完成,如果超过预定时间能未能得到有效响应或者响应时间过长,都会造成ANR。一般地,这时往往会弹出一个提示框,告知用户当前xxx未响应,用户可选择继续等待或者Force Close。

那么哪些场景会造成ANR呢?

  • Service Timeout:比如前台服务在20s内未执行完成;
  • BroadcastQueue Timeout:比如前台广播在10s内未执行完成
  • ContentProvider Timeout:内容提供者,在publish过超时10s;
  • InputDispatching Timeout: 输入事件分发超时5s,包括按键和触摸事件。

触发ANR的过程可分为三个步骤: 埋炸弹, 拆炸弹, 引爆炸弹

二 Service

Service Timeout是位于”ActivityManager”线程中的AMS.MainHandler收到SERVICE_TIMEOUT_MSG消息时触发。

对于Service有两类:

  • 对于前台服务,则超时为SERVICE_TIMEOUT = 20s;
  • 对于后台服务,则超时为SERVICE_BACKGROUND_TIMEOUT = 200s

由变量ProcessRecord.execServicesFg来决定是否前台启动

2.1 埋雷

Service进程attach到system_server进程的过程中会调用realStartServiceLocked()方法来埋下炸弹.

2.1.1 AS.realStartServiceLocked

[-> ActiveServices.java]

private final void realStartServiceLocked(ServiceRecord r, ProcessRecord app, boolean execInFg) throws RemoteException {...//发送delay消息(SERVICE_TIMEOUT_MSG),【见小节2.1.2】bumpServiceExecutingLocked(r, execInFg, "create");try {...//最终执行服务的onCreate()方法app.thread.scheduleCreateService(r, r.serviceInfo,mAm.compatibilityInfoForPackageLocked(r.serviceInfo.applicationInfo),app.repProcState);} catch (DeadObjectException e) {mAm.appDiedLocked(app);throw e;} finally {...}
}
2.1.2 AS.bumpServiceExecutingLocked
private final void bumpServiceExecutingLocked(ServiceRecord r, boolean fg, String why) {... scheduleServiceTimeoutLocked(r.app);
}void scheduleServiceTimeoutLocked(ProcessRecord proc) {if (proc.executingServices.size() == 0 || proc.thread == null) {return;}long now = SystemClock.uptimeMillis();Message msg = mAm.mHandler.obtainMessage(ActivityManagerService.SERVICE_TIMEOUT_MSG);msg.obj = proc;//当超时后仍没有remove该SERVICE_TIMEOUT_MSG消息,则执行service Timeout流程【见2.3.1】mAm.mHandler.sendMessageAtTime(msg,proc.execServicesFg ? (now+SERVICE_TIMEOUT) : (now+ SERVICE_BACKGROUND_TIMEOUT));
}

该方法的主要工作发送delay消息(SERVICE_TIMEOUT_MSG). 炸弹已埋下, 我们并不希望炸弹被引爆, 那么就需要在炸弹爆炸之前拆除炸弹.

2.2 拆雷

在system_server进程AS.realStartServiceLocked()调用的过程会埋下一颗炸弹, 超时没有启动完成则会爆炸. 那么什么时候会拆除这颗炸弹的引线呢? 经过Binder等层层调用进入目标进程的主线程handleCreateService()的过程.

2.2.1 AT.handleCreateService

[-> ActivityThread.java]

    private void handleCreateService(CreateServiceData data) {...java.lang.ClassLoader cl = packageInfo.getClassLoader();Service service = (Service) cl.loadClass(data.info.name).newInstance();...try {//创建ContextImpl对象ContextImpl context = ContextImpl.createAppContext(this, packageInfo);context.setOuterContext(service);//创建Application对象Application app = packageInfo.makeApplication(false, mInstrumentation);service.attach(context, this, data.info.name, data.token, app,ActivityManagerNative.getDefault());//调用服务onCreate()方法 service.onCreate();//拆除炸弹引线[见小节2.2.2]ActivityManagerNative.getDefault().serviceDoneExecuting(data.token, SERVICE_DONE_EXECUTING_ANON, 0, 0);} catch (Exception e) {...}}

在这个过程会创建目标服务对象,以及回调onCreate()方法, 紧接再次经过多次调用回到system_server来执行serviceDoneExecuting.

2.2.2 AS.serviceDoneExecutingLocked
private void serviceDoneExecutingLocked(ServiceRecord r, boolean inDestroying, boolean finishing) {...if (r.executeNesting <= 0) {if (r.app != null) {r.app.execServicesFg = false;r.app.executingServices.remove(r);if (r.app.executingServices.size() == 0) {//当前服务所在进程中没有正在执行的servicemAm.mHandler.removeMessages(ActivityManagerService.SERVICE_TIMEOUT_MSG, r.app);...}...
}

该方法的主要工作是当service启动完成,则移除服务超时消息SERVICE_TIMEOUT_MSG

2.3 引爆

前面介绍了埋炸弹和拆炸弹的过程, 如果在炸弹倒计时结束之前成功拆卸炸弹,那么就没有爆炸的机会, 但是世事难料. 总有些极端情况下无法即时拆除炸弹,导致炸弹爆炸, 其结果就是App发生ANR. 接下来,带大家来看看炸弹爆炸的现场:

在system_server进程中有一个Handler线程, 名叫”ActivityManager”.当倒计时结束便会向该Handler线程发送 一条信息SERVICE_TIMEOUT_MSG,

2.3.1 MainHandler.handleMessage

[-> ActivityManagerService.java ::MainHandler]

final class MainHandler extends Handler {public void handleMessage(Message msg) {switch (msg.what) {case SERVICE_TIMEOUT_MSG: {...//【见小节2.3.2】mServices.serviceTimeout((ProcessRecord)msg.obj);} break;...}...}
}
2.3.2 AS.serviceTimeout
void serviceTimeout(ProcessRecord proc) {String anrMessage = null;synchronized(mAm) {if (proc.executingServices.size() == 0 || proc.thread == null) {return;}final long now = SystemClock.uptimeMillis();final long maxTime =  now -(proc.execServicesFg ? SERVICE_TIMEOUT : SERVICE_BACKGROUND_TIMEOUT);ServiceRecord timeout = null;long nextTime = 0;for (int i=proc.executingServices.size()-1; i>=0; i--) {ServiceRecord sr = proc.executingServices.valueAt(i);if (sr.executingStart < maxTime) {timeout = sr;break;}if (sr.executingStart > nextTime) {nextTime = sr.executingStart;}}if (timeout != null && mAm.mLruProcesses.contains(proc)) {Slog.w(TAG, "Timeout executing service: " + timeout);StringWriter sw = new StringWriter();PrintWriter pw = new FastPrintWriter(sw, false, 1024);pw.println(timeout);timeout.dump(pw, " ");pw.close();mLastAnrDump = sw.toString();mAm.mHandler.removeCallbacks(mLastAnrDumpClearer);mAm.mHandler.postDelayed(mLastAnrDumpClearer, LAST_ANR_LIFETIME_DURATION_MSECS);anrMessage = "executing service " + timeout.shortName;}}if (anrMessage != null) {//当存在timeout的service,则执行appNotRespondingmAm.appNotResponding(proc, null, null, false, anrMessage);}
}

其中anrMessage的内容为”executing service [发送超时serviceRecord信息]”;

三 BroadcastReceiver

BroadcastReceiver Timeout是位于”ActivityManager”线程中的BroadcastQueue.BroadcastHandler收到BROADCAST_TIMEOUT_MSG消息时触发。

对于广播队列有两个: foreground队列和background队列:

  • 对于前台广播,则超时为BROADCAST_FG_TIMEOUT = 10s;
  • 对于后台广播,则超时为BROADCAST_BG_TIMEOUT = 60s

3.1 埋雷

广播启动通过调用 processNextBroadcast来处理广播.其流程为先处理并行广播,再处理当前有序广播,最后获取并处理下条有序广播.

3.1.1 processNextBroadcast

[-> BroadcastQueue.java]

final void processNextBroadcast(boolean fromMsg) {synchronized(mService) {...//part 2: 处理当前有序广播do {r = mOrderedBroadcasts.get(0);//获取所有该广播所有的接收者int numReceivers = (r.receivers != null) ? r.receivers.size() : 0;if (mService.mProcessesReady && r.dispatchTime > 0) {long now = SystemClock.uptimeMillis();if ((numReceivers > 0) &&(now > r.dispatchTime + (2*mTimeoutPeriod*numReceivers))) {//当广播处理时间超时,则强制结束这条广播【见小节3.3.2】broadcastTimeoutLocked(false);...}}if (r.receivers == null || r.nextReceiver >= numReceivers|| r.resultAbort || forceReceive) {if (r.resultTo != null) {//处理广播消息消息performReceiveLocked(r.callerApp, r.resultTo,new Intent(r.intent), r.resultCode,r.resultData, r.resultExtras, false, false, r.userId);r.resultTo = null;}//拆炸弹【见小节3.2.2】cancelBroadcastTimeoutLocked();}} while (r == null);...//part 3: 获取下条有序广播r.receiverTime = SystemClock.uptimeMillis();if (!mPendingBroadcastTimeoutMessage) {long timeoutTime = r.receiverTime + mTimeoutPeriod;//埋炸弹【见小节3.1.2】setBroadcastTimeoutLocked(timeoutTime);}...}
}

对于广播超时处理时机:

  1. 首先在part3的过程中setBroadcastTimeoutLocked(timeoutTime) 设置超时广播消息;
  2. 然后在part2根据广播处理情况来处理:
    • 当广播接收者等待时间过长,则调用broadcastTimeoutLocked(false);
    • 当执行完广播,则调用cancelBroadcastTimeoutLocked;
3.1.2 setBroadcastTimeoutLocked
final void setBroadcastTimeoutLocked(long timeoutTime) {if (! mPendingBroadcastTimeoutMessage) {Message msg = mHandler.obtainMessage(BROADCAST_TIMEOUT_MSG, this);mHandler.sendMessageAtTime(msg, timeoutTime);mPendingBroadcastTimeoutMessage = true;}
}

设置定时广播BROADCAST_TIMEOUT_MSG,即当前往后推mTimeoutPeriod时间广播还没处理完毕,则进入广播超时流程。

3.2 拆雷

broadcast跟service超时机制大抵相同,但有一个非常隐蔽的技能点,那就是通过静态注册的广播超时会受SharedPreferences(简称SP)的影响。

3.2.1 sendFinished

关于广播是否考虑SP的情况取决于如下代码:

public final void finish() {if (mType == TYPE_COMPONENT) {final IActivityManager mgr = ActivityManager.getService();if (QueuedWork.hasPendingWork()) {//当SP有未同步到磁盘的工作,则需等待其完成,才告知系统已完成该广播QueuedWork.queue(new Runnable() {public void run() {sendFinished(mgr);}}, false);} else {sendFinished(mgr);}} else if (mOrderedHint && mType != TYPE_UNREGISTERED) {final IActivityManager mgr = ActivityManager.getService();sendFinished(mgr);}
}

可见,只有XML静态注册的广播超时检测过程会考虑是否有SP尚未完成,动态广播并不受其影响。

3.2.2 cancelBroadcastTimeoutLocked
final void cancelBroadcastTimeoutLocked() {if (mPendingBroadcastTimeoutMessage) {mHandler.removeMessages(BROADCAST_TIMEOUT_MSG, this);mPendingBroadcastTimeoutMessage = false;}
}

移除广播超时消息BROADCAST_TIMEOUT_MSG

3.3 引爆

3.3.1 BroadcastHandler.handleMessage

[-> BroadcastQueue.java ::BroadcastHandler]

private final class BroadcastHandler extends Handler {public void handleMessage(Message msg) {switch (msg.what) {case BROADCAST_TIMEOUT_MSG: {synchronized (mService) {//【见小节3.3.2】broadcastTimeoutLocked(true);}} break;...}...}
}
3.3.2 broadcastTimeoutLocked

[-> BroadcastRecord.java]

//fromMsg = true
final void broadcastTimeoutLocked(boolean fromMsg) {if (fromMsg) {mPendingBroadcastTimeoutMessage = false;}if (mOrderedBroadcasts.size() == 0) {return;}long now = SystemClock.uptimeMillis();BroadcastRecord r = mOrderedBroadcasts.get(0);if (fromMsg) {if (mService.mDidDexOpt) {mService.mDidDexOpt = false;long timeoutTime = SystemClock.uptimeMillis() + mTimeoutPeriod;setBroadcastTimeoutLocked(timeoutTime);return;}if (!mService.mProcessesReady) {return; //当系统还没有准备就绪时,广播处理流程中不存在广播超时}long timeoutTime = r.receiverTime + mTimeoutPeriod;if (timeoutTime > now) {//如果当前正在执行的receiver没有超时,则重新设置广播超时setBroadcastTimeoutLocked(timeoutTime);return;}}BroadcastRecord br = mOrderedBroadcasts.get(0);if (br.state == BroadcastRecord.WAITING_SERVICES) {//广播已经处理完成,但需要等待已启动service执行完成。当等待足够时间,则处理下一条广播。br.curComponent = null;br.state = BroadcastRecord.IDLE;processNextBroadcast(false);return;}r.receiverTime = now;//当前BroadcastRecord的anr次数执行加1操作r.anrCount++;if (r.nextReceiver <= 0) {return;}...Object curReceiver = r.receivers.get(r.nextReceiver-1);//查询App进程if (curReceiver instanceof BroadcastFilter) {BroadcastFilter bf = (BroadcastFilter)curReceiver;if (bf.receiverList.pid != 0&& bf.receiverList.pid != ActivityManagerService.MY_PID) {synchronized (mService.mPidsSelfLocked) {app = mService.mPidsSelfLocked.get(bf.receiverList.pid);}}} else {app = r.curApp;}if (app != null) {anrMessage = "Broadcast of " + r.intent.toString();}if (mPendingBroadcast == r) {mPendingBroadcast = null;}//继续移动到下一个广播接收者finishReceiverLocked(r, r.resultCode, r.resultData,r.resultExtras, r.resultAbort, false);scheduleBroadcastsLocked();if (anrMessage != null) {// [见小节3.3.3]mHandler.post(new AppNotResponding(app, anrMessage));}
}
  1. mOrderedBroadcasts已处理完成,则不会anr;
  2. 正在执行dexopt,则不会anr;
  3. 系统还没有进入ready状态(mProcessesReady=false),则不会anr;
  4. 如果当前正在执行的receiver没有超时,则重新设置广播超时,不会anr;
3.3.3 AppNotResponding

[-> BroadcastQueue.java]

private final class AppNotResponding implements Runnable {...public void run() {// 进入ANR处理流程mService.appNotResponding(mApp, null, null, false, mAnnotation);}
}

四 ContentProvider

ContentProvider Timeout是位于”ActivityManager”线程中的AMS.MainHandler收到CONTENT_PROVIDER_PUBLISH_TIMEOUT_MSG消息时触发。

ContentProvider 超时为CONTENT_PROVIDER_PUBLISH_TIMEOUT = 10s. 这个跟前面的Service和BroadcastQueue完全不同, 由Provider进程启动过程相关.

4.1 埋雷

 埋炸弹的过程 其实是在进程创建的过程,进程创建后会调用attachApplicationLocked()进入system_server进程.

4.1.1 AMS.attachApplicationLocked
private final boolean attachApplicationLocked(IApplicationThread thread, int pid) {ProcessRecord app;if (pid != MY_PID && pid >= 0) {synchronized (mPidsSelfLocked) {app = mPidsSelfLocked.get(pid); // 根据pid获取ProcessRecord}} ...//系统处于ready状态或者该app为FLAG_PERSISTENT进程则为trueboolean normalMode = mProcessesReady || isAllowedWhileBooting(app.info);List<ProviderInfo> providers = normalMode ? generateApplicationProvidersLocked(app) : null;//app进程存在正在启动中的provider,则超时10s后发送CONTENT_PROVIDER_PUBLISH_TIMEOUT_MSG消息if (providers != null && checkAppInLaunchingProvidersLocked(app)) {Message msg = mHandler.obtainMessage(CONTENT_PROVIDER_PUBLISH_TIMEOUT_MSG);msg.obj = app;mHandler.sendMessageDelayed(msg, CONTENT_PROVIDER_PUBLISH_TIMEOUT);}thread.bindApplication(...);...
}

10s之后引爆该炸弹

4.2 拆雷

当provider成功publish之后,便会拆除该炸弹.

4.2.1 AMS.publishContentProviders
public final void publishContentProviders(IApplicationThread caller, List<ContentProviderHolder> providers) {...synchronized (this) {final ProcessRecord r = getRecordForAppLocked(caller);final int N = providers.size();for (int i = 0; i < N; i++) {ContentProviderHolder src = providers.get(i);...ContentProviderRecord dst = r.pubProviders.get(src.info.name);if (dst != null) {ComponentName comp = new ComponentName(dst.info.packageName, dst.info.name);mProviderMap.putProviderByClass(comp, dst); //将该provider添加到mProviderMapString names[] = dst.info.authority.split(";");for (int j = 0; j < names.length; j++) {mProviderMap.putProviderByName(names[j], dst);}int launchingCount = mLaunchingProviders.size();int j;boolean wasInLaunchingProviders = false;for (j = 0; j < launchingCount; j++) {if (mLaunchingProviders.get(j) == dst) {//将该provider移除mLaunchingProviders队列mLaunchingProviders.remove(j);wasInLaunchingProviders = true;j--;launchingCount--;}}//成功pubish则移除该消息if (wasInLaunchingProviders) {mHandler.removeMessages(CONTENT_PROVIDER_PUBLISH_TIMEOUT_MSG, r);}synchronized (dst) {dst.provider = src.provider;dst.proc = r;//唤醒客户端的wait等待方法dst.notifyAll();}...}}}    
}

4.3 引爆

在system_server进程中有一个Handler线程, 名叫”ActivityManager”.当倒计时结束便会向该Handler线程发送 一条信息CONTENT_PROVIDER_PUBLISH_TIMEOUT_MSG,

4.3.1 MainHandler.handleMessage

[-> ActivityManagerService.java ::MainHandler]

final class MainHandler extends Handler {public void handleMessage(Message msg) {switch (msg.what) {case CONTENT_PROVIDER_PUBLISH_TIMEOUT_MSG: {...ProcessRecord app = (ProcessRecord)msg.obj;synchronized (ActivityManagerService.this) {//【见小节4.3.2】processContentProviderPublishTimedOutLocked(app);}} break;...}...}
}
4.3.2 AMS.processContentProviderPublishTimedOutLocked
private final void processContentProviderPublishTimedOutLocked(ProcessRecord app) {//[见4.3.3]cleanupAppInLaunchingProvidersLocked(app, true); //[见小节4.3.4]removeProcessLocked(app, false, true, "timeout publishing content providers");
}
4.3.3 AMS.cleanupAppInLaunchingProvidersLocked
boolean cleanupAppInLaunchingProvidersLocked(ProcessRecord app, boolean alwaysBad) {boolean restart = false;for (int i = mLaunchingProviders.size() - 1; i >= 0; i--) {ContentProviderRecord cpr = mLaunchingProviders.get(i);if (cpr.launchingApp == app) {if (!alwaysBad && !app.bad && cpr.hasConnectionOrHandle()) {restart = true;} else {//移除死亡的providerremoveDyingProviderLocked(app, cpr, true);}}}return restart;
}

removeDyingProviderLocked()的功能跟进程的存活息息相关:详见ContentProvider引用计数 []小节4.5]

  • 对于stable类型的provider(即conn.stableCount > 0),则会杀掉所有跟该provider建立stable连接的非persistent进程.
  • 对于unstable类的provider(即conn.unstableCount > 0),并不会导致client进程被级联所杀.
4.3.4 AMS.removeProcessLocked
private final boolean removeProcessLocked(ProcessRecord app, boolean callerWillRestart, boolean allowRestart, String reason) {final String name = app.processName;final int uid = app.uid;//移除mProcessNames中的相应对象removeProcessNameLocked(name, uid);if (mHeavyWeightProcess == app) {mHandler.sendMessage(mHandler.obtainMessage(CANCEL_HEAVY_NOTIFICATION_MSG,mHeavyWeightProcess.userId, 0));mHeavyWeightProcess = null;}boolean needRestart = false;if (app.pid > 0 && app.pid != MY_PID) {int pid = app.pid;synchronized (mPidsSelfLocked) {mPidsSelfLocked.remove(pid);mHandler.removeMessages(PROC_START_TIMEOUT_MSG, app);}...boolean willRestart = false;if (app.persistent && !app.isolated) {if (!callerWillRestart) {willRestart = true;} else {needRestart = true;}}app.kill(reason, true); //杀进程handleAppDiedLocked(app, willRestart, allowRestart);if (willRestart) {removeLruProcessLocked(app);addAppLocked(app.info, false, null /* ABI override */);}} else {mRemovedProcesses.add(app);}return needRestart;
}

五、总结

当出现ANR时,都是调用到AMS.appNotResponding()方法手机信息

Timeout时长
  • 对于前台服务,则超时为SERVICE_TIMEOUT = 20s;
  • 对于后台服务,则超时为SERVICE_BACKGROUND_TIMEOUT = 200s
  • 对于前台广播,则超时为BROADCAST_FG_TIMEOUT = 10s;
  • 对于后台广播,则超时为BROADCAST_BG_TIMEOUT = 60s;
  • ContentProvider超时为CONTENT_PROVIDER_PUBLISH_TIMEOUT = 10s;
超时检测

Service超时检测机制:

  • 超过一定时间没有执行完相应操作来触发移除延时消息,则会触发anr;

BroadcastReceiver超时检测机制:

  • 有序广播的总执行时间超过 2* receiver个数 * timeout时长,则会触发anr;
  • 有序广播的某一个receiver执行过程超过 timeout时长,则会触发anr;

另外:

  • 对于Service, Broadcast, Input发生ANR之后,最终都会调用AMS.appNotResponding;
  • 对于provider,在其进程启动时publish过程可能会出现ANR, 则会直接杀进程以及清理相应信息,而不会弹出ANR的对话框. appNotRespondingViaProvider()过程会走appNotResponding(), 这个就不介绍了,很少使用,由用户自定义超时时间.

这篇关于深度刨析Android ANR触发原理的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/826501

相关文章

深入探索协同过滤:从原理到推荐模块案例

文章目录 前言一、协同过滤1. 基于用户的协同过滤(UserCF)2. 基于物品的协同过滤(ItemCF)3. 相似度计算方法 二、相似度计算方法1. 欧氏距离2. 皮尔逊相关系数3. 杰卡德相似系数4. 余弦相似度 三、推荐模块案例1.基于文章的协同过滤推荐功能2.基于用户的协同过滤推荐功能 前言     在信息过载的时代,推荐系统成为连接用户与内容的桥梁。本文聚焦于

hdu4407(容斥原理)

题意:给一串数字1,2,......n,两个操作:1、修改第k个数字,2、查询区间[l,r]中与n互质的数之和。 解题思路:咱一看,像线段树,但是如果用线段树做,那么每个区间一定要记录所有的素因子,这样会超内存。然后我就做不来了。后来看了题解,原来是用容斥原理来做的。还记得这道题目吗?求区间[1,r]中与p互质的数的个数,如果不会的话就先去做那题吧。现在这题是求区间[l,r]中与n互质的数的和

Android实现任意版本设置默认的锁屏壁纸和桌面壁纸(两张壁纸可不一致)

客户有些需求需要设置默认壁纸和锁屏壁纸  在默认情况下 这两个壁纸是相同的  如果需要默认的锁屏壁纸和桌面壁纸不一样 需要额外修改 Android13实现 替换默认桌面壁纸: 将图片文件替换frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.*  (注意不能是bmp格式) 替换默认锁屏壁纸: 将图片资源放入vendo

Android平台播放RTSP流的几种方案探究(VLC VS ExoPlayer VS SmartPlayer)

技术背景 好多开发者需要遴选Android平台RTSP直播播放器的时候,不知道如何选的好,本文针对常用的方案,做个大概的说明: 1. 使用VLC for Android VLC Media Player(VLC多媒体播放器),最初命名为VideoLAN客户端,是VideoLAN品牌产品,是VideoLAN计划的多媒体播放器。它支持众多音频与视频解码器及文件格式,并支持DVD影音光盘,VCD影

基于UE5和ROS2的激光雷达+深度RGBD相机小车的仿真指南(五):Blender锥桶建模

前言 本系列教程旨在使用UE5配置一个具备激光雷达+深度摄像机的仿真小车,并使用通过跨平台的方式进行ROS2和UE5仿真的通讯,达到小车自主导航的目的。本教程默认有ROS2导航及其gazebo仿真相关方面基础,Nav2相关的学习教程可以参考本人的其他博客Nav2代价地图实现和原理–Nav2源码解读之CostMap2D(上)-CSDN博客往期教程: 第一期:基于UE5和ROS2的激光雷达+深度RG

韦季李输入法_输入法和鼠标的深度融合

在数字化输入的新纪元,传统键盘输入方式正悄然进化。以往,面对实体键盘,我们常需目光游离于屏幕与键盘之间,以确认指尖下的精准位置。而屏幕键盘虽直观可见,却常因占据屏幕空间,迫使我们在操作与视野间做出妥协,频繁调整布局以兼顾输入与界面浏览。 幸而,韦季李输入法的横空出世,彻底颠覆了这一现状。它不仅对输入界面进行了革命性的重构,更巧妙地将鼠标这一传统外设融入其中,开创了一种前所未有的交互体验。 想象

android-opencv-jni

//------------------start opencv--------------------@Override public void onResume(){ super.onResume(); //通过OpenCV引擎服务加载并初始化OpenCV类库,所谓OpenCV引擎服务即是 //OpenCV_2.4.3.2_Manager_2.4_*.apk程序包,存

hdu4407容斥原理

题意: 有一个元素为 1~n 的数列{An},有2种操作(1000次): 1、求某段区间 [a,b] 中与 p 互质的数的和。 2、将数列中某个位置元素的值改变。 import java.io.BufferedInputStream;import java.io.BufferedReader;import java.io.IOException;import java.io.Inpu

hdu4059容斥原理

求1-n中与n互质的数的4次方之和 import java.io.BufferedInputStream;import java.io.BufferedReader;import java.io.IOException;import java.io.InputStream;import java.io.InputStreamReader;import java.io.PrintWrit

从状态管理到性能优化:全面解析 Android Compose

文章目录 引言一、Android Compose基本概念1.1 什么是Android Compose?1.2 Compose的优势1.3 如何在项目中使用Compose 二、Compose中的状态管理2.1 状态管理的重要性2.2 Compose中的状态和数据流2.3 使用State和MutableState处理状态2.4 通过ViewModel进行状态管理 三、Compose中的列表和滚动