惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Engineering at Meta
Engineering at Meta
T
Threat Research - Cisco Blogs
V
Vulnerabilities – Threatpost
T
Tor Project blog
T
Troy Hunt's Blog
C
CERT Recently Published Vulnerability Notes
C
Cisco Blogs
W
WeLiveSecurity
Cloudbric
Cloudbric
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
爱范儿
爱范儿
Google Online Security Blog
Google Online Security Blog
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Simon Willison's Weblog
Simon Willison's Weblog
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
Martin Fowler
Martin Fowler
Cisco Talos Blog
Cisco Talos Blog
F
Full Disclosure
MongoDB | Blog
MongoDB | Blog
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
I
Intezer
www.infosecurity-magazine.com
www.infosecurity-magazine.com
G
GRAHAM CLULEY
B
Blog RSS Feed
云风的 BLOG
云风的 BLOG
人人都是产品经理
人人都是产品经理
M
MIT News - Artificial intelligence
腾讯CDC
L
LangChain Blog
L
LINUX DO - 热门话题
H
Help Net Security
S
Schneier on Security
N
Netflix TechBlog - Medium
博客园 - Franky
酷 壳 – CoolShell
酷 壳 – CoolShell
Spread Privacy
Spread Privacy
S
Secure Thoughts
T
The Exploit Database - CXSecurity.com
P
Privacy International News Feed
P
Privacy & Cybersecurity Law Blog
Cyberwarzone
Cyberwarzone
A
About on SuperTechFans
NISL@THU
NISL@THU
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
D
DataBreaches.Net
The GitHub Blog
The GitHub Blog
Recorded Future
Recorded Future
雷峰网
雷峰网
AWS News Blog
AWS News Blog
V2EX - 技术
V2EX - 技术

音视频开发进阶

音视频教程-第三节 音视频教程-第二节 真的,AI 可能就是新时代的信息差 充值 Cursor 之后,工作有了哪些变化?🤔 个人'蒸馏'大模型能做哪些有意思的事情 DeepSeek 大模型在 Mac 上的部署和运行 音视频教程-第一节 【WebRTC 专栏】-- Android 开发集成 WebRTC 库的几种方式 【WebRTC 专栏】-- 在 Mac M1 等系列芯片编译和开发 WebRTC-Android 库 Meta Llama3 大模型在 Mac 上的部署和运行 iOS VideoToolBox 解码 HEVC Open-GOP 视频的问题排查 Flutter 状态管理之 InheritedWidget 使用和分析 用 ChatGPT 回答技术问题怎么样 ? 音视频开发系统入门大致路线 UE 4.27 添加自定义 ShadingModel 用 UE4 虚幻引擎做个捏脸小功能~~ UE4 材质练习 之 凹凸贴图偏移的使用 UE4 材质练习系列基础 OpenGL上下文创建以及共享机制 007 | 播放器系列专栏-解析 MP4 文件读取信息 006 | 播放器系列专栏-在 Mac 上查看 MP4 格式信息 干货 | 快速抽取缩略图是怎么练成的? 关于直播、WebRTC、FFmpeg 的那些事 005 | 播放器系列专栏-在 Windows 上查看 MP4 格式信息 将音视频中的花屏、绿屏、黑屏问题一网打尽 关于音视频里面的解码帧率和渲染帧率 004 | 播放器系列专栏-认识MP4视频(下) 003 | 播放器系列专栏-认识MP4视频(上) 入门或者转行音视频,应该要怎么做? H264视频文件如何缩放分辨率 002 | 播放器系列专栏-FFmpeg依赖库的配置 001 | 播放器系列专栏-关于播放器项目的一个小实践 Seek策略以及在有B帧情况下的处理 目前流媒体开发工程师工作内容主要是什么? 一个音视频领域专业问答的小圈子 干货收藏 || Vulkan Game Engine 视频教程 音视频春节假期内卷指南(实操) Vulkan 在 FFmpeg 中的支持 Windows 下 FFmpeg 和 LibX264 的编译和配置 Metal 开发 | 使用 C++ 进行接口调用 音视频开发工作经验分享 || 视频版 FFmpeg 调用 MediaCodec 硬解码到 Surface 上 代码吸猫 | 用 OpenGL 图像渲染的养猫计划 百倍变速--解码到底能不能丢 非参考帧 ?FFmpeg 有话说!!! 老生常谈-FFmpeg 的编译问题轻松搞定 FFmpeg 调用 Android MediaCodec 进行硬解码(附源码) 【WebRTC 专栏】--创建相机预览 Unity Shader 光照基础之 Half Lambert 光照模型 Unity Shader 光照基础之Lambert光照模型 Unity Shader 光照基础内容 Unity Shader 显示一张图片纹理 UnityShader 的基本概念 Unity 物体的基本操作 C++ 模板系列小结07-尾置返回类型 C++ 模板系列小结06-可变参数模板特性 C++ 中的多线程的使用和线程池建设 C++ 模板系列小结05-模板类型作为模板参数 C++ 模板系列小结04-类模板中的成员模板 C++ 模板系列小结03-在模板中指定变量类型 C++ 模板系列小结02-非类型模板参数 C++ 模板系列小结01-函数模板和类模板 从零打造渲染引擎系列01-什么是渲染引擎 iOS开发 - 在 Swift 中去调用 C/C++ 代码 2021 技术新番 - 从零打造渲染引擎系列 iOS 音视频开发的一些基础准备工作 音视频交流群又来啦~~~ 【WebRTC 专栏】WebRTC & Android 开发学习环境搭建~ 【喜大普奔】域名终于备案通过啦 Shader 优化 | OpenGL 绘制网格效果 【音视频连载-011】第二季 FFmpeg 一层一层获取文件信息 KodeLife | Shader 实时编辑预览的强大工具使用实践 推荐几个堪称教科书级别的 Android 音视频入门项目 【音视频连载-010】第二季 FFmpeg 日志打印 【音视频连载-008】基础学习篇-SDL 播放 PCM 音频文件(下) 【音视频连载-007】基础学习篇-SDL 播放 PCM 音频文件(上) 【音视频连载-006】基础学习篇-SDL 播放 YUV 视频文件 【音视频连载-005】基础学习篇-SDL 加载 YUV 文件并显示 【音视频连载-004】基础学习篇-SDL 加载图片并显示 【音视频连载-003】基础学习篇-SDL 消息循环和事件响应 【音视频连载-002】基础学习篇-SDL 创建窗口并显示颜色 【音视频连载-001】基础学习篇- SDL 介绍以及工程配置 LearnOpenGL 源码在 MAC 上的编译与调试 2019 年终总结与回顾 Android NDK 开发的免费技术视频来啦~~ OpenGL 实现视频编辑中的转场效果 OpenGL 实践之贝塞尔曲线绘制 图像库 libjpeg-turbo 编译与实践 图像库 libpng 编译与实践 rust 开发编译 Android 动态库实践 Android NDK 开发 —— 从 Assets 文件夹加载图片并上传纹理 简单易用的图像解码库介绍 —— stb_image 博客图床迁移记 进击的 Vulkan 移动开发之 SwapChain 进击的 Vulkan 移动开发之 Command Buffer 进击的 Vulkan 移动开发之 Instance & Device & Queue 进击的 Vulkan 移动开发(一)之今生前世 Java 显式锁 Lock 与条件队列 C++ 标准容器库小结 一文读懂 YUV 的采样与格式 《OpenGL ES 3.x 游戏开发》碰撞检测之 AABB 包围盒
Android StateMachine 状态机分析
音视频开发进阶 · 2017-12-22 · via 音视频开发进阶

一个专注音视频领域的小圈子

之前就有写过一篇文章来学习状态机:状态机学习。 在之后的工作中多次用到了 StateMachine 状态机,简单记录其原理。 StateMachine 类位于 Android 源码中的,路径是frameworks/base/core/java/com/android/internal/util/StateMachine.java,从路径名可以看出,这是一个工具类,该类对状态机进行了封装,方便使用。

在安卓源码中进行搜索,会发现很多类用到了状态机,比如:WifiStateMachineA2dpStateMachineNsdStateMachineP2dStateMachine等等,它们都直接或间接的继承了 StateMachine 类。可见,状态机的思想在安卓源码中得到了广泛使用。

在我们的应用开发中也可以使用源码中的StateMachine类,只要从源码中把StateMachineState类拷贝到我们的工程目录就可以使用了,对于其 import 的一些类,在我们的开发环境中也全都有,不用担心。

如果你对状态机有那么一丝丝的了解,那么学习 StateMachine 类最好的资料就是它的注释了,解释的一清二楚。

简单使用阐述

Android 中的状态机是一个分层的消息处理机制,每一层都会有一到多个节点,而状态机的消息就是在这些节点之间流转处理,如下结构所示:

	// 状态机分层结构
          mP0
         /   \
        mP1   mS0
       /   \
      mS2   mS1
     /  \    \
    mS3  mS4  mS5  ---> initial state 初始节点

而节点就是State类,它实现了IState接口,除了enterexit方法外,还有processMessage方法,表示用来处理节点的消息。若返回HANDLED则表示消息处理完成,若返回NOT_HANDLED则表示消息没有处理。

构造状态机

在我们使用 StateMachine 之前,要构造好所需的状态分层结构。通过addState方法来向状态机中添加节点,例如如下的状态结构,mS1 和 mS2 节点有公共的父节点 mP1,同时还有一个孤立的节点 mP2。

      // 状态分层结构设定
        mP1      mP2
       /   \
      mS2   mS1
      // 构造状态机结构代码
      addState(mP1);
          addState(mS1, mP1);
          addState(mS2, mP1);
      addState(mP2);

addState方法添加节点时,还能指定其父节点添加。

当我们构造完了状态机时,还需要指定其中一个节点为启动点,消息从启动点开始处理,通过setInitialState方法来指定启动点,最后通过start方法启动状态机。

状态机消息处理

当构造完想要的状态机结构时,就是对状态机内部消息流转的处理了。

start状态机时,状态机的第一个动作就是调用节点的enter方法,不过,它调用的是指定的启动点的最远的父节点的enter方法,然后再是次一级的父节点的enter方法,最后才是启动点的enter方法,就如同上面的结构所示,先调用 mP1 点,然后才是 mS1 点的方法,此时 mS1 节点就是状态机的当前对外的点。由此可见,当启动点进入 enter状态时,它的父节点,直至最顶层的父节点都进入了enter状态了。

状态机中的每个节点都 0 个或 1 个父节点,当子节点不能处理当前消息时,它可以通过返回NOT_HANDLED将当前消息传递给其父节点来处理。如果一个消息从未被处理过,那么unhandledMessage方法将会被调用给最后一次机会来处理该消息。

除此之外,节点还可以通过transitionTo方法将当前节点转移至另外一个新的节点。

          mP0
         /   \
        mP1   mS0
       /   \
      mS2   mS1
     /  \    \
    mS3  mS4  mS5  ---> initial state

例如,当 mS5 处理消息,想要将当前节点转移至 mS4 时,那么它会先找到 mS5 和 mS4 最近的公有父节点 mP1。然后,除了这个最近的公有父节点 mP1 以及它的上层节点外,mS5 进入enter状态时启动的那些父节点都会退出调用exit方法。最后再由 mP1 节点下的节点调用enter方法直到新节点 mS4 调用了enter方法。

也就是说,从 mS5 到 mS4 状态的转变,先是 mS5、mS1 调用了exit方法,再是 mS2、mS4 调用了enter方法,这就是状态机中节点发生状态转移时的调用过程。

除此之外,节点还可以调用deferMessagesendMessageAtFroneOfQueue方法。deferMessage方法使得消息存储在消息队列中,当状态转移到新节点时才会处理,而sendMessageAtFrontOfQueue方法则是将消息放置到消息队列的头部。

当状态机的所有消息都完成时,可以调用transitionToHaltingState方法来将状态机处理停止状态。此时,状态机将转移到HaltingState停止状态,并调用halting方法。随后收到的所有消息都只是会调用haltedProcessMessage方法来处理了。

若想要完全的停止状态机,则可以使用quit或者quitNow方法来处理。

以上就是状态机对于消息处理的过程,长篇的文字说明还是不如代码来的直观,这里就不贴完整的代码了,参考代码的连接如下:GithubGist 链接地址

状态机实现原理分析

如果我们在初始化状态机时只是传递了一个名字,而没有传递 Looper 或者 Handler 之类的消息循环,那么状态机默认就是启用其内部的一个线程HandlerThread

  protected StateMachine(String name) {
        mSmThread = new HandlerThread(name); // 创建 HandlerThread 线程
        mSmThread.start();
        Looper looper = mSmThread.getLooper();
        initStateMachine(name, looper);
    }
    // 将消息循环 Looper 与 Handler 进行绑定
  private void initStateMachine(String name, Looper looper) {
        mName = name;
        mSmHandler = new SmHandler(looper, this);
    }

构造状态及时,在其内部开启了一个线程,并将其消息循环 Looer 传递给了 SmHandler 对象,而SmHandler对象就是状态机中最主要的用来派发消息事件和切换状态的了,它派发的消息都是在 HandlerThread 线程进行处理的。

同时,SmHandler 内部有两个数组,用来保存状态机中的链式状态关系,分别是mStateStackmTempStateStack变量。当状态机完成启动时,就会通过上面来个变量来保存节点信息。

而状态机的消息处理,内部也是通过 SmHandler 来处理转发的。

具体的实现,建议参考这篇文章:http://blog.csdn.net/yangwen123/article/details/10591451 讲的实在太详细了,拜读了多遍也不敢说写的能比它更清楚。

参考

1、http://blog.csdn.net/yangwen123/article/details/10591451

原创文章,转载请注明来源:    Android StateMachine 状态机分析