第1章 第一行Android代码
1.1 Android 简介
1.1.1 Android系统架构:Linux内核层、系统运行库层、应用框架层和应用层
- Linux内核层
Android系统是基于Linux内核的,这一层为Android设备的各种硬件提供了底层的驱动,如显示驱动、音频驱动、照相机驱动、蓝牙驱动、Wifi驱动、电源管理等。
- 系统运行库层
这一层通过一些C/C++库来为Android系统提供主要的特性支持。如SQLite库提供了数据库的支持,OpenGL|ES库提供了3D绘图的支持,Webkit库提供了浏览器内核的支持等。
同样在这一层还有Android运行时库,它主要提供一些核心库,能够允许开发者使用Java语言来编写Android应用。另外,Android运行时库还包含了Dalvik虚拟机(5.0系统之后改为ART运行环境),它使得每一个Android应用都能运行在独立的进程当中,并且拥有一个自己的Davilk虚拟机实例。相较于Java虚拟机,Dalvik是专门为移动设备定制的,它针对手机内存、CPU性能有限等情况做了优化处理。
- 应用框架层
这一层主要提供构建应用程序时可能用到的各种API,Android自带的一些核心应用就是使用这些API完成的,开发者也可以通过使用这些API来构建自己的应用程序。
- 应用层
所有安装在手机上的应用程序都是属于这一层的,比如系统自带的联系人、短信等程序,或者是你从Google Play上下载的小游戏,当然还包括自己开发的程序。
1.1.3 Android应用开发特色
- 四大组件
Android系统四大组件分别是活动(Activity)、服务(Service)、广播接收器(Broadcast Receiver)和内容提供器(Content Provider)。
其中活动是所有Android应用程序的门面,凡是在应用中看得到的东西,都是放在活动中的。而服务,你无法看到它,但它会一直在后台默默地运行,即使用户退出了应用,服务仍然是可以继续运行的。广播接收器允许你的应用接收来自各处的广播消息,比如电话、短信等,当然你的应用同样也可以向外发送广播消息。内容提供器则为应用程序之间共享数据提供了可能,比如你想要读取系统电话簿中的联系人,就需要通过内容提供器来实现。
- 丰富的系统控件
Android系统为开发者提供了丰富的系统控件,若不满足系统自带的控件效果,也可以定制属于自己的控件。
- SOLite数据库
Android系统还自带了这种轻量级、运算速度极快的嵌入式关系型数据库。它不仅支持标准的SQL语法,还可以通过Android封装好的API进行操作,让存储和读取数据变得非常方便。
- 强大的多媒体
Android系统还提供了丰富的多媒体服务,如音乐、视频、录音、拍照、闹铃等等,这一切你都可以在程序中通过代码进行控制,让你的应用变得更加丰富多彩。
- 地理位置定位
移动设备和PC相比起来,地理位置定位功能应该可以算是很大的一个亮点。现在的Android手机都内置有GPS,走到哪都可以定位到自己的位置,发挥想象可以做出创意十足的应用,如果再结合强大的地图功能,LBS这一领域潜力无限。
1.2 搭建开发环境
设备均已安装相应软件,暂略环境搭建过程。
1.3 创建第一个Android项目
1.3.4 分析第一个Android程序
默认Android模式的项目结构如下图:
将项目结构模式切换成Project,此时是项目真实的目录结构,如下图:
- .gradle和.idea ------ 这两个目录下放置的都是Android Studio 自动生成的一些文件。
- app -------------------- 项目中的代码、资源等内容都是放置在这个目录下,后面单独讲解。
- build ------------------- 这个目录主要包含一些编译时自动生成的文件(上图没有,原因待定)
- gradle ----------------- 这个目录下包含了gradle wrapper的配置文件,使用gradle wrapper的方式不需要提前将gradle下载好,而是会自动根据本地的缓存情况决定是否需要联网下载gradle 。Android Studio默认就是启用gradle wrapper方式的,如果需更改成离线模式,可以点击Android Studio 导航栏---> File--->Setting--->Build, Execution, Deployment---->gradle,进行配置更改。
- .gitignore ------------- 这个文件是将指定的目录或文件排除在版本控制之外的,版本控制见后续
- build.gradle ---------- 这是项目全局的gradle构建脚本,通常该文件内容不需修改。
- gradle.properties --- 这个文件是全局的gradle配置文件,在这里配置的属性将会影响到项目中所有的gradle编译脚本。
- gradlew和gradlew.bat 这两个文件是用来在命令行界面中执行gradle命令的,其中gradlew是在Linux或Mac系统中使用的,gradlew.bat 是在Windows系统中使用的。
- HelloWorld.iml ------ 自动生成,用于标识这是一个IntelliJ IDEA项目,不需修改(上图未显示,原因未知)
- local.properties ----- 用于指定本机中的Android SDK 路径,通常自动生成。
- settings.gradle ------ 这个文件用于指定项目中所有引入的模块。由于Hello World项目中只有一个app模块,因此该文件中也就只引入了app这一个模块。通常情况下,模块的引入是自动完成的。
除app目录之外,大多数的文件和目录是自动生成的,app目录下的才是重点,展开如图:
- build ------ 和外层的build目录类似,包含一些在编译时自动生成的文件
- libs -------- 如果项目中使用到了第三方jar包,需要把这些jar包都放在libs目录下,放在这个目录下的jar包都会被自动添加到构建路径里去。
- androidTest ---- 此处是用来编写Android Test 测试用例的,可以对项目进行一些自动化测试
- java ------- java目录是放置所有java代码的地方。
- res -------- 在项目中使用到的所有图片、布局、字符串等资源都要存放在这个目录下。
- AndroidManifest.xml --------- 这是整个项目的配置文件,在程序中定义的所有四大组件都需要在这个文件里注册,另外还可以在这个文件中给应用程序添加权限声明。
- test -------- 此处是用来比编写Unit Test测试用例的,是对项目进行自动化测试的另一种方式。
- .gitignore ----- 这个文件用于将app模块内指定的目录或文件排除在版本控制之外,作用和外层的.gitignore文件类似。
- app.iml -------- IntelliJ IDEA项目自动生成的文件,不需修改。
- build.gradle ---- 这是app模块的gradle构建脚本,这个文件中会指定很多项目构建相关的配置
- proguard-rules.pro ----- 这个文件用于指定项目代码的混淆规则,当代码开发完成后打包成安装包文件,如果不希望代码被别人破解,通常会将代码进行混淆,从而让破解者难以阅读。
打开AndroidManifest.xml文件,里面含有如下代码:
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
这段代码表示对MainActivity进行注册,没有在AndroidManifest.xml里注册的Activity是不能使用的。<action android:name="android.intent.action.MAIN" />和 <category android:name ="android.intent.category.LAUNCHER" />表示MainActivity是这个项目的主Activity,在手机上点击图标,首先启动的就是这个Activtity。
打开MainActivity,代码如下:
package com.example.helloworld;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
MainActivity是继承自AppCompatActivity的。AppCompatActivity是AndroidX中提供的一种向下兼容是我Activity,可以使Activity在不同系统版本中的功能保持一致性,而Activity类是Android系统提供的一个基类,项目中所有定义的Activity都必须继承它或者它的子类才能拥有Activity的特性(AppCompatActivity是Activity的子类)。
MainActivity中有一个onCreate()方法,这个方法是一个Activity被创建时必定要执行的方法。
Android程序的设计讲究逻辑和视图分离,因此通常在布局文件中编写界面,然后在Activity中引入进来。在onCreate()方法的第二行调用了setContentView()方法,这个方法给当前的Activity引入了一个activity_main布局。
布局文件都是定义在res/layout目录下的,展开layout目录,打开activity_main.xml文件,切换到Text视图,代码如下:
<?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=".MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
其中,TextView是Android提供的一个控件,用于在布局中显示文字。
1.3.5 详解项目中的资源
res目录如下图:
其中,所有以drawable开头的文件夹都是用来放图片的;所有以mipmap开头的文件夹都是用来放应用图标的;所有以values开头的文件夹都是用来放字符串、样式、颜色等配置;layout文件夹用来放布局文件的。
这么多mipmap开头的文件夹,是为了让程序能够更好地兼容各种设备。应自行创建drawable-hdpi、drawable-xhdpi、drawable-xxhdpi等目录。在制作程序时,能够给同一张图片提供几个不同分辨率的版本,分别放在这些目录下,程序运行的时候,会自动根据当前运行设备分辨率的高低选择加载哪个目录下的图片。如果只有一份图片,把所有图片放在drawable-xxhdpi目录下,这是最主流的设备分辨率目录。
打开res/values/strings.xml文件,内容如下:
<resources>
<string name="app_name">HelloWorld</string>
</resources>
这里定义了一个应用程序名的字符串,有以下两种方式引用它。
- 在代码中通过R.string.hello_world可以获得该字符串的引用。 R.string.app_name
- 在XML中通过@string/hello_world可以获得该字符串的引用。 @string/app_name
其中,string部分可以替换,如果引用图片资源替换为drawable,引用应用图标替换成mipmap,引用布局文件替换成layout,以此类推。
例如,打开AndroidManifest.xml文件,找到如下代码:
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.HelloWorld">
...
</application>
其中,HelloWorld项目的应用图标是通过android:icon属性指定的;应用的名称是通过android:lable指定的。这里对资源的引用方式正是在XML中引用资源的语法。
1.3.6 详解build.gradle文件
Android Studio是采用Gradle来构建项目的。Gradle是一个非常先进的项目构建工具,它使用了一种基于Groovy的领域特定语言(DSL)来声明项目设置,摒弃了传统基于XML的各种繁琐配置。
HelloWorld项目中有两个build.gradle文件,一个在最外层目录下,一个在app目录下。这两个文件对构建Android Studio项目都起到了至关重要的作用。
- 最外层目录下的build.gradle文件,代码如下:
buildscript {
repositories {
google()
mavenCentral()
}
dependencies {
classpath "com.android.tools.build:gradle:7.0.4"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
其中,repositories的闭包中声明了google()和mavenCentral()这两行配置,它们分别对应了一个代码仓库,google仓库中包含的主要是Google自家的扩展依赖库,而mavenCentral仓库中包含的大多是一些第三方的开源库,声明这两行配置后,可以在项目中轻松引用任何google和mavenCentral仓库中的依赖库。
dependencies闭包中使用classpath声明了一个Gradle插件。插件版本号通常和当前Android Studio的版本是对应的。
通常情况下,并不需要修改这个文件中的内容,除非想添加一些全局的项目构建配置。
- app目录下的build.gradle文件,代码如下:
plugins {
id 'com.android.application'
}
android {
compileSdk 32
defaultConfig {
applicationId "com.example.helloworld"
minSdk 21
targetSdk 32
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'com.google.android.material:material:1.3.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
}
第一行应用了一个插件,一般有两种值可选:com.android.application表示这是一个应用程序模块,com.android.library表示这是一个库模块。两者区别是,应用程序模块可以直接运行,库模块只能作为代码库依附于别的应用程序模块来运行。
在android闭包中可以配置项目构建的各种属性。其中,compileSdk用于指定项目的编译版本。android闭包中嵌套了一个defaultConfig闭包,defaultConfig闭包中可以对项目的更多细节进行配置。其中,application用于指定项目的包名,这里可以对包名进行修改。minSdk用于指定项目最低兼容的Android系统版本。targetSdk指定的值表示在该目标版本上已经做过充分的测试,系统将会为你的应用程序启动一些最新的功能和特性。versionCode用于指定项目版本号,versionName 用于指定项目版本名,这两个属性在生成安装文件时非常重要。最后,testInstrumentationRunner用于在当前项目中启用JUnit测试,可以为当前项目编写测试用例,以保证功能的正确性和稳定性
buildTypes闭包中用于指定生成安装文件的相关配置,通常只会有两个子闭包:一个是debug ,一个是release。debug闭包用于指定生成测试版安装文件的配置,release闭包用于指定生成正式版安装文件的配置。另外,debug闭包是可以忽略不写的。release闭包中,minifyEnabled用于指定是否对代码进行混淆,true表示混淆,false表示不混淆。proguardFiles用于指定混淆时使用的规则文件,这里指定了两个文件:第一个proguard-android-optimize.txt是在<Android SDK>/tools/ proguard目录下的,里面是所有项目通用的混淆规则;第二个proguard-rules.pro是在当前项目的根目录下的,里面可以编写当前项目特有的混淆规则。需要注意的是,通过Android Studio直接运行项目生成的都是测试版安装文件,关于如何生成正式版安装文件,会在后续章节学习。
dependencies闭包的功能非常强大,它可以指定当前项目所有的依赖关系。通常Android Studio项目一共有3种依赖方式:本地依赖、库依赖和远程依赖。本地依赖可以对本地的jar包或目录添加依赖关系,库依赖可以对项目中的库模块添加依赖关系,远程依赖可以对仓库上开源项目添加依赖关系。
在dependencies闭包的配置中,implementation是远程依赖声明,androidx.appcompat: appcompat:1.2.0就是一个标准的远程依赖库格式,其中androidx.appcompat是域名部分,用于和其他公司的库做区分;appcompat是工程名部分,用于和同一个公司中不同的库工程做区分;1.2.0是版本号,用于和同一个库不同的版本做区分。加上这句声明后,Gradle在构建项目时会首先检查下本地是否已经有这个库的缓存,如果没有的话则会自动联网下载,然后再添加到项目的构建路径中。至于库依赖声明,这里没有用到,它的基本格式是implementation project后面加上要依赖的库的名称,比如implementation project(':helper')这句声明,helper为一个库模块的名字。剩下的testImplementation和androidTestImplementation都是用于声明测试用例库的,暂时用不到,先忽略。
1.4 掌握日志工具的使用
1.4.1 使用Android的日志工具Log
Android中的日志工具类是Log(android.util.Log),这个类中提供了如下5个方法来供打印日志。
- Log.v() ---- 用于打印最为琐碎的、意义最小的日志信息。对应级别verbose,是Android日志里级别最低的一种。
- Log.d() ---- 用于打印一些调试信息,这些信息对调试程序和分析问题有帮助。对应级别debug,比verbose高一级。
- Log.i() ------ 用于打印一些比较重要的数据,可以帮助分析用户行为的数据。对应级别info,比debug高一级。
- Log.w() ----- 用于打印警告信息,提示程序在这个地方可能会有潜在的风险,最好修复一下。对应级别warn,比info高一级。
- Log.e() ------ 用于打印程序中的错误信息,必须尽快修复。对应级别error,比warn高一级。
打开MainActivity,在onCreate()方法中添加一行打印日志的语句,如下所示:
package com.example.helloworld;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.d("MainActivity", "onCreate execute");
}
}
Log.d()方法中传入了两个参数:第一个参数是tag,一般传入当前的类名就好,主要用于对打印信息进行过滤;第二个参数是msg,即想要打印的具体内容。
1.4.2 为什么使用Log而不使用System.out
System.out.println()方法的缺点太多,比如日志打印不可控制、打印时间无法确定、不能添加过滤器、日志没有级别区分等。
Log和logcat配合的强大之处。首先,Logcat可以轻松添加过滤器,下图可以看到目前所有的过滤器:
目前只有3个过滤器,Show only selected application表示只显示当前选中程序的日志,Firebase 是谷歌提供的一个分析工具,可以不用管它,No Filters相当于没有过滤器,会把所有的日志都显示出来。除此之外,可以自定义过滤器,添加过滤器流程如下:
点击上图中的“Edit Filter Configuration”,会弹出一个过滤器配置界面,给过滤器取名叫data,并且让它对名为data的tag进行过滤,如下图:
点击OK,会发现已经多了一个data过滤器。当点击这个过滤器的时候,会发现刚才在onCreate()方法里打印的日志没了,这是因为data过滤器只会显示tag名为data的日志。在onCreate()方法中把打印语句改成Log.d("data", "onCreate execute"),然后再次运行程序,就会在data过滤器下看到这行日志了。
logcat中主要有5个日志级别控制,分别对应上面介绍的5个方法,如下图:
当前选中的级别是Verbose,也就是最低等级。这意味着不管使用哪一个方法打印日志,这条日志都一定会显示出来。如果奖级别选中为debug,这是只有使用debug及以上级别方法打印的日志才会显示出来,以此类推。
关键字过滤。如果使用过滤器加日志级别控制还是不能锁定想查看的日志内容,那么可以通过关键字进行进一步的过滤。如图:
可以在输入框中输入关键字的内容,这样只有符合关键字条件的日志才会显示出来,从而能够快速定位到任何想查看的日志。另外,关键字过滤是支持正则表达式的。