【Android AMS】startActivity流程分析

news2025/4/8 12:19:06

文章目录

  • AMS
  • ActivityStack
  • startActivity流程
    • startActivityMayWait
      • startActivityUncheckedLocked
    • startActivityLocked(ActivityRecord r, boolean newTask, boolean doResume, boolean keepCurTransition)
      • resumeTopActivityLocked
  • 参考

AMS是个用于管理Activity和其它组件运行状态的系统进程。

AMS

AMS在系统启动的时候,创建一个线程循环处理客户端的请求。AMS会向ServiceManager注册多种Binder Server:“activity”、“meminfo”、“cpuinfo”等。

AMS启动过程:

/*frameworks/base/services/java/com/android/server/SystemServer.java*/
 public static void main(String[] args) {
      ...
        // This used to be its own separate thread, but now it is
        // just the loop we run on the main thread.
        ServerThread thr = new ServerThread();
        thr.initAndLoop();
    }
/*frameworks/base/services/java/com/android/server/SystemServer.java*/
public void run() {Slog.i(TAG, "Activity Manager");
     context = ActivityManagerService.main(factoryTest); //启动AMSActivityManagerService.setSystemProcess(); //向Service Manager注册}

AMS提供了一个static main函数,通过它可以轻松启动AMS,通过setSystemProcess把这个重要系统服务注册到ServiceManager。

//frameworks/base/services/java/com/android/server/am/ActivityManagerService.java
   public static final Context main(int factoryTest) {
        AThread thr = new AThread();//AMS线程
        thr.start();//启动
		//这个线程执行在system server上,通过thr.mService判断AMS启动是否成功。如果成功,返回system server,否则一直等待。如果出错,就无力回天,空处理。
        synchronized (thr) {
            while (thr.mService == null) {
                try {
                    thr.wait();
                } catch (InterruptedException e) {
                }
            }
        }

        ActivityManagerService m = thr.mService;
        mSelf = m;
        ActivityThread at = ActivityThread.systemMain();
        mSystemThread = at;
        Context context = at.getSystemContext();
        context.setTheme(android.R.style.Theme_Holo);
        m.mContext = context;
        m.mFactoryTest = factoryTest;
        m.mIntentFirewall = new IntentFirewall(m.new IntentFirewallInterface());

        m.mStackSupervisor = new ActivityStackSupervisor(m, context, thr.mLooper);

        m.mBatteryStatsService.publish(context);
        m.mUsageStatsService.publish(context);
        m.mAppOpsService.publish(context);

		//唤醒
        synchronized (thr) {
            thr.mReady = true;
            thr.notifyAll();
        }

        m.startRunning(null, null, null, null);

        return context;
    }

这里一个wait & notify配对使用,让system server确保AMS启动成功,它自己再接着执行。这么做的原因无它,就是system server需要依赖于AMS。

将AMS注册到ServiceManager之后,它还注册了一系列和进程管理相关的服务:

    public static void setSystemProcess() {
        try {
            ActivityManagerService m = mSelf;            
            ServiceManager.addService("activity", m, true);//AMS的主业
            ServiceManager.addService("meminfo", new MemBinder(m));//内存使用情况//其他服务省略
    }

要了解AMS提供的所有功能,可以查看IActivityManager.java文件。
可以把这些接口进行分类:

  • 组件状态管理:例如startActivity、startService
  • 组件状态查询:例如getServices
  • Task相关:例如removeSubTask
  • 其它:查询运行时信息,例如getMemoryInfo

ActivityStack

/*frameworks/base/services/java/com/android/server/am/ActivityManagerService.java*/
public static final Context main(int factoryTest) {/*main()函数是启动AMS的入口*/ActivityManagerService m = thr.mService;
     …
     m.mMainStack = new ActivityStack(m, context, true, thr.mLooper);/*生成ActivityStack对象*/}

ActivityStack是管理当前系统所有activity状态的一个数据结构。
它里面有个enum 叫做ActivityState :

enum ActivityState {
       INITIALIZING, //正在初始化
       RESUMED,     //恢复
       PAUSING,     //正在暂停
       PAUSED,      //已经暂停
       STOPPING,    //正在停止
       STOPPED,     //已经停止
       FINISHING,   //正在完成
       DESTROYING,  //正在销毁
       DESTROYED    //已经销毁
    }

ActivityStack除了管理状态,还有一系列不同功能的ArrayList成员变量,它们都是ActivityRecord,用来记录每个activity的runtime信息:
在这里插入图片描述

startActivity流程

startActivity()用来启动一个Activity,它有可能启动当前进程的Activity,也有可能启动其它进程的Activity。当通过Intent匹配到目标对象,如果目标对象的进程已经启动,那么AMS就会通知这个进程加载并运行这个activity。如果进程没有启动,AMS就会先启动进程,再让进程运行目标activity。

/*frameworks/base/services/java/com/android/server/am/ActivityManagerService.java*/
    public final int startActivity(IApplicationThread caller, String callingPackage,
           Intent intent, String resolvedType, IBinder resultTo,
           String resultWho, int requestCode, int startFlags,
           String profileFile, ParcelFileDescriptor profileFd, Bundle options) {
        return startActivityAsUser(caller, callingPackage, intent, resolvedType,
                          result To, resultWho, requestCode, startFlags, 
                           profileFile, profileFd, options,UserHandle.getCallingUserId());
    }

startActivityAsUser比startActivity多了一个userId参数,用来表示调用者的用户ID,通过Binder机制的getCallingUid获得。

    public final int startActivityAsUser(IApplicationThread caller, String calling Package,
          Intent intent, String resolvedType, IBinder resultTo,
          String resultWho, int requestCode, int startFlags, String profileFile,
          ParcelFileDescriptor profileFd, Bundle options, int userId ){
      enforceNotIsolatedCaller("startActivity");
      userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId,
            false, true, "startActivity", null);
      return mMainStack.startActivityMayWait(caller,-1,callingPackage,intent, resolvedType,
            resultTo, resultWho, requestCode, startFlags, profileFile, profileFd,
            null, null, options, userId);/*这个函数是ActivityStack提供的*/
    }

startActivityAsUser的重点是做权限检查。

startActivityMayWait

startActivityMayWait的工作:
在这里插入图片描述
这个过程中,可能会“wait”,具体如流程图:
在这里插入图片描述
接着调用的是startActivityLocked,它有两个重载函数:

final int startActivityLocked(IApplicationThread caller,
            Intent intent, String resolvedType,
            Uri[] grantedUriPermissions,
            int grantedMode, ActivityInfo aInfo, IBinder resultTo,
            String resultWho, int requestCode,
            int callingPid, int callingUid, boolean onlyIfNeeded,
            boolean componentSpecified, ActivityRecord[] outActivity) {
        int err = START_SUCCESS;
        ProcessRecord callerApp = null;
        if (caller != null) {
            callerApp = mService.getRecordForAppLocked(caller);
            if (callerApp != null) {
                callingPid = callerApp.pid;
                callingUid = callerApp.info.uid;
            } else {
                Slog.w(TAG, "Unable to find app for caller " + caller
                      + " (pid=" + callingPid + ") when starting: "
                      + intent.toString());
                err = START_PERMISSION_DENIED;
            }
        }
        if (err == START_SUCCESS) {
            Slog.i(TAG, "START {" + intent.toShortString(true, true, true, false)
                    + "} from pid " + (callerApp != null ? callerApp.pid : callingPid));
        }
        ActivityRecord sourceRecord = null;
        ActivityRecord resultRecord = null;
        if (resultTo != null) {
            int index = indexOfTokenLocked(resultTo);
            if (DEBUG_RESULTS) Slog.v(
                TAG, "Will send result to " + resultTo + " (index " + index + ")");
            if (index >= 0) {
                sourceRecord = mHistory.get(index);
                if (requestCode >= 0 && !sourceRecord.finishing) {
                    resultRecord = sourceRecord;
                }
            }
        }
        int launchFlags = intent.getFlags();
        if ((launchFlags&Intent.FLAG_ACTIVITY_FORWARD_RESULT) != 0
                && sourceRecord != null) {
            // Transfer the result target from the source activity to the new
            // one being started, including any failures.
            if (requestCode >= 0) {
                return START_FORWARD_AND_REQUEST_CONFLICT;
            }
            resultRecord = sourceRecord.resultTo;
            resultWho = sourceRecord.resultWho;
            requestCode = sourceRecord.requestCode;
            sourceRecord.resultTo = null;
            if (resultRecord != null) {
                resultRecord.removeResultsLocked(
                    sourceRecord, resultWho, requestCode);
            }
        }
        if (err == START_SUCCESS && intent.getComponent() == null) {
            // We couldn't find a class that can handle the given Intent.
            // That's the end of that!
            err = START_INTENT_NOT_RESOLVED;
        }
        if (err == START_SUCCESS && aInfo == null) {
            // We couldn't find the specific class specified in the Intent.
            // Also the end of the line.
            err = START_CLASS_NOT_FOUND;
        }
        if (err != START_SUCCESS) {
            if (resultRecord != null) {
                sendActivityResultLocked(-1,
                    resultRecord, resultWho, requestCode,
                    Activity.RESULT_CANCELED, null);
            }
            mDismissKeyguardOnNextActivity = false;
            return err;
        }
        final int perm = mService.checkComponentPermission(aInfo.permission, callingPid,
                callingUid, aInfo.applicationInfo.uid, aInfo.exported);
        if (perm != PackageManager.PERMISSION_GRANTED) {
            if (resultRecord != null) {
                sendActivityResultLocked(-1,
                    resultRecord, resultWho, requestCode,
                    Activity.RESULT_CANCELED, null);
            }
            mDismissKeyguardOnNextActivity = false;
            String msg;
            if (!aInfo.exported) {
                msg = "Permission Denial: starting " + intent.toString()
                        + " from " + callerApp + " (pid=" + callingPid
                        + ", uid=" + callingUid + ")"
                        + " not exported from uid " + aInfo.applicationInfo.uid;
            } else {
                msg = "Permission Denial: starting " + intent.toString()
                        + " from " + callerApp + " (pid=" + callingPid
                        + ", uid=" + callingUid + ")"
                        + " requires " + aInfo.permission;
            }
            Slog.w(TAG, msg);
            throw new SecurityException(msg);
        }
        if (mMainStack) {
            if (mService.mController != null) {
                boolean abort = false;
                try {
                    // The Intent we give to the watcher has the extra data
                    // stripped off, since it can contain private information.
                    Intent watchIntent = intent.cloneFilter();
                    abort = !mService.mController.activityStarting(watchIntent,
                            aInfo.applicationInfo.packageName);
                } catch (RemoteException e) {
                    mService.mController = null;
                }
    
                if (abort) {
                    if (resultRecord != null) {
                        sendActivityResultLocked(-1,
                            resultRecord, resultWho, requestCode,
                            Activity.RESULT_CANCELED, null);
                    }
                    // We pretend to the caller that it was really started, but
                    // they will just get a cancel result.
                    mDismissKeyguardOnNextActivity = false;
                    return START_SUCCESS;
                }
            }
        }
        ActivityRecord r = new ActivityRecord(mService, this, callerApp, callingUid,
                intent, resolvedType, aInfo, mService.mConfiguration,
                resultRecord, resultWho, requestCode, componentSpecified);
        if (outActivity != null) {
            outActivity[0] = r;
        }
        if (mMainStack) {
            if (mResumedActivity == null
                    || mResumedActivity.info.applicationInfo.uid != callingUid) {
                if (!mService.checkAppSwitchAllowedLocked(callingPid, callingUid, "Activity start")) {
                    PendingActivityLaunch pal = new PendingActivityLaunch();
                    pal.r = r;
                    pal.sourceRecord = sourceRecord;
                    pal.grantedUriPermissions = grantedUriPermissions;
                    pal.grantedMode = grantedMode;
                    pal.onlyIfNeeded = onlyIfNeeded;
                    mService.mPendingActivityLaunches.add(pal);
                    mDismissKeyguardOnNextActivity = false;
                    return START_SWITCHES_CANCELED;
                }
            }
        
            if (mService.mDidAppSwitch) {
                // This is the second allowed switch since we stopped switches,
                // so now just generally allow switches.  Use case: user presses
                // home (switches disabled, switch to home, mDidAppSwitch now true);
                // user taps a home icon (coming from home so allowed, we hit here
                // and now allow anyone to switch again).
                mService.mAppSwitchesAllowedTime = 0;
            } else {
                mService.mDidAppSwitch = true;
            }
         
            mService.doPendingActivityLaunchesLocked(false);
        }
        
        err = startActivityUncheckedLocked(r, sourceRecord,
                grantedUriPermissions, grantedMode, onlyIfNeeded, true);
        if (mDismissKeyguardOnNextActivity && mPausingActivity == null) {
            // Someone asked to have the keyguard dismissed on the next
            // activity start, but we are not actually doing an activity
            // switch...  just dismiss the keyguard now, because we
            // probably want to see whatever is behind it.
            mDismissKeyguardOnNextActivity = false;
            mService.mWindowManager.dismissKeyguard();
        }
        return err;
    }

这里确保调用者本身的进程是存在的,否则返回START_PERMISSION_DENIED。这种情况出现在调用者被系统杀死,crash等。

FLAG_ACTIVITY_FORWARD_RESULT这个FLAG有跨越传递的作用,比如Activity1正常启动了Activity2,而当Activity2启动Activity3时使用了这个标志,那么当Activity3调用setResult时,result并不会像一般情况中那样传递给Activity2,而是传递给最初的Activity1。

startActivityUncheckedLocked

这个方法先拿到Intent中的FLAG,然后处理FLAG_ACTIVITY_NO_USER_ACTION,这个FLAG表示来电、闹钟等非用户主动触发的Activity事件。
这里处理了很多FLAG,例如LAUNCH_SINGLE_INSTANCE、LAUNCH_SINGLE_TASK这些,这里暂时不展开。

startActivityLocked(ActivityRecord r, boolean newTask, boolean doResume, boolean keepCurTransition)

    private final void startActivityLocked(ActivityRecord r, boolean newTask,
            boolean doResume, boolean keepCurTransition) {
     ...
        if (doResume) {
            resumeTopActivityLocked(null);
        }
    }

这里是启动activity的最后一站了,是AMS启动activity的关键。如果activity不是在新task中启动,那么程序要找出目标activity位于那个已有的task中。找到之后,如果它当前对用户不可见,就将它加入mHistory中,并在WMS中注册,但是不启动它。

接着将这个activity放在stack的最顶层:

mHistory.add(addPos, r);
r.putInHistory();
r.frontOfTask = newTask;

接下来,如果不是AMS的第一个activity,即mHistory > 0,则执行切换动画。
一个activity的UI能否显示,有个关键是WMS中必须有档案可查,就是appToken,它在startActivity中添加的:

mService.mWindowManager.addAppToken(
       addPos, r.appToken, r.task.taskId, r.info.screenOrientation, r.fullscreen);

activity有affinity特性的,它们更亲近affinity相符的task,关键地方在于FLAG是否有FLAG_ACTIVITY_RESET_TASK_IF_NEEDED。

Android源码中有很多函数都有locked标志,提醒开发者必须保证它们的线程安全。

到这里startActivity分析完毕了,但是activity的启动流程还没完。

resumeTopActivityLocked

AMS会继续调用resumeTopActivityLocked来恢复最上层的Activity,并pause之前的Activity,并且在Activity切换的过程中还要首先展示切换动画,然后两个新旧Activity还会向WMS分别申请和释放Surface,最终将它们显示/不显示在屏幕上。

  int i = mHistory.size()-1;//所有Activity的数量
  while (i >= 0) {
      ActivityRecord r = mHistory.get(i);
      if (!r.finishing && r != notTop && okToShow(r)) {
          return r;
      }
      i--;
  }
  return null;

这里处理ActivityRecord。

resumeTopActivityLocked执行后面的时候,可以正式启动目标activity了,但是有两种情况,一种是目标activity所属的进程已经在运行,一种是没有运行。
前者我们可以通知WMS这个Activity已经具备显示条件了:

mService.mWindowManager.setAppVisibility(next.appToken, true);

更新一系列全局变量,如果有等待启动的对象,就会通过:

next.app.thread.scheduleResumeActivity(next.appToken, mService.isNextTransitionForward());

告知目标线程要resume指定的activity。

后者的情况复杂些,会通过startSpecificActivityLocked启动进程,接着调用一系列和startActivity长得差不多的函数,最终调用zygote来fork一个新的进程:

Process.ProcessStartResult startResult =
           Process.start("<strong>android.app.ActivityThread</strong>", app.processName, uid, uid, gids,
                        debugFlags,app.info.targetSdkVersion, null, null);

可以看出,一个进程启动的时候,实际上会加载ActivityThread。

那么新启动的进程什么时候启动activity呢?
进程启动后要通知AMS,AMS会预留一段时间等待回调。这个在不同设备上有所差异,有的10s,有的300s。如果进程指定时间内没有完成attachApplication回调,那么AMS就认为异常了。如果进程完成了attachApplication回调,AMS就会判断当前是不是有Activity在等待这个进程启动。是的话,调用realStartActivityLocked继续之前的任务。
接着就是activity的生命周期了,onCreate,onStart,onResume等,并且在WMS和SurfaceFlinger的配合下,目标Activity描述的UI界面会显示在屏幕。

startActivity流程才算真正完成。

在这里插入图片描述

参考

《深入理解Android内核设计思想》

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

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

相关文章

人类连接的桥梁:探索Facebook如何连接世界

随着技术的发展和全球化的进程&#xff0c;我们的世界正在变得越来越紧密相连。在这个过程中&#xff0c;社交媒体平台扮演了一个至关重要的角色&#xff0c;为人们提供了一个跨越国界、文化和语言的交流平台。其中&#xff0c;Facebook作为全球最大的社交媒体平台&#xff0c;…

Harbor安装手册

安装Docker yum install -y yum-utils device-mapper-persistent-data lvm2 yum-config-manager \ --add-repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo sed -i -e /mirrors.cloud.aliyuncs.com/d -e /mirrors.aliyuncs.com/d \ /etc/yum.repos.d/…

error C2649: “typename”: 不是“class”的解决方法

目录 1.现象 2.解决方法 1.现象 我们定义了一个模版类&#xff0c;代码如下&#xff1a; template<typename X> class CShortWaveLinkProProtocol {friend class X;public:explicit CShortWaveLinkProProtocol() {}virtual ~CShortWaveLinkProProtocol() {}private:vo…

别让商业机密跑了:企业如何锁紧数据大门

Facebook 用户数据泄露&#xff1a;2018年&#xff0c;Cambridge Analytica公司非法获取了8700万Facebook用户的个人数据&#xff0c;并用于政治广告定向&#xff1b;Capital One 金融公司泄密&#xff1a;2019年&#xff0c;美国银行Capital One遭到黑客攻击&#xff0c;导致1…

Bridge 2024---创意无限,数字资产管理新纪元

Bridge 2024是Adobe公司开发的一款强大的视觉管理工具&#xff0c;专为创意工作者和摄影师设计。它提供了一个直观、高效的平台&#xff0c;用于组织、浏览、管理和展示图像、视频和音频文件。Bridge 2024具备全面的资源管理功能&#xff0c;用户可以轻松导入、组织、预览和搜索…

白盒测试之条件组合覆盖

白盒测试之条件组合覆盖&#xff08;蓝桥课学习笔记&#xff09; 实验介绍 使用分支-条件覆盖法设计白盒测试用例时可以使程序中所有判断语句中的条件取值为真、为假的情况和整个判断语句取真分支、假分支的情况都至少被执行过一次&#xff0c;但无法覆盖到所有路径&#xff…

c++-----继承

01&#xff1a;继承是什么 定义 继承 (inheritance) 机制是面向对象程序设计 使代码可以复用 的最重要的手段&#xff0c;它允许程序员在 保 持原有类特性的基础上进行扩展 &#xff0c;增加功能&#xff0c;这样产生新的类&#xff0c;称派生类。继承 呈现了面向对象 程序设计…

Java springboot使用EasyExcel读Excel文件,映射不到属性值,对象属性值都是null

如果你的类上有这个注解&#xff0c;去掉火或注释掉就可以了 Accessors(chain true)解决方法

常用日期组件封装

date.js // 获取近期日期数组 例&#xff1a;["2024-04-04 00:00:00", "2024-04-05 23:59:59"] const getDateRange (num 1) > {const time1 new Date()const diff new Date().getTime() - 86400000 * (num)const time2 new Date(diff)const year…

Canvas使用详细教学:从基础绘图到进阶动画再到实战(海报生成、Flappy Bird 小游戏等),掌握绘图与动画的秘诀

一、Canvas基础 1. Canvas简介 Canvas是HTML5引入的一种基于矢量图形的绘图技术&#xff0c;它是一个嵌入HTML文档中的矩形区域&#xff0c;允许开发者使用JavaScript直接操作其内容进行图形绘制。Canvas元素不包含任何内在的绘图能力&#xff0c;而是提供了一个空白的画布&a…

LLM-01 大模型 本地部署运行 ChatGLM2-6B-INT4(6GB) 简单上手 环境配置 单机单卡多卡 2070Super8GBx2 打怪升级!

写在前面 其他显卡环境也可以&#xff01;但是最少要有8GB的显存&#xff0c;不然很容易爆。 如果有多显卡的话&#xff0c;单机多卡也是很好的方案&#xff01;&#xff01;&#xff01; 背景介绍 目前借到一台算法组的服务器&#xff0c;我们可以查看一下目前显卡的情况 …

一篇出色的答辩状,需要在“答”与“辩”两方面下功夫,你做到了吗?

一篇出色的答辩状&#xff0c;需要在“答”与“辩”两方面下功夫&#xff0c;你做到了吗&#xff1f; 在法律诉讼中&#xff0c;答辩状的重要性不言而喻。它不仅是你回应对方指控的主要手段&#xff0c;也是展示你立场和观点的关键平台。在#李秘书讲写作#看来&#xff0c;一篇…

5.HC-05蓝牙模块

配置蓝牙模块 注意需要将蓝牙模块接5v,实测接3.3v好像不太好使的样子 首先需要把蓝牙模块通过TTL串口模块接到我们的电脑,然后打开我们的串口助手 注意,我们现在是配置蓝牙模块,所以需要进入AT模式,需要按着蓝牙模块上的黑色小按钮再上电,这时候模块上的LED灯以一秒慢闪一次…

Adobe AE(After Effects)2024下载地址及安装教程

Adobe After Effects是一款专业级别的视觉效果和动态图形处理软件&#xff0c;由Adobe Systems开发。它被广泛用于电影、电视节目、广告和其他多媒体项目的制作。 After Effects提供了强大的合成和特效功能&#xff0c;可以让用户创建出令人惊艳的动态图形和视觉效果。用户可以…

Python 物联网入门指南(七)

原文&#xff1a;zh.annas-archive.org/md5/4fe4273add75ed738e70f3d05e428b06 译者&#xff1a;飞龙 协议&#xff1a;CC BY-NC-SA 4.0 第二十四章&#xff1a;基本开关 到目前为止一定是一段史诗般的旅程&#xff01;回想一下你开始阅读这本书的时候&#xff0c;你是否曾想象…

【产品介绍】安科瑞Acrel-2000T无线测温解决方案

ARTM-Pn 无线测温采集装置&#xff0c;可用于3~35kV 户内开关柜&#xff0c;包括中置柜、手车柜、固定柜、环网柜等多种开关柜&#xff0c;也可用于 0.4kV 低压柜&#xff0c;包括固定柜&#xff0c;抽屉柜等。无线温度传感器可安装于柜内任何发热点上&#xff0c;利用无线数据…

跨境物流系统解决方案:构建全球化供应链的关键步骤

随着全球化的发展&#xff0c;跨境物流已成为国际贸易中不可或缺的重要环节。然而&#xff0c;由于各国之间的政治、法律、文化和语言差异&#xff0c;跨境物流常常面临诸多挑战&#xff0c;如货物清关、运输安全、物流跟踪等问题。因此&#xff0c;构建一个高效、可靠的跨境物…

Windows如何下载Bun并在前端Vue或React项目上替代Yarn或Npm

Bun Bun网站 Bun 在 Windows 上下载并安装 Bun 非常简单。你可以使用以下命令在 Windows 10 或更高版本上安装 Bun powershell -c "irm bun.sh/install.ps1 | iex"“powershell”不是内部或外部命令&#xff0c;也不是可运行的程序或批处理文件 PowerShell 命令解决…

嵌入式第四天:(C语言入门)

目录 什么是数组&#xff1f; 数组&#xff1a; 数组的使用&#xff1a; 数组的初始化&#xff1a; 数组名&#xff1a; 数组案例&#xff1a; 一维数组的最大值&#xff1a; 一维数组的逆置&#xff1a; 数组和指针&#xff1a; 通过指针操作数组元素&#xff1a; …

UART自适应任意(典型)波特率原理以及FPGA实现

文章目录 前言一、特征值自适应原理二、整体结构框图三、接收模块四、发送模块五、编写仿真测试文件以及结果分析六、上板测试参考 前言 在上一篇文章《详解UART通信协议以及FPGA实现》我们实现了UART的通信&#xff0c;我们知道UART通信需要双方约定好波特率&#xff0c;如果想…