Android系统原理性问题分析 - 消息传递机制的分析(Looper 和 Handler)

news/2024/5/19 23:47:38/文章来源:https://blog.csdn.net/Xiaoma_Pedro/article/details/130546631

声明

  • 在Android系统中经常会遇到一些系统原理性的问题,在此专栏中集中来讨论下。
  • 比如:Android为了线程安全,不允许在UI线程外操作UI,很多时候做界面刷新都需要通过Handler来通知UI组件更新。
  • 此篇参考一些博客和书籍,不方便逐一列出,仅供学习、知识分享。

1. 消息传递机制概述

1.1 问题引入

在这里插入图片描述

1.2 执行流程

在这里插入图片描述

概念解释
UI线程Activity主线程,系统在创建UI线程的时候会初始化一个Looper对象,同时也会创建一个与其关联的MessageQueue;
Handler发送与处理信息,如果希望Handler正常工作,在当前线程中要有一个Looper对象
MessageHandler接收与处理的消息对象
MessageQueue消息队列,先进先出管理Message,在初始化Looper对象时会创建一个与之关联的MessageQueue
Looper每个线程只能够有一个Looper,管理MessageQueue,不断地从中取出Message分发给对应的Handler处理

  就应用程序而言,Android 系统中 Java 的应用程序和其他系统上相同,都是靠消息驱动来工作的,它们大致的工作原理如下:

  • 有一个消息队列,可以往这个消息队列中投递消息。
  • 有一个消息循环,不断从消息队列中取出消息,然后处理。

从图中可看出:

  • 事件源把待处理的消息加入到消息队列中,一般是加至队尾,一些优先级高的消息也可以加至队列头。事件源提交的消息可以是按键、触摸屏等物理事件产生的消息,也可以是系统或应用程序本身发出的请求消息。
  • 处理线程不断从消息队列头中取出消息并处理,事件源可以把优先级高的消息放到队列头,这样,优先级高的消息就会首先被处理。

在Android系统中,这些工作主要由 Looper 和 Handler 来实现。

  • Looper 类,用于封装消息循环,并且有一个消息队列。
  • Handler 类,有点像辅助类,它封装了消息投递、消息处理等接口。

1.3 Handler类中的相关方法

Methods解释
void handleMessage(Message msg)处理消息的方法,通常是用于被重写
sendEmptyMessage(int what)发送空消息
sendEmptyMessageDelayed(int what,long delayMillis)指定延时多少毫秒后发送空信息
sendMessage(Message msg)立即发送信息
sendMessageDelayed(Message msg)指定延时多少毫秒后发送信息
final boolean hasMessage(int what)检查消息队列中是否包含what属性为指定值的消息。如果是参数为(int what,Object object):除了判断what属性,还需要判断Object属性是否为指定对象的消息

1.4 示例

1.4.1 Handler写在Activity UI主线程中

示例目的:定时切换图片的程序,通过Timer定时器,定时修改ImageView显示的内容,从而形成帧动画。

在主线程中,因为系统已经初始化了一个Looper对象,所以我们直接创建Handler对象,就可以进行信息的发送与处理了!

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  xmlns:tools="http://schemas.android.com/tools"  android:id="@+id/RelativeLayout1"  android:layout_width="match_parent"  android:layout_height="match_parent"  android:gravity="center"  tools:context="com.jay.example.handlerdemo1.MainActivity" >  <ImageView  android:id="@+id/imgchange"  android:layout_width="wrap_content"  android:layout_height="wrap_content"  android:layout_alignParentLeft="true"  android:layout_alignParentTop="true" />  </RelativeLayout> 

MainActivity.java

public class MainActivity extends Activity {  //定义切换的图片的数组id  int imgids[] = new int[]{  R.drawable.s_1, R.drawable.s_2,R.drawable.s_3,  R.drawable.s_4,R.drawable.s_5,R.drawable.s_6,  R.drawable.s_7,R.drawable.s_8  };  int imgstart = 0;  final Handler myHandler = new Handler()  {  @Override  //重写handleMessage方法,根据msg中what的值判断是否执行后续操作  public void handleMessage(Message msg) {  if(msg.what == 0x123)  {  imgchange.setImageResource(imgids[imgstart++ % 8]);  }  }  };  @Override  protected void onCreate(Bundle savedInstanceState) {  super.onCreate(savedInstanceState);  setContentView(R.layout.activity_main);  final ImageView imgchange = (ImageView) findViewById(R.id.imgchange);  //使用定时器,每隔200毫秒让handler发送一个空信息  new Timer().schedule(new TimerTask() {            @Override  public void run() {  myHandler.sendEmptyMessage(0x123);  }  }, 0,200);  }  } 

1.4.2 Handler写在子线程中

如果是Handler写在了子线程中的话,我们就需要自己创建一个Looper对象了!创建的流程如下:

  1. 直接调用Looper.prepare()方法即可为当前线程创建Looper对象,而它的构造器会创建配套的MessageQueue
  2. 创建Handler对象,重写handleMessage( )方法就可以处理来自于其他线程的信息了
  3. 调用Looper.loop()方法启动Looper

示例目的:输入一个数,计算后通过Toast输出在这个范围内的所有质数

<LinearLayout  xmlns:android="http://schemas.android.com/apk/res/android"  android:layout_width="match_parent"  android:layout_height="match_parent"  android:orientation="vertical">  <EditText  android:id="@+id/etNum"  android:inputType="number"  android:layout_width="match_parent"  android:layout_height="wrap_content"  android:hint="请输入上限"/>  <Button  android:layout_width="match_parent"  android:layout_height="wrap_content"  android:onClick="cal"  android:text="计算"/>    
</LinearLayout>  

MainActivity.java

public class CalPrime extends Activity  
{  static final String UPPER_NUM = "upper";  EditText etNum;  CalThread calThread;  // 定义一个线程类  class CalThread extends Thread  {  public Handler mHandler;  public void run()  {  //调用prepare方法Looper.prepare();  mHandler = new Handler()  {  // 定义处理消息的方法  @Override  public void handleMessage(Message msg)  {  if(msg.what == 0x123)  {  int upper = msg.getData().getInt(UPPER_NUM);  List<Integer> nums = new ArrayList<Integer>();  // 计算从2开始、到upper的所有质数  outer:  for (int i = 2 ; i <= upper ; i++)  {  // 用i处于从2开始、到i的平方根的所有数  for (int j = 2 ; j <= Math.sqrt(i) ; j++)  {  // 如果可以整除,表明这个数不是质数  if(i != 2 && i % j == 0)  {  continue outer;  }  }  nums.add(i);  }  // 使用Toast显示统计出来的所有质数  Toast.makeText(CalPrime.this , nums.toString()  , Toast.LENGTH_LONG).show();  }  }  };//进入消息循环Looper.loop();  }  }  @Override  public void onCreate(Bundle savedInstanceState)  {  super.onCreate(savedInstanceState);  setContentView(R.layout.main);  etNum = (EditText)findViewById(R.id.etNum);  calThread = new CalThread();  // 启动新线程  calThread.start();  }  // 为按钮的点击事件提供事件处理函数  public void cal(View source)  {  // 创建消息  Message msg = new Message();  msg.what = 0x123;  Bundle bundle = new Bundle();  bundle.putInt(UPPER_NUM ,  Integer.parseInt(etNum.getText().toString()));  msg.setData(bundle);  // 向新线程中的Handler发送消息  calThread.mHandler.sendMessage(msg);  }  
} 

2. Looper 和 Handler 类分析

2.1 Looper类分析

  代码位置:frameworks/base/core/java/android/os/Looper.java,第一个调用函数是 Looper 的 prepare 函数。它会做什么工作呢?其代码如下所示:

	// sThreadLocal.get() will return null unless you've called prepare().static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();/** Initialize the current thread as a looper.* This gives you a chance to create handlers that then reference* this looper, before actually starting the loop. Be sure to call* {@link #loop()} after calling this method, and end it by calling* {@link #quit()}.*/public static void prepare() {prepare(true);}private static void prepare(boolean quitAllowed) {// 一个Looper只能调用一次prepareif (sThreadLocal.get() != null) {throw new RuntimeException("Only one Looper may be created per thread");}// 构造一个Looper对象,设置到调用线程的局部变量中。sThreadLocal.set(new Looper(quitAllowed));}

  ThreadLocal是 Java 中的线程局部变量类,全名应该是 Thread Local Variable(可参考JDK API文档)。它的实现和操作系统提供的线程本地存储(TLS)有关系。该类有两个关键函数(set/get 的结果都和调用这个函数的线程有关):

  • set:设置调用线程的局部变量。
  • get:获取调用线程的局部变量。

  根据上面的分析可知,prepare 会在调用线程的局部变量中设置一个 Looper 对象。这个调用线程就是 CalThread 的 run 线程。先看看 Looper 对象的构造如下所示:

    private Looper(boolean quitAllowed) {//构造一个消息队列mQueue = new MessageQueue(quitAllowed);//获取当前线程的Thread对象mThread = Thread.currentThread();}

  prepare 方法主要干了一件事:在调用 prepare 的线程中,设置了一个 Looper 对象,这个Looper 对象就保存在这个调用线程的 TLV 中。而 Looper 对象内部封装了一个消息队列。
  也就是说,prepare 函数通过 ThreadLocal 机制,巧妙地把 Looper 和调用线程关联在起了。

    /*** Run the message queue in this thread. Be sure to call* {@link #quit()} to end the loop.*/public static void loop() {final Looper me = myLooper();//myLooper返回保存在调用线程TLV中的Looper对象if (me == null) {throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");}//取出这个Looper的消息队列final MessageQueue queue = me.mQueue;// Make sure the identity of this thread is that of the local process,// and keep track of what that identity token actually is.Binder.clearCallingIdentity();final long ident = Binder.clearCallingIdentity();for (;;) {Message msg = queue.next(); // might block// 处理消息,Message 对象中有一个 target,它是 Handler 类型。// 如果 target 为空,则表示常要退出消息循环。if (msg == null) {// No message indicates that the message queue is quitting.return;}// This must be in a local variable, in case a UI event sets the loggerfinal Printer logging = me.mLogging;if (logging != null) {logging.println(">>>>> Dispatching to " + msg.target + " " +msg.callback + ": " + msg.what);}final long traceTag = me.mTraceTag;if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {Trace.traceBegin(traceTag, msg.target.getTraceName(msg));}try {//调用该消息的Handler,交给它的dispatchMessage函数处理msg.target.dispatchMessage(msg);} finally {if (traceTag != 0) {Trace.traceEnd(traceTag);}}if (logging != null) {logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);}// Make sure that during the course of dispatching the// identity of the thread wasn't corrupted.final long newIdent = Binder.clearCallingIdentity();if (ident != newIdent) {Log.wtf(TAG, "Thread identity changed from 0x"+ Long.toHexString(ident) + " to 0x"+ Long.toHexString(newIdent) + " while dispatching to "+ msg.target.getClass().getName() + " "+ msg.callback + " what=" + msg.what);}msg.recycleUnchecked();}}/*** Return the Looper object associated with the current thread.  Returns* null if the calling thread is not associated with a Looper.*/// 返回调用线程的线程局部变量,也就是存储在其中的Looper对象 public static @Nullable Looper myLooper() {return sThreadLocal.get();}

通过分析会发现,Looper 的作用是:

  • 封装了一个消息队列。
  • Looper的 prepare 函数把这个 Looper 和调用 prepare 的线程绑定在一起了。
  • 处理线程调用 loop 函数,处理来自该消息队列的消息。

  当事件源向这个 Looper 发送消息的时候,其实是把消息加到这个 Looper 的消息队列里了。那么,该消息就将由和 Looper 绑定的处理线程来处理。可事件源又是怎么向 Looper 消息队列添加消息的呢?

  Looper、Message 和 Handler 之间也存在暧昧关系:

  • Looper 中有一个Message 队列,里面存储的是一个个待处理的 Message。
  • Message 中有一个 Handler,这个 Handler 是用来处理 Message 的。

其中,Handler 类封装了很多琐碎的工作。下节分析下这个 Handler。

2.2 Handler类分析

2.2.1 Handler 构造函数

  代码位置:frameworks/base/core/java/android/os/Handler.java,Handler中包括的成员:

    final Looper mLooper;final MessageQueue mQueue;final Callback mCallback;final boolean mAsynchronous;IMessenger mMessenger;

  这几个成员变量是怎么使用的呢?这首先得分析 Handler 的构造函数。Handler 一共有四个构造函数,它们主要的区别是在对上面三个重要成员变量的初始化上。

    public Handler() {this(null, false);}public Handler(Callback callback) {this(callback, false);}public Handler(Looper looper) {this(looper, null, false);}public Handler(Looper looper, Callback callback) {this(looper, callback, false);}public Handler(Callback callback, boolean async) {if (FIND_POTENTIAL_LEAKS) {final Class<? extends Handler> klass = getClass();if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&(klass.getModifiers() & Modifier.STATIC) == 0) {Log.w(TAG, "The following Handler class should be static or leaks might occur: " +klass.getCanonicalName());}}//获得调用线程的LoopermLooper = Looper.myLooper();if (mLooper == null) {throw new RuntimeException("Can't create handler inside thread that has not called Looper.prepare()");}//得到Looper的消息队列mQueue = mLooper.mQueue;mCallback = callback;mAsynchronous = async;}public Handler(Looper looper, Callback callback, boolean async) {mLooper = looper;//外部传入一个Looper,是哪个线程的Looper不确定mQueue = looper.mQueue;mCallback = callback;mAsynchronous = async;}

在上述构造函数中,Handler 中的消息队列变量最终都会指向 Looper 的消息队列。

2.2.2 Handler 的真面目

  根据前面的分析可知,Handler 中的消息队列实际就是某个 Looper 的消息队列,那么Handler如此安排的目的何在? 在回答这个问题之前,我先来问一个问题:怎么往 Looper 的消息队列插入消息?

如果不知道 Handler,这里有一个很原始的方法可解决上面这个问题:

  • 调用 Looper 的 myQueue,它将返回消息队列对象 MessageQueue。
  • 构造一个 Message,填充它的成员,尤其是 target 变量。
  • 调用 MessageQueue 的 enqueueMessage,将消息插人消息队列。

  这种原始方法的确很麻烦,且极容易出错。但有了 Handler 后,我们的工作就变得异常简单了。Handler 更像一个辅助类,帮助我们简化编程的工作。

2.2.2.1 Handler 和 Message

  Handler 提供了一系列函数,帮助我们完成创建消息和插入消息队列的工作。这里只列举几个。要掌握详细的 API,则需要查看相关的文档。

	//查看消息队列中是否有详细码是what的消息public final boolean hasMessages(int what) {return mQueue.hasMessages(this, what, null);}//从消息队列中移除消息码是what的消息public final void removeMessages(int what) {mQueue.removeMessages(this, what, null);}//从Handler中创建一个消息码是what的消息public final Message obtainMessage(int what){return Message.obtain(this, what);}//发送一个消息,该消息添加到队尾public final boolean sendMessage(Message msg){return sendMessageDelayed(msg, 0);}//发送一个只填充了消息码的消息public final boolean sendEmptyMessage(int what){return sendEmptyMessageDelayed(what, 0);}//发送一个消息,该消息添加到队头,优先级最高public final boolean sendMessageAtFrontOfQueue(Message msg) {MessageQueue queue = mQueue;if (queue == null) {RuntimeException e = new RuntimeException(this + " sendMessageAtTime() called with no mQueue");Log.w("Looper", e.getMessage(), e);return false;}return enqueueMessage(queue, msg, 0);}

  看到上面这些函数我们可以预见,如果没有 Handler 的辅助,当我们自己操作 MessageQueue 的 enqueueMessage 时,得花费多大工夫。Handler 把 Message 的 target 设为自己,是因为 Handler 除了封装消息添加等功能外还封装了消息处理的接口。

2.2.2.1 Handler 的消息处理

  刚才,我们往 Looper 的消息队列中加入了一个消息,按照 Looper 的处理规则,它在获取消息后会调用 target 的 dispatchMessage 函数,再把这个消息派发给 Handler 处理。Handler在这块是如何处理消息的呢?

    /*** Handle system messages here.*/public void dispatchMessage(Message msg) {//如果Message本身有mCallback ,则直接交给Message的mCallback 处理if (msg.callback != null) {handleCallback(msg);} else {//如果本Handler设置了mCallback ,则交给mCallback 处理if (mCallback != null) {if (mCallback.handleMessage(msg)) {return;}}//最后交给子类处理handleMessage(msg);}}

dispatchMessage 定义了一套消息处理的优先级机制,它们分别是:

  • Message 如果自带了 callback 处理,则交给 callback 处理。
  • Handler 如果设置了全局的 mCallback,则交给 mCallback 处理。
  • 如果上述都没有,该消息则会被交给 Handler 子类实现的 handleMessage 来处理。当然,这需要从 Handler 派生并重载 handleMessage 函数。

在通常情况下,我们一般都是采用第三种方法,即在子类中通过重载 handleMessage 来完成处理工作的。

2.3 Looper和Handler的同步关系

Looper 和 Handler 会有什么同步关系呢?它们之间确实有同步关系,而且如果不注意此关系,定会铸成大错!同步关系肯定与多线程有关,我们来看下面的一个例子:

// 先定义一个 LooperThread 类
class LooperThread extends Thread {public Looper myLooper = null;//定义一个public 的成员myLooper,初值为空public void run() { // 假设 run 在线程2中执行Looper.prepare();// myLooper 必须在这个线程中赋值myLooper = Looper.myLooper();Looper.loop();}
}
//下面这段代码在线程 1中执行,并且会创建线程2
{LooperThread lpThread= new LooperThread();lpThread.start(); //start 后会创建线程 2Looper looper = lpThread.myLooper; //<======注意// thread2Handler 和线程 2 的 Looper 挂上钩Handler thread2Handler = new Handler(looper);//sendMessage 发送的消息将由线程 2处理threadHandler.sendMessage(...);

上面这段代码的目的:

  • 线程1中创建线程2,并且线程2 通过 Looper 处理消息。
  • 线程1中得到线程2的 Looper,并且根据这个 Looper 创建一个 Handler,这样发送给该 Handler 的消息将由线程2处理。

  但很可惜,上面的代码是有问题的。如果我们熟悉多线,就会发现标有 “注意” 的那行代码存在着严重问题。myLoper 的创建是在线程2中,而looper 的赋值在线程1中很有可能此时线程2的run函数还没来得及给 myLooper 赋值,这样线程1中的 looper 将取到myLooper的初值,也就是 looper 等于null。另外,
    Handler thread2Handler = new Handler(looper) 不能替换成
    Handler thread2Handler = new Handler(Looper.myLooper())
  这是因为,myLooper 返回的是调用线程的 Looper,即 Thread1 的 Looper,而不是我们想要的Thread2的 Looper。
  解决此问题,可以采用同步的方式进行处理。其实, Android 早就替我们想好了,它提供了一个 HandlerThread 来解决这个问题。

2.4 HandlerThread介绍

HandlerThread 完美地解决了myLooper 可能为空的问题。下面来看看它是怎么做的,代码如下所示:

/*** Handy class for starting a new thread that has a looper. The looper can then be* used to create handler classes. Note that start() must still be called.*/
public class HandlerThread extends Thread {......@Override// 线程2运行它的run函数,looper就是在run线程里创建的public void run() {mTid = Process.myTid();Looper.prepare();//创建这个线程上的Loopersynchronized (this) {mLooper = Looper.myLooper();notifyAll(); //通知取Looper的线程1,此时Looper已经创建好了}Process.setThreadPriority(mPriority);onLooperPrepared();Looper.loop();mTid = -1;}/*** This method returns the Looper associated with this thread. If this thread not been started* or for any reason is isAlive() returns false, this method will return null. If this thread* has been started, this method will block until the looper has been initialized.* @return The looper.*/// 线程1调用 getLooper来获取新线程的Looperpublic Looper getLooper() {if (!isAlive()) {return null;}// If the thread has been started, wait until the looper has been created.synchronized (this) {while (isAlive() && mLooper == null) {try {wait(); //如果新线程还未创建Looper,等待} catch (InterruptedException e) {}}}return mLooper;}......
}

  HandlerThread 很简单,通过 wait/notifyAll 就解决了问题。为了避免重复发明轮子,就使用 HandlerThread 类吧。

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

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【3】使用YOLOv8训练自己的目标检测数据集-【收集数据集】-【标注数据集】-【划分数据集】-【配置训练环境】-【训练模型】-【评估模型】-【导出模型】

在自定义数据上训练 YOLOv8 目标检测模型的步骤可以总结如下 6 步&#xff1a; &#x1f31f;收集数据集&#x1f31f;标注数据集&#x1f31f;划分数据集&#x1f31f;配置训练环境&#x1f31f;训练模型&#x1f31f;评估模型 1. 收集数据集 随着深度学习技术在计算机视觉领…

8 集群管理

8 集群管理 8.1 集群结构 ES通常以集群方式工作&#xff0c;这样做不仅能够提高 ES的搜索能力还可以处理大数据搜索的能力&#xff0c;同时也增加了系统的容错能力及高可用&#xff0c;ES可以实现PB级数据的搜索。 下图是ES集群结构的示意图&#xff1a; 从上图总结以下概念…

【ChirpStack 】如何获取 JWT TOKEN并利用 API 下发数据?

LoRa App Server 提供了两类 API 接口&#xff0c;其中 RESTful JSON API 提供了一个 API console&#xff0c;在AS地址的基础上使用 /api 即可访问&#xff0c;罗列了 API 端点和文档介绍&#xff0c;测试起来非常方便。 本文主要介绍 如何使用 chirpstack 的API 进行测试以及…

187页9万字企业大数据治理与云平台实施方案(word)

1 项目背景概述 1.1 项目背景理解 1.2 项目需求范围 2 项目技术方案 2.1 咨询研究服务方案 2.1.1 咨询研究服务内容 2.1.2 咨询服务方案 2.2 第三方独立评估 2.2.1 概述 2.2.2 管理办法 2.2.3 考核机制 2.3 安全咨询研究服务方案 2.3.1 安全咨询服务内…

静态库和动态库的制作与使用

1.静态库的制作与使用 小知识&#xff1a;删除命令行&#xff0c;或者是配置好的路径之类的&#xff1a;退出编辑模式后&#xff1a;dd 保存并退出&#xff1a;退出编辑模式后&#xff0c;&#xff1a;wq (1)静态库的制作 1.首先生成你需要加入的文件的.O文件。使用如下代码 …

(浙大陈越版)数据结构 第二章 线性结构 2.4 多项式的加法和乘法运算实现

目录 2.4.1多项式的加法运算实现 如何设计一个函数分别求两个一元多项式的和&#xff1f; 算法思路&#xff1a;两个指针p1&#xff0c;p2分别指向两个多项式的第一个结点&#xff08;最高项&#xff09;并循环 循环&#xff1a; 2.4.2 多项式的乘积 1.多项式的表示 2.程…

前端本地存储方案-localForage

1 前言 前端有多种本地存储方案可供选择&#xff0c;以下是其中一些常见的方案&#xff1a; Cookie&#xff1a;Cookie是一种小型的文本文件&#xff0c;可以在浏览器中存储少量数据。Cookie通常用于存储会话信息或用户偏好设置等数据&#xff08;只能存储少量数据&#xff0…

c语言实现三子棋(思路+项目展示+源代码)

&#x1f4d5;博主介绍&#xff1a;目前大一正在学习c语言&#xff0c;数据结构&#xff0c;计算机网络。 c语言学习&#xff0c;是为了更好的学习其他的编程语言&#xff0c;C语言是母体语言&#xff0c;是人机交互接近底层的桥梁。 本章来写一个三子棋小游戏吧。 让我们开启c…

eSIM证书要求-证书验证-EID

SM-DP 和 SM-DS 应该验证 EUM 和 eUICC 证书中限制的 IIN 和 EID 的一致性&#xff08;参见第 4.5.2.1.0.2 和 4.5.2.1.0.3 节&#xff09;&#xff0c;并考虑 SGP.29 [ 89]。 根据 SGP.29 [89] 颁发的 EID 没有 SGP.02 [2] 中定义的 8 位 IIN。 相反&#xff0c;它们具有可变长…

系统移植 5-10

1.进入linux内核源码目录下&#xff0c;打开Makefile文件&#xff0c;搜索vmlinux&#xff0c;找到cmd_link-vmlinux命令&#xff0c; 1179 cmd_link-vmlinux \ 1180 $(CONFIG_SHELL) $< "$(LD)" "…

NOA上车「清一色」自主品牌,哪些供应商正在突围前线

随着入门级L2进入普及周期&#xff0c;以NOA&#xff08;高速、城区&#xff09;为代表的L2/L2赛道&#xff0c;正在成为主机厂、硬件供应商、算法及软件方案商的下一波市场制高点的争夺阵地。 高工智能汽车研究院监测数据显示&#xff0c;2023年1-3月中国市场&#xff08;不含…

Linux shell编程 数组 ^ 数组排序

数组定义 数组内数据类型可以为数值也可以为字符串。 若字符串类型需要使用 " " 包含以免空格扰乱数组。 方法1 空格分隔直接定义数组 arr(10 20 30 40 50) arr1(zhangsan lisi wangwu) 方法2 指定元素下标定义&#xff0c;若跳过元素不设置会显示为空 arr([0]1…

科技云报道:ChatGPT应用爆火,安全的大数据底座何处寻?

科技云报道原创。 毫无疑问&#xff0c;AIGC正在给人类社会带来一场深刻的变革。 而剥开其令人眼花缭乱的华丽外表&#xff0c;运行的核心离不开海量的数据支持。 ChatGPT的“入侵”已经引起了各行各业对内容抄袭的担忧&#xff0c;以及网络数据安全意识的提高。 虽然AI技术…

如何考核产品经理的绩效?

公司里几乎任何一个岗位都会被考核&#xff0c;产品经理也不例外。那么在产品经理实际工作该如何去考核呢&#xff1f;相信即将步入或身在职场的产品经理一定感兴趣&#xff0c;其实产品经理考核主要分为业绩考核和文化考核两大部分&#xff0c;下面将这两部分具体聊聊。 一、…

Xilinx 7系列FPGA内置ADC

Xilinx 7系列FPGA全系内置了一个ADC&#xff0c;称之为XADC。这个XADC&#xff0c;内部是两个1mbps的ADC&#xff0c;可以采集模拟信号转为数字信号送给FPGA内部使用。 XADC内部可以直接获取芯片结温和FPGA的若干供电电压&#xff08;7系列不包括VCCO&#xff09;&#xff0c;用…

翻遍GitHub帮你总结了一份并发图册+高并发笔记,一次性搞懂并发编程

前言 现在的面试&#xff0c;动不动就是“三高”&#xff0c;Java并发编程已然成为名企大厂面试的必考问题&#xff0c;其重要性不⾔⽽喻。 我也问了一些面试官&#xff0c;他们说&#xff1a;很多面试者&#xff0c;最基本的Java内存模型和JVM内存结构都分不清楚&#xff0c…

华硕ROG|玩家国度魔霸新锐2023 Windows11原厂预装系统 工厂模式恢复安装带ASUSRecevory一键还原

华硕ROG|玩家国度魔霸新锐2023 Windows11原厂预装系统 工厂模式恢复安装带ASUSRecevory一键还原 文件地址&#xff1a;https://pan.baidu.com/s/1snKOsH3OMl3GZLqeAf-GLA?pwd8888 华硕工厂恢复系统 &#xff0c;安装结束后带隐藏分区以及机器所有驱动软件 需准备一个16G左右…

Unity Audio -- (3)创建3D音效

本节会添加场景中小瀑布的音效。小瀑布的音效会有一个作用范围&#xff0c;也会根据角色所处的位置不同&#xff0c;产生不同的效果。 添加小瀑布的声音 1. 在Hierarchy中&#xff0c;点击右键&#xff0c;选择Audio -> Create Audio Source&#xff0c;将这个新的Audio So…

C/C++每日一练(20230510) 编辑距离、多数元素、数列累和

目录 1. 编辑距离 &#x1f31f;&#x1f31f;&#x1f31f; 2. 多数元素 &#x1f31f; 3. 求分数数列的前N项和 ※ &#x1f31f; 每日一练刷题专栏 &#x1f31f; Golang每日一练 专栏 Python每日一练 专栏 C/C每日一练 专栏 Java每日一练 专栏 1. 编辑距离 给你…

Baumer工业相机堡盟工业相机如何进行多个工业相机IP地址配置

Baumer工业相机堡盟工业相机如何进行多个工业相机IP地址配置 Baumer工业相机Baumer工业相机进行多相机IP配置的技术背景Baumer工业相机如何进行多相机IP配置1.配置Baumer工业相机连接的PC端IP地址2.配置Baumer工业相机的IP地址 Baumer工业相机 Baumer工业相机堡盟相机是一种高…