Android_应用初体验

介绍

本文将介绍如何构建简单的Android应用。首先,了解如何通过Android Studio创建“Hello, World!”项目并运行它。然后,为应用创建一个新界面,该界面会接受用户输入,并切换到应用中的一个新屏幕以显示用户输入内容。

开始之前,需要了解有关Android应用的两个基本概念:它们如何提供多个入口点,以及它们如何适应不同的设备。

应用提供多个入口点

Android应用都是将各种可单独调用的组件加以组合构建而成。例如,activity是提供界面(UI)的一种应用组件。

“主”activity在用户点按您的应用图标时启动。还可以将用户从其他位置(例如,从通知中,甚至从其他应用中)引导至某个activity。

其他组件(如WorkManager)可使应用能够在没有界面的情况下执行后台任务。

应用可适应不同的设备

Android允许您为不同的设备提供不同的资源。例如,您可以针对不同的屏幕尺寸创建不同的布局。系统会根据当前设备的屏幕尺寸确定要使用的布局。

如果应用的任何功能需要使用特定的硬件(例如摄像头),您可以在运行时查询该设备是否具有该硬件,如果没有,则停用相应的功能。您可以指定应用需要使用特定的硬件,这样,Google Play就不会允许在没有这些硬件的设备上安装应用。

界面布局

Android应用的界面(UI)以布局微件的层次结构形式构建而成。布局是ViewGroup对象,即控制其子视图在屏幕上的放置方式的容器。微件是View对象,即按钮和文本框等界面组件。

ViewGroup对象如何在布局中形成分支并包含View对象的图示

Android提供了ViewGroup和View类的XML词汇表,因此界面的大部分内容都在XML文件中定义。

Android Studio的布局编辑器会在开发者拖放视图构建布局时自动编写XML代码,如何使用布局编辑器创建布局呢?

构建简单的界面 - 布局编辑器

左下方的Component Tree面板显示布局的视图层次结构。在本例中,根视图是ConstraintLayout,它仅包含一个TextView对象。

ConstraintLayout是一种布局,它根据同级视图和父布局的约束条件定义每个视图的位置,这样一来,使用扁平视图层次结构既可以创建简单布局,又可以创建复杂布局。这种布局无需嵌套布局。嵌套布局是布局内的布局,会增加绘制界面所需的时间。

例如可以声明以下布局。

  • 视图A距离父布局顶部16dp
  • 视图A距离父布局左侧16dp
  • 视图B距离视图A右侧16dp
  • 视图B与视图A顶部对齐

ConstraintLayout内放有两个视图的图示

添加文本框

按照下面的步骤添加文本框:

  1. 首先,需要移除布局中已有的内容。在Componrnt Tree面板中点击TextView,然后按Delete键。
  2. 在Palette面板中,点击Text以显示可用的文本控件。
  3. 将Plain Text拖动到设计编辑器中,并将其放在靠近布局顶部的位置。这是一个接受纯文本输入的EditText微件。
  4. 点击设计编辑器中的视图。现在,可以在每个角上看到调整视图大小的正方形手柄,并在每个边上看到圆形约束锚点。为了更好地控制,可能需要放大编辑器。
  5. 点击并按住顶边上的锚点,将其向上拖动,直至其贴靠到布局顶部,然后将其释放。这是一个约束条件:它会将视图约束在已设置的默认外边距内。在本例中,将其设置为距离布局顶部16dp。
  6. 使用相同的过程创建一个从视图左侧到布局左侧的约束条件。

结果如图。

按照到父布局顶部和左侧的距离约束文本框

添加按钮

  1. 在Palette面板中,点击Buttons
  2. 将Button微件拖到设计编辑器中,并将其放在靠近右侧的位置。
  3. 创建一个从按钮左侧到文本框右侧的约束条件。
  4. 如需按水平对齐约束视图,请创建一个文本基线之间的约束条件。为此,请右键点击按钮,然后选择Show Baseline。基线锚点显示在按钮内部。点击并按住此锚点,然后将其拖动到相邻文本框中显示的基线锚点上。(也可以右键点击文本框,从文本框内部发出基线锚点,拖动到相邻按钮中显示的基线锚点上。)

结果应如图所示。

按照到文本框右侧的距离以及文本框基线来约束按钮

还可以根据顶边或底边实现水平对齐。但按钮的图片周围有内边距,因此如果以这种方式对齐,那么它们看上去是没有对齐的。

更改界面字符串

若要预览界面,点击工具栏中的Select Design Surface,然后选择Design。文本输入和按钮标签应设置为默认值。

若要更改界面字符串,按以下步骤操作:

  1. Project窗口,打开app > res > values > strings.xml。这是一个字符串资源文件,可以在此文件中指定所有界面字符串。可以利用该文件在一个位置管理所有界面字符串,使字符串的查找、更新和本地化变得更加容易。

  2. 点击窗口顶部的Open editor。此时将打开Translations Editor,它提供了一个可以添加和修改默认字符串的简单界面。还有助于让所有已翻译的字符串井然有序。

  3. 点击加号Add Key可以创建一个新字符串作为文本框的“提示文本”。此时会打开如图所示的窗口。
    用于添加新字符串的对话框

    在Add Key对话框中,完成以下步骤:

    1. Key字段中输入“edit_message”。
    2. Default Value字段中输入“Enter a message”。
    3. 点击OK
  4. 再添加一个名为"button_send"且值为"Send"的键。

现在,您可以为每个视图设置这些字符串。若要返回布局文件,请点击标签页栏中的activity_main.xml。然后,添加字符串,如下所示:

  1. 点击布局中的文本框。如果右侧还未显示Attributes窗口,请点击右侧边栏上的 Attributes
  2. 找到text属性(当前设为“Name”)并删除相应的值。
  3. 找到hint属性,然后点击文本框右侧的img(Pick a Resource)。在显示的对话框中,双击列表中的edit_message
  4. 点击布局中的按钮,找到其text属性(当前设为“Button”)。然后点击img(Pick a Resource),并选择button_send

让文本框大小可灵活调整

若要创建一个适应不同屏幕尺寸的布局,需要让文本框拉伸以填充去除按钮和外边距后剩余的所有水平空间

继续操作之前,点击工具栏中的Select Design Surface,然后选择Blueprint

若要让文本框大小可灵活调整,请按以下步骤操作:

  1. 选择两个视图。若要执行此操作,请点击一个视图,在按住Shift键的同时点击另一个视图,然后右键点击任一视图并依次选择Chains > Create Horizontal Chain。布局随即显示出来,如图所示。
    选择Create Horizontal Chain后所得到的结果

    链是两个或多个视图之间的双向约束条件,可让您采用一致的方式安排链接的视图。

  2. 选择按钮并打开Attributes窗口。然后使用Constraint Widget将右外边距设为16 dp

  3. 点击文本框以查看其属性。然后,点击宽度指示器(途中红框框住的地方)两次,确保将其设置为锯齿状线(Match Constraints),如图中的标注1所示。
    点击以将宽度更改为Match Constraints

    “Match constraints”表示宽度将延长以符合水平约束条件和外边距的定义。因此,文本框将拉伸以填充去除按钮和所有外边距后剩余的水平空间。

现在,布局已经完成,如图。

文本框现在拉伸以填充剩余空间

最终布局XML应为以下内容:将其与Code标签页中看到的内容进行比较看看错了没。属性顺序不同没影响。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.example.myfirstapp.MainActivity">

<EditText
android:id="@+id/editText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginLeft="16dp"
android:layout_marginTop="16dp"
android:ems="10"
android:hint="@string/edit_message"
android:inputType="textPersonName"
app:layout_constraintEnd_toStartOf="@+id/button"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />

<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:layout_marginStart="16dp"
android:text="@string/button_send"
app:layout_constraintBaseline_toBaselineOf="@+id/editText"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/editText" />
</androidx.constraintlayout.widget.ConstraintLayout>

此时可以测试运行一下。

下面构建在点按按钮后启动的另一个activity。

启动另一个Activity

将向MainActivity添加一些代码,以便在用户点按Send按钮时启动一个显示消息的新activity。

响应“Send”按钮

MainActivity类添加一个在用户点按Send按钮时调用的方法:

  1. app > java > com.example.myfirstapp > MainActivity文件中,添加以下sendMessage()方法桩,这个方法将在某个事件发生后被回调。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public class MainActivity extends AppCompatActivity
    {
    // ...

    /* Called when the user taps the Send Button */
    public void sendMessage(View view)
    {
    // Do something in response to button
    }
    }

    可能会看到一条错误,因为Android Studio无法解析用作方法参数的View类。若要清除错误,点击View声明,将光标置于其上,然后按Alt+Enter(在 Mac 上则按Option+Enter)进行快速修复。如果出现一个菜单,选择Import class。

  2. 返回到activity_main.xml文件,并从该按钮调用此方法:

    1. 选择布局编辑器中的相应按钮。
    2. 在Attributes窗口中,找到onClick属性,并从其下拉列表中选择sendMessage[MainActivity];或者在activity_main.xml中的Button标签中加入android:onClick="sendMessage"属性。

    现在,当用户点按该按钮时,系统将调用sendMessage()方法。

    请注意此方法中提供的详细信息。系统需要这些信息来识别此方法是否与android:onClick属性兼容。具体来说,此方法具有以下特性:

    1. public。
    2. 返回值为空,或在Kotlin中为隐式Unit
    3. View是唯一的参数。这是在第1步结束时点击的View对象。
  3. 接下来,填写此方法,以读取文本字段的内容,并将该文本传递给另一个activity。

构建一个intent

Intent是在相互独立的组件(如两个activity)之间提供运行时绑定功能的对象。Intent表示应用执行某项操作的意图。可以使用intent执行多种任务,但在本例中,intent将用于启动另一个activity

MainActivity中,添加EXTRA_MESSAGE常量和sendMessage()代码,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class MainActivity extends AppCompatActivity {
public static final String EXTRA_MESSAGE = "com.example.myfirstapp.MESSAGE";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}

/** Called when the user taps the Send button */
public void sendMessage(View view) {
Intent intent = new Intent(this, DisplayMessageActivity.class);
EditText editText = (EditText) findViewById(R.id.editText);
String message = editText.getText().toString();
intent.putExtra(EXTRA_MESSAGE, message);
startActivity(intent);
}
}

DisplayMessageActivity仍有错误,但没有关系,下一部分中将修复错误。

sendMessage()将发生以下情况:

  • Intent构造函数会获取两个参数:ContextClass
    Context参数首先会被使用,因为Activity类是Context的子类,在构造activity时,则先构造context父模块。
    在本例中,系统将Intent传递到的应用组件的class参数是要启动的activity。
  • putExtra()方法将editText的值添加到intent。Intent能够以称为"extra"的键值对形式携带数据。
    key是一个公共常量EXTRA_MESSAGE,因为下一个activity将使用该键检索文本值。为intent extra定义键时,最好使用应用的软件包名称作为前缀。这样可以确保这些键是独一无二的,这在应用需要与其他应用进行交互时很重要。
    value是message,即为editText的内容。将在下一个activity中获取。
  • startActivity()方法将启动一个由Intent指定的DisplayMessageActivity实例,接下来需要创建该类。

创建第二个activity

  1. 在Project窗口中,右键点击app文件夹,然后依次选择New > Activity > Empty Activity
  2. Configure Activity窗口中,输入“DisplayMessageActivity”作为Activity Name。将所有其他属性保留为默认设置,然后点击Finish

Android Studio会自动执行下列三项操作:

  • 创建DisplayMessageActivity文件。
  • 创建DisplayMessageActivity文件对应的布局文件 activity_display_message.xml
  • AndroidManifest.xml中添加所需的<activity>元素。

如果您运行应用并点按第一个activity上的按钮,将启动第二个activity,但它为空。这是因为第二个activity使用模板提供的空布局。

添加文本视图

新activity包含一个空白布局文件。请按以下步骤操作,在显示消息的位置添加一个文本视图:

  1. 打开app > res > layout > activity_display_message.xml文件。
  2. 点击工具栏中的imgEnable Autoconnection to Parent。系统将启用Autoconnect
  3. Palette面板中,点击Text,将TextView拖动到布局中,然后将其放置在靠近布局顶部中心的位置,使其贴靠到出现的垂直线上。Autoconnect将添加左侧和右侧约束条件,以便将该视图放置在水平中心位置。
  4. 再创建一个从文本视图顶部到布局顶部的约束条件,使该视图如图1中所示。

或者,您可以对文本样式进行一些调整,方法是在Attributes窗口的Common Attributes面板中展开textAppearance,然后更改textSizetextColor等属性。

显示消息

在此步骤中,修改第二个activity以显示第一个activity传递的消息。

DisplayMessageActivity中,将以下代码添加到onCreate()方法中:

1
2
3
4
5
6
7
8
9
10
11
12
13
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_display_message);

// Get the Intent that started this activity and extract the string
Intent intent = getIntent();
String message = intent.getStringExtra(MainActivity.EXTRA_MESSAGE);

// Capture the layout's TextView and set the string as its text
TextView textView = findViewById(R.id.textView);
textView.setText(message);
}

添加向上导航功能

应用中,不是主入口点的每个屏幕(所有不是主屏幕的屏幕)都必须提供导航功能,以便将用户引导至应用层次结构中的逻辑父级屏幕。为此,请在应用栏中添加向上按钮。

若要添加向上按钮,需要在AndroidManifest.xml文件中声明哪个activity是逻辑父级。打开app > manifests > AndroidManifest.xml文件,找到DisplayMessageActivity<activity>标记,然后将其替换为以下代码:

1
2
3
4
5
6
7
<activity android:name=".DisplayMessageActivity"
android:parentActivityName=".MainActivity">
<!-- The meta-data tag is required if you support API level 15 and lower -->
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".MainActivity" />
</activity>

Android系统现在会自动向应用栏添加向上按钮。

Android_架构

分层

Android大致可以分为四层架构:Linux内核层、系统运行库层、应用框架层和应用层。

  1. 应用层。系统内置的应用程序以及非系统级的应用程序都属于应用层,负责与用户进行直接交互,通常都是用Java开发。

  2. 应用框架层。这一层是由Java代码编写的,可以称为Java API Framework。这一层主要提供了构建应用程序时可能用到的各种API,Android自带的一些核心应用就是使用这些API完成的,开发者也可以通过这些API来构建自己的应用程序。开发者API可以直接映射到底层HAL接口,并可提供与实现驱动程序相关的实用信息。这一层所提供的主要组件有:

    1. Activity Manager,活动管理器。管理各个应用程序生命周期,以及常用的导航回退功能。
    2. Location Manager,位置管理器。提供地理位置及定位功能服务。
    3. Package Manager,包管理器。管理所有安装在Android系统中的应用程序。
    4. Notification Manager,通知管理器。使得应用程序可以在状态栏中显示自定义的提示信息。
    5. Resource Manager,资源管理器。提供应用程序使用的各种非代码资源,如本地化字符串、图片、布局文件、颜色文件等。
    6. Telephony Manager,电话管理器。管理所有的移动设备功能。
    7. Window Manager,窗口管理器。管理所有开启的窗口程序。
    8. Content Provider,内容提供器。使得不同应用程序之间可以共享数据。
    9. View System,视图系统。构建应用程序的基本组件。

    image-20220812120406746

  3. Binder IPC。Binder 进程间通信 (IPC) 机制允许应用框架跨越进程边界并调用Android系统服务代码,这使得高级框架API能与Android系统服务进行交互。在应用框架级别,开发者无法看到此类通信的过程,但一切似乎都在“按部就班地运行”。

  4. 系统服务层。系统服务是专注于特定功能的模块化组件,例如窗口管理器、搜索服务或通知管理器。应用框架 API 所提供的功能可与系统服务通信,以访问底层硬件。Android 包含两组服务:“系统”(诸如窗口管理器和通知管理器之类的服务)和“媒体”(涉及播放和录制媒体的服务)。

    1. 这一层通过一些C/C++库来为Android系统提供了主要的特性支持。如SQLite库提供了数据库的支持,OpenGL|ES库提供了3D绘图的支持,Webkit库提供了浏览器内核的支持等。
    2. 这一层还有Android运行时库,它主要提供了一些核心库,能够允许开发者使用Java语言来编写Android应用。另外,Android运行时库中还包含了Dalvik虚拟机(5.0系统之后改为ART运行环境),它使得每一个Android应用都能运行在独立的进程当中,并且拥有一个自己的Dalvik虚拟机实例。相较于Java虚拟机,Dalvik是专门为移动设备定制的,它针对手机内存、CPU性能有限等情况做了优化处理。
  5. 硬件抽象层。HAL 可定义一个标准接口以供硬件供应商实现,这可让 Android 忽略较低级别的驱动程序实现。借助 HAL,您可以顺利实现相关功能,而不会影响或更改更高级别的系统。HAL 实现会被封装成模块,并会由 Android 系统适时地加载。如需了解详情,请参阅硬件抽象层 (HAL)

  6. Linux内核。Android系统是基于Linux内核的,这一层为Android设备的各种硬件提供了底层的驱动,如显示驱动、音频驱动、照相机驱动、蓝牙驱动、Wi-Fi驱动、电源管理等。开发设备驱动程序与开发典型的 Linux 设备驱动程序类似。Android 使用的 Linux 内核版本包含一些特殊的补充功能,例如低内存终止守护进程(一个内存管理系统,可更主动地保留内存)、唤醒锁定(一种 PowerManager 系统服务)、Binder IPC 驱动程序,以及对移动嵌入式平台来说非常重要的其他功能。这些补充功能主要用于增强系统功能,不会影响驱动程序开发。您可以使用任意版本的内核,只要它支持所需功能(如 Binder 驱动程序)即可。不过,我们建议您使用 Android 内核的最新版本。如需了解详情,请参阅构建内核一文。

HIDL(HAL接口定义语言) - AIDL

HAL Interface Definition Language。

Android 8.0 重新设计了 Android 操作系统框架(在一个名为“Treble”的项目中),以便让制造商能够以更低的成本更轻松、更快速地将设备更新到新版 Android 系统。在这种新架构中,HAL 接口定义语言(HIDL,发音为“hide-l”)指定了 HAL 和其用户之间的接口,让用户无需重新构建 HAL,就能替换 Android 框架。在 Android 10 中,HIDL 功能已整合到 AIDL(Android Interface Definition Language, 即Android接口定义语言)中。此后,HIDL 就被废弃了,并且仅供尚未转换为 AIDL 的子系统使用。

Android进阶解密中的分层

Android系统架构分为5层,从上到下依次是应用层、应用框架层、系统运行库层、硬件抽象层和Linux内核层。

image-20220812144626701

  1. 应用层(System Apps)

    1. 系统内置的应用程序以及非系统级的应用程序都属于应用层,负责与用户进行直接交互,通常都是用Java进行开发的。
  2. 应用框架层(Java API Framework)

    1. 应用框架层为开发人员提供了开发应用程序所需要的API,我们平常开发应用程序都是调用这一层所提供的API,当然也包括系统应用。这一层是由Java代码编写的,可以称为Java Framework。下面来看这一层所提供的主要组件。

    2. 名称 功能描述
      Activity Manager(活动管理器) 管理各个应用程序生命周期。以及常用的导航回退功能
      Location Manager(位置管理器) 提供地理位置及定位功能服务
      Package Manager(包管理器) 管理所有安装在Android系统中的应用程序
      Notification Manager(通知管理器) 使得应用程序可以在状态栏中显示自定义的提示信息
      Resource Manager(资源管理器) 提供应用程序使用的各种非代码资源,如本地化字符串、图片、布局文件、 颜色文件等
      Telephony Manager(电话管理器) 管理所有的移动设备功能
      Window Manager(窗口管理器) 管理所有开启的窗口程序
      Content Provider(内容提供器) 使得不同应用程序之间可以共享数据
      View System(视图系统) 构建应用程序的基本组件
  3. 系统运行库层(Native)。系统运行库层分为两部分,分别是C/C++程序库和Android运行时库

    1. C/C++程序库能被Android系统中的不同组件所使用,并通过应用程序框架为开发者提供服务,表列出了主要的C/C++程序库。

      功能描述 名称
      OpenGL ES 3D绘图函数库
      Libc 从BSD继承来的标准C系统函数库,专门为基于嵌入式Linux的设备定制
      Media Framework 多媒体库。支持多种常用的音频。视频格式录制和回放
      SQLite 轻型的关系型数据库引擎
      SGL 底层的2D图形渲染引擎
      SSL 安全套接层,是一种为网络通信提供安全及数据完整性的安全协议
      FreeType 可移植的字体引擎,它提供统一的接口来访问多种字体格式文件
    2. Android运行时库又分为核心库和ART(Android 5.0系统之后,Dalvik虚拟机被ART取代)。

      1. 核心库提供了Java语言核心库的大多数功能,这样开发者可以使用Java语言来编写Android应用。
      2. 与JVM相比,Dalvik虚拟机(DVM)是专门为移动设备定制的,允许在有限的内存中同时运行多个虚拟机的实例,并且每一个Dalvik应用作为一个独立的Linux进程执行。独立的进程可以防止在虚拟机崩溃的时候所有程序都被关闭。DVM中的应用每次运行时,字节码都需要通过即时编译器(Just In Time,JIT)转换为机器码,这会使得应用的运行效率降低。而ART的机制与DVM不同,在ART中,系统在安装应用时会进行一次预编译(Ahead Of Time,AOT),将字节码预先编译成机器码并存储在本地,这样应用每次运行时就不需要执行编译了,运行效率也大大提高。
  4. 硬件抽象层(HAL)

    1. 硬件抽象层是位于操作系统内核与硬件电路之间的接口层,其目的在于将硬件抽象化,为了保护硬件厂商的知识产权,它隐藏了特定平台的硬件接口细节,为操作系统提供虚拟硬件平台,使其具有硬件无关性,可在多种平台上进行移植。从软硬件测试的角度来看,软硬件的测试工作都可分别基于硬件抽象层来完成,使得软硬件测试工作的并行进行成为可能。通俗来讲,就是将控制硬件的动作放在硬件抽象层中。
  5. Linux内核层(Linux Kernel)

    1. Android的核心系统服务基于Linux内核,在此基础上添加了部分Android专用的驱动。系统的安全性、内存管理、进程管理、网络协议栈和驱动模型等都依赖于该内核。