Android开发学习心得(二) 情悦其淑美兮,心振荡而不怡 -- 前端逻辑

本文主要是介绍Android开发学习心得(二) 情悦其淑美兮,心振荡而不怡 -- 前端逻辑,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

因为是学习心得, 免不了很多理解错误, 欢迎指教和抬杠!

前文通过HelloWorld程序体验过了应用程序的生死轮回, 我们建立起了应用程序在平台中的位置和Android平台如何对待这些应用程序的启动和销毁的基本概念, 我想不论是谁都会在这之后对应用程序的能力边界到底有多大有种跃跃欲试的感觉, 至少我就有点爪痒.

一个基本的判断是, 应用程序的能力取决于Android平台允许它干什么. 平台自身当然希望自己越开放越好, 从逻辑上让应用程序能够它想干的任何事, 但自由意味着无序, 怎么能让整个平台既充分开放又严格有序呢? 平台为此做了大量工作.

首先平台抽象定义了软件的一些基本运行逻辑, 并创建了与之相对应的一些基础类和其继承体系, 使建立在Android平台上的应用程序虽然可以广泛的定制自己需要的功能, 但其基本运行逻辑则必须遵循系统规程, 以此才能建立起系统资源管理, 安全验证, 消息分发等的统一机制.

我们这第二篇就专门介绍Android平台如何管理应用程序的用户界面部分, 也就是我们平时说的前端.

手机应用程序基本都是为交互而存在, 而交互的载体是手机屏幕和用户界面. 基于这个逻辑, Android平台把应用程序的主要操作单元定义为Activity, 对应于一次完整的与用户之间的交互活动, 而作为交互载体的用户界面则分为三层置于Activity管理之下, 如下图所示:

用户界面层级关系图
Alt
应用程序 (App 以下用App作为专有名词代替) 定制的用户界面主要位于ContentView这个区域内.

这个结构说明了一个重要问题, App的用户界面由Activity负责创建和绘制, 而且在Activity中可以获取所有元素实例的句柄.

用户界面可能是App开发里面最繁琐复杂的部分了, ContentView区域盛放的就是这些繁琐复杂. 如果我们从Android平台的角度来考虑问题, 我们会发现, 用户界面不仅需要按钮, 文本框, 输入框, 下拉列表等等交互用基本对象, 还要对界面上的所有元素的大小, 字体, 颜色, 位置等全部样式进行定义, 除了这些, 还需要向页面传入显示用的数据, 图片等各种资源需要妥当加载, 页面需要能监听用户触发的各种事件并做出相应, 开发时页面需要能够互相引用, 还需要安排好页面之间的跳转和数据传递等等工作.

看着这些任务, 头都大了. Android平台是如何理顺这些任务的呢?

首先, 必须有一个Class专门负责页面绘制方面的所有工作, 绘制完成后, 它就是页面本人, 负责跟页面相关的数据展示, 控制,刷新, 和事件监听等全部工作, 这个Class的名字叫View(android.view.View).

这么多工作让它自己完成, 这个类得多复杂多庞大啊?! 没错, View这个类就是一个巨大复杂的类, 而且它还很抽象, 是个抽象类(Abstract Class), 并不能直接使用, 我们只能使用它的那些定义了全部抽象方法的子子孙孙们.

下面是取自Android开发文档关于View类的定义部分:

public class View 
extends Object implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSourcejava.lang.Object↳	android.view.ViewKnown direct subclasses
AnalogClock, ImageView, KeyboardView, MediaRouteButton, ProgressBar, Space, SurfaceView, TextView, 
TextureView, ViewGroup, ViewStubKnown indirect subclasses
AbsListView, AbsSeekBar, AbsSpinner, AbsoluteLayout, ActionMenuView, AdapterView<T extends Adapter>, 
AdapterViewAnimator, AdapterViewFlipper, AppWidgetHostView, AutoCompleteTextView, Button, CalendarView, 
CheckBox, CheckedTextView, Chronometer, and 52 others.

看到最后and 52 others的时候是不是有种"我的天啊!"的感觉.

既然要负责绘制页面上的所有元素, 那么就让它们都成为我的子孙吧! View类的设计者是这么想的也是这么做的, 于是, 页面上的所有控件都成了View的后继类.

既然绘制的时候还要考虑所有元素的样式, 那么就让所有跟样式有关的元素也都成为子孙的一部分吧! 成不了一部分的就变成我新的子孙吧! 于是, 所有控件都有了定义样式的基本属性, 而布局信息是无法成为控件属性的, 于是就成了全新的ViewGroup类以及它的一群Layout子类, 专门负责定义页面布局信息.

既然还要监听用户触发的事件, 那么就让所有事件监听程序都把接口安装在我身上吧! 于是, View类就拥有了全部用户界面监听程序的回调入口.

可以想象, 当我们的App把当前页面的所有元素都实例化完成后塞进一个汇总的View实例里面, 最后调用draw()方法进行画面绘制时的内部场景, 如果它是一部能运转的机械, 应该相当壮观吧!

View的定义里, 监听程序, 页面刷新管理, 绘制等这些画面元素的通用部分, 都被直接定义在View类里面了. 页面控件作为View类的子孙自动获取了这些通用功能后, 加入了自己的定制部分, 形成了独立的组件. 这两点比较容易理解. 稍微复杂一些的是ViewGroup以及它的Layout子孙们, 它们还藏着一点小逻辑.

下面的图片引自Android开发文档中关于Layout部分的介绍:
在这里插入图片描述
在"用户界面层级关系图"中, ContentView区域本身就是一个ViewGroup, 在这个ViewGroup中再存放其它的ViewGroupView们. 我们可以把这个结构看成一棵树, View是叶子节点. 这种结构的设计极大的方便了页面的布局操作和代码复用. 从此妈妈再也不用担心页面排版啦!

下图是完整的用户界面层级关系:
在这里插入图片描述
在看这个图的时候, 聪明的你还应该在每个名字叫做xxxView的框框外面脑补出很多名字叫onXxxListener()的监听程序插在上面, 以至于每个框框看起来都毛茸茸的才对. :p

这就是Android在面对用户界面这个复杂问题时给出的基本解决思路.

“等等! 求求你不要胡扯了, 难道我看到的其它文档中铺天盖地而来的xxx.xml文件都是摆设吗?” 聪明的你终于愤怒了, 在你想替天行道戳死作者之前, 请容我喝口水, 再慢慢讲那些铺满了你的天空的.xml们都在干什么.

很显然, 光有上面说的那些关于View类的抽象定义是远远不够的, 距离我们写出能运行的App还有十万八千毫米那么远!

对的, 看起来很远, 但实际上已经只剩下一扇窗户纸啦!

我们开发自己的App需要关注的仅仅是ContentView这个框框里面的部分, 把它的树状结构设计好, 定义好其中各元素的样式, 然后在ActivitysetContentView()一下就好了. 逻辑上就是这样, 当然我们还要安排页面数据, 和响应页面事件等等, 但这些不那么帅的事情后面再讲也来得及.

那么这些页面要如何定制呢? 这就需要求助于我们万能的格式定义文件.xml了.

所有的页面布局定义都以.xml文件存在, 存放在App项目目录的/app/src/main/res/layout下面. 心得(一)的HelloWorld例子中我们已经看到了这个目录下的名为activity_mail.xml的页面定义文件, 我们的"Hello World!"字符串就静静的躺在里面, 以至于我们根本无法在Java Class中看到这些硬代码"Hard Coding".

基本的原则是,

  • 每个用户界面都需要一个.xml描述文件, 文件名中.xml前面的部分作为页面布局的资源名
  • 每个View控件都需要存放在一个唯一的ViewGroup
  • Layout描述文件可以通过引用进行复用

虽然页面定义.xml文件的实际运用会根据应用程序的要求变得极为复杂, 但了解了这上面这些基本原理以后, 这些复杂的应用就会变成尾巴, 需要的时候就摇一摇, 需要怎么摇就怎么摇, 那些看起来像大海一样无穷无尽的描述文档也变得不再让人畏惧, 它们一下子就变成工具书静静地躺在案边, 随用随翻就可以了.

Android平台会通过读取.xml描述文件实例化各种View, 意味着我们也可以把我们自己继承自View的定制Class放到.xml描述文件中, 参与页面建设, 具体方法很多文档中都有具体描述, 这里就不再赘述了, 聪明的你一定可以自主学习的, 对不对?
详情请参考 Custom View Components

页面布局和样式设计好了, 就大功告成了吗? NONONO, 耍完帅以后当然还有很多不那么帅的工作要做.

根据上面的层级结构图, 我们可以看出, 页面是嵌在Activity中的, 有一个ContentView就必然会有一个Activity在外面作为后台程序服务它. 所以当我们通过.xml文件定义好页面后, 就必须着手搞定Activity了.

下面是来自Android开发文档对于Activity的定义:

public class Activity 
extends ContextThemeWrapper implements LayoutInflater.Factory2, Window.Callback, KeyEvent.Callback, 
View.OnCreateContextMenuListener, ComponentCallbacks2java.lang.Object↳	android.content.Context↳	android.content.ContextWrapper↳	android.view.ContextThemeWrapper↳	android.app.ActivityKnown direct subclasses
AccountAuthenticatorActivity, ActivityGroup, AliasActivity, ExpandableListActivity, ListActivity, NativeActivityKnown indirect subclasses
LauncherActivity, PreferenceActivity, TabActivity

从这个定义我们大概可以猜出ActivityView简单多了, 至少没有other xx more了, 而实际上Activity也确实比View简单得多. Activity继承自Context, 就表明了设计者对它的定位是为App运行提供Context环境管理.

于是, Activity在App中主要负责运行环境(Runtime Context)管理的工作, 为View的展示和用户交互提供后台支持. 包括用户界面层级关系中各层级实例的维护, Activity自身生命周期维护, 事件监听回调程序, 以及Intent管理等任务.

所以我们在设计App的时候, 会把页面调度逻辑, 数据处理逻辑和一部分事件监听程序, 放在Activity中.

为什么不把所有的事件监听程序都放在Activity中呢? 上面的层级关系图中显示Activity是所有内容最外层的容器, 虽然完全有能力容纳所有事件的监听, 但随着页面复杂度的增加, 监听程序会让Activity类的复杂度急剧增加, 不利于程序的阅读和维护. 所以大多数的事件监听程序都定义在与页面相对应的View类中, 各安其所.

Activity创建以后还需要在AndroidManifest.xml文件中进行注册后才能参与系统调度, 详细配置方法请参阅: Introduction to Activities 和 Intents and Intent Filters

App的页面迁移工作主要由Intent负责管理.

Intent是个很有意思的类, 它就像个交流大使, 在各个实体间传递信息, 制造交流. 页面迁移只是它的职责之一. 它的主要职责有三个:1. 启动Activity 2. 启动Service 3. 发布广播消息.

根据这个定位, 我们是不是可以这样设想, 所有和活动间互操作有关的事情都可以找它呢? 目前来看至少在Activity上是这样的, 另两个还没学到, 以后再来验证我们的这个猜测好了.

通过Intent我们甚至可以跨应用调用别人的Activity! 像系统提供的相机, 浏览器, 日历, 地图, 闹钟, Email, 文件, 电话, 短信息等等应用都可以通过Intent激活. 因为太有趣了, 所以放段例子代码给你感受一下:

使用Intent调用网络浏览器打开网页的例子代码, 截取自Android开发文档:

Example intent:

    public void openWebPage(String url) {Uri webpage = Uri.parse(url);Intent intent = new Intent(Intent.ACTION_VIEW, webpage);if (intent.resolveActivity(getPackageManager()) != null) {startActivity(intent);}}

Example intent filter:

    <activity ...><intent-filter><action android:name="android.intent.action.VIEW" /><!-- Include the host attribute if you want your app to respondonly to URLs with your app's domain. --><data android:scheme="http" android:host="www.example.com" /><category android:name="android.intent.category.DEFAULT" /><!-- The BROWSABLE category is required to get links from web pages. --><category android:name="android.intent.category.BROWSABLE" /></intent-filter></activity>

Intent启动Activity的方法有两种, 一种是显式一种是隐式(Explicit and Implicit), 很简单, 在很多资料上都有介绍这里也不想重复, 有兴趣的话, 聪明的你可以去这里:Intents and Intent Filters逛逛, 就可以查到了哟!

至此, 我们关于前端开发就只剩下一点内容还想说一说了, 就是Android平台关于资源的管理.

从心得(一)的Hello World!例子中我们可以看到这样一行代码:

    setContentView(R.layout.activity_main); //from MainActivity.java

是用于通知Android平台加载由activity_main.xml文件定义格式的页面, 可是R.layout.activity_main是什么鬼? activity_main.xml又在哪?

奥妙就在这个名叫R的Java类中. R.java是由编译器负责自动维护的类, 用于管理所有App中用到的资源id. Android平台要求为App用到的所有资源分配一个与名称相对应的资源id, 类型为int, 且唯一. 这个int数字由编译器调用id生成函数自动生成, 无序人工干预. 这还真是从未有过的编程体验啊! 有点意思!

资源被分为几大类分别存放在app/src/main/res下的

  • drawable (图片)
  • layout (页面定义.xml文件)
  • mipmap (图标)
  • values (常量)
  • menu (菜单定义.xml文件)

等文件夹中. 这些资源都会在R.java中生成以文件名(不包含扩展名的部分)为变量名的对应项, 文件夹名则为相对应的内部类名. 例如, drawable/bg1.png文件相对应的idR.drawable.bg1, 而layout/activity_main.xml相对应的id则为R.layout.activity_main.

除了各种文件, 在全部.xml文件中定义的各对象id也可以通过@+id/id_name的方式到R文件中进行注册, 注册后除了在Java文件中可以取得这些资源, 在.xml文件中也可以通过@id/id_name的方式引用他们, 这些对象id生成后被放在R.id这个内部类里面. 真是天才的设计!

总结一下, Activity是用户交互活动的基本单位, View负责页面显示相关的全部工作, layout/*.xml文件用于定义页面格式和样式, Intent负责交互活动(Activity)间的沟通交流. 大概的结构关系如下图显示:
在这里插入图片描述
PS: 绝大多数App中都不止一个Activity, Android平台使用返回栈(back stack)数据结构管理Activity序列, 这种后进先出式结构最适合Activity的应用逻辑, 可以非常方便的让处于栈顶的页面保持活动, 而压制其他页面, 完美配合Activity生命周期的各种状态切换. 详细情况请参考: Understand Tasks and Back Stack

这篇关于Android开发学习心得(二) 情悦其淑美兮,心振荡而不怡 -- 前端逻辑的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



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

相关文章

Android Paging 分页加载库使用实践

《AndroidPaging分页加载库使用实践》AndroidPaging库是Jetpack组件的一部分,它提供了一套完整的解决方案来处理大型数据集的分页加载,本文将深入探讨Paging库... 目录前言一、Paging 库概述二、Paging 3 核心组件1. PagingSource2. Pager3.

PyQt5 GUI 开发的基础知识

《PyQt5GUI开发的基础知识》Qt是一个跨平台的C++图形用户界面开发框架,支持GUI和非GUI程序开发,本文介绍了使用PyQt5进行界面开发的基础知识,包括创建简单窗口、常用控件、窗口属性设... 目录简介第一个PyQt程序最常用的三个功能模块控件QPushButton(按钮)控件QLable(纯文本

从入门到精通详解LangChain加载HTML内容的全攻略

《从入门到精通详解LangChain加载HTML内容的全攻略》这篇文章主要为大家详细介绍了如何用LangChain优雅地处理HTML内容,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录引言:当大语言模型遇见html一、HTML加载器为什么需要专门的HTML加载器核心加载器对比表二

基于Python开发一个图像水印批量添加工具

《基于Python开发一个图像水印批量添加工具》在当今数字化内容爆炸式增长的时代,图像版权保护已成为创作者和企业的核心需求,本方案将详细介绍一个基于PythonPIL库的工业级图像水印解决方案,有需要... 目录一、系统架构设计1.1 整体处理流程1.2 类结构设计(扩展版本)二、核心算法深入解析2.1 自

MySQL逻辑删除与唯一索引冲突解决方案

《MySQL逻辑删除与唯一索引冲突解决方案》本文探讨MySQL逻辑删除与唯一索引冲突问题,提出四种解决方案:复合索引+时间戳、修改唯一字段、历史表、业务层校验,推荐方案1和方案3,适用于不同场景,感兴... 目录问题背景问题复现解决方案解决方案1.复合唯一索引 + 时间戳删除字段解决方案2:删除后修改唯一字

Android kotlin中 Channel 和 Flow 的区别和选择使用场景分析

《Androidkotlin中Channel和Flow的区别和选择使用场景分析》Kotlin协程中,Flow是冷数据流,按需触发,适合响应式数据处理;Channel是热数据流,持续发送,支持... 目录一、基本概念界定FlowChannel二、核心特性对比数据生产触发条件生产与消费的关系背压处理机制生命周期

Android ClassLoader加载机制详解

《AndroidClassLoader加载机制详解》Android的ClassLoader负责加载.dex文件,基于双亲委派模型,支持热修复和插件化,需注意类冲突、内存泄漏和兼容性问题,本文给大家介... 目录一、ClassLoader概述1.1 类加载的基本概念1.2 android与Java Class

SpringBoot开发中十大常见陷阱深度解析与避坑指南

《SpringBoot开发中十大常见陷阱深度解析与避坑指南》在SpringBoot的开发过程中,即使是经验丰富的开发者也难免会遇到各种棘手的问题,本文将针对SpringBoot开发中十大常见的“坑... 目录引言一、配置总出错?是不是同时用了.properties和.yml?二、换个位置配置就失效?搞清楚加

前端如何通过nginx访问本地端口

《前端如何通过nginx访问本地端口》:本文主要介绍前端如何通过nginx访问本地端口的问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、nginx安装1、下载(1)下载地址(2)系统选择(3)版本选择2、安装部署(1)解压(2)配置文件修改(3)启动(4)

HTML中meta标签的常见使用案例(示例详解)

《HTML中meta标签的常见使用案例(示例详解)》HTMLmeta标签用于提供文档元数据,涵盖字符编码、SEO优化、社交媒体集成、移动设备适配、浏览器控制及安全隐私设置,优化页面显示与搜索引擎索引... 目录html中meta标签的常见使用案例一、基础功能二、搜索引擎优化(seo)三、社交媒体集成四、移动