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 kotlin语言实现删除文件的解决方案

《Androidkotlin语言实现删除文件的解决方案》:本文主要介绍Androidkotlin语言实现删除文件的解决方案,在项目开发过程中,尤其是需要跨平台协作的项目,那么删除用户指定的文件的... 目录一、前言二、适用环境三、模板内容1.权限申请2.Activity中的模板一、前言在项目开发过程中,尤

vue基于ElementUI动态设置表格高度的3种方法

《vue基于ElementUI动态设置表格高度的3种方法》ElementUI+vue动态设置表格高度的几种方法,抛砖引玉,还有其它方法动态设置表格高度,大家可以开动脑筋... 方法一、css + js的形式这个方法需要在表格外层设置一个div,原理是将表格的高度设置成外层div的高度,所以外层的div需要

使用DeepSeek API 结合VSCode提升开发效率

《使用DeepSeekAPI结合VSCode提升开发效率》:本文主要介绍DeepSeekAPI与VisualStudioCode(VSCode)结合使用,以提升软件开发效率,具有一定的参考价值... 目录引言准备工作安装必要的 VSCode 扩展配置 DeepSeek API1. 创建 API 请求文件2.

Vue项目中Element UI组件未注册的问题原因及解决方法

《Vue项目中ElementUI组件未注册的问题原因及解决方法》在Vue项目中使用ElementUI组件库时,开发者可能会遇到一些常见问题,例如组件未正确注册导致的警告或错误,本文将详细探讨这些问题... 目录引言一、问题背景1.1 错误信息分析1.2 问题原因二、解决方法2.1 全局引入 Element

详解如何在React中执行条件渲染

《详解如何在React中执行条件渲染》在现代Web开发中,React作为一种流行的JavaScript库,为开发者提供了一种高效构建用户界面的方式,条件渲染是React中的一个关键概念,本文将深入探讨... 目录引言什么是条件渲染?基础示例使用逻辑与运算符(&&)使用条件语句列表中的条件渲染总结引言在现代

详解Vue如何使用xlsx库导出Excel文件

《详解Vue如何使用xlsx库导出Excel文件》第三方库xlsx提供了强大的功能来处理Excel文件,它可以简化导出Excel文件这个过程,本文将为大家详细介绍一下它的具体使用,需要的小伙伴可以了解... 目录1. 安装依赖2. 创建vue组件3. 解释代码在Vue.js项目中导出Excel文件,使用第三

Java实现Excel与HTML互转

《Java实现Excel与HTML互转》Excel是一种电子表格格式,而HTM则是一种用于创建网页的标记语言,虽然两者在用途上存在差异,但有时我们需要将数据从一种格式转换为另一种格式,下面我们就来看看... Excel是一种电子表格格式,广泛用于数据处理和分析,而HTM则是一种用于创建网页的标记语言。虽然两

基于Python开发电脑定时关机工具

《基于Python开发电脑定时关机工具》这篇文章主要为大家详细介绍了如何基于Python开发一个电脑定时关机工具,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录1. 简介2. 运行效果3. 相关源码1. 简介这个程序就像一个“忠实的管家”,帮你按时关掉电脑,而且全程不需要你多做

Java中的Opencv简介与开发环境部署方法

《Java中的Opencv简介与开发环境部署方法》OpenCV是一个开源的计算机视觉和图像处理库,提供了丰富的图像处理算法和工具,它支持多种图像处理和计算机视觉算法,可以用于物体识别与跟踪、图像分割与... 目录1.Opencv简介Opencv的应用2.Java使用OpenCV进行图像操作opencv安装j

vue解决子组件样式覆盖问题scoped deep

《vue解决子组件样式覆盖问题scopeddeep》文章主要介绍了在Vue项目中处理全局样式和局部样式的方法,包括使用scoped属性和深度选择器(/deep/)来覆盖子组件的样式,作者建议所有组件... 目录前言scoped分析deep分析使用总结所有组件必须加scoped父组件覆盖子组件使用deep前言