1.Android系统结构
(1)Linux内核:为Android设备的各种硬件提供了底层的驱动,如显示驱动、蓝牙驱动、Wi-Fi驱动。
(2)核心类库:包含系统库和Android运行时库。
系统库主要是通过C/C++库来为Android系统提供主要的特性支持,如SQLite库提供了数据库的支持,Webkit库提供了浏览器内核的支持等。
Android运行时库主要提供了一些核心库,能够允许开发者使用Java语言来编写Android应用。Android运行时库还包含了Dalvik虚拟机,它使得每一个Android应用都能运行在独立的进程中,并且拥有一个自己的Dalvik虚拟机实例。
(3)应用框架层:提供了构建应用程序时用到的各种API,例如视图(View),活动管理器(Activity Manager)
(4)应用层:就是一个软件应用。
2.四大组件
(1)Activity(活动):是所有Android应用程序的门面,凡是在应用中你看得到的东西,都是放在活动中的。
(2)Service(服务):服务就比较低调了,你无法看到它,但它一直在后台默默的运行,即使用户退出了应用,服务仍然可以继续运行。
(3)BroadcastReeiver(广播接收者):允许你的应用接收来自各处的广播消息,比如电话、短信等,你的应用也可以向外发出广播消息。
(4)ContentProvider(内容提供者):为应用程序之间共享数据提供了可能,比如你想要读取电话薄中的联系人,就需要通过内容提供器来实现。
3.SQLite数据库
Android系统自带了轻量级、运行速度极快的嵌入式关系型数据库。它不仅支持标准的SQL语法,还可以通过Android封装好的API进行操作。
4.强大的多媒体
Android系统还提供了丰富的多媒体服务如音乐,拍照,闹铃等等,这些你都可以在在程序中,通过代码进行控制。
5.地理位置定位
Android手机都内置有GPS
6.JDK
JDK是java语言的软件开发包,它包含了java的运行环境、工具集合、基础类库等内容。
7.Android SDK
是谷歌提供的Android开发工具包,需要引入该工具包,来使用Android相关的API
8.AndroidManifest.xml中的
<activity android:name=".MainActivity" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
其中intent-filter
里的两行代码非常重要,<action android:name="android.intent.action.MAIN"/>
和<category android:name="android.intent.category.LAUNCHER" />
表示MainActivity是这个项目的主Activity,在手机上点击应用图标,首先启动的就是这个Activity。
9.
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
AppCompatActivity
是AndroidX中提供的一种向下兼容的Activity,可以使Activity在不同系统版本中的功能保持一致性。而Activity类是Android系统提供的一个基类,我们项目中所有自定义的Activity都必须继承它或者它的子类才能拥有Activity的特性(AppCompatActivity
是Activity的子类)。然后可以看到MainActivity中有一个onCreate()
方法,这个方法是一个Activity被创建时必定要执行的方法。
10.res文件目录
(1)drawable:用来存放图片的
(2)mipmap:用来存放图标的
(3)values:用来存放字符串、样式、颜色等配置
(4)layout:用来存放布局文件
“mipmap”开头的目录,里面包含不同分辨率的相同的文件,其实主要是为了让程序能够更好地兼容各种设备。drawable目录也是相同的道理,虽然Android Studio没有帮我们自动生成,但是我们应该自己创建drawable-hdpi、drawable-xhdpi、drawable-xxhdpi等目录。在制作程序的时候,最好能够给同一张图片提供几个不同分辨率的版本,分别放在这些目录下,然后程序运行的时候,会自动根据当前运行设备分辨率的高低选择加载哪个目录下的图片。当然这只是理想情况,更多的时候美工只会提供给我们一份图片,这时你把所有图片都放在drawable-xxhdpi目录下就好了,因为这是最主流的设备分辨率目录。
11.res/values/strings.xml文件
<resources>
<string name="app_name">HelloWorld</string>
</resources>
可以看到,这里定义了一个应用程序名的字符串,我们有以下两种方式来引用它。
- 在代码中通过
R.string.app_name
可以获得该字符串的引用。 - 在XML中通过
@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/AppTheme">
...
</application>
12.Project:build.gradle
buildscript {
repositories {
google()
jcenter()
}
dependencies {
classpath "com.android.tools.build:gradle:4.1.1"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
google()
jcenter()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
两处repositories
的闭包中都声明了google()
和jcenter()
这两行配置,那么它们是什么意思呢?其实它们分别对应了一个代码仓库,google仓库中包含的主要是Google自家的扩展依赖库,而jcenter仓库中包含的大多是一些第三方的开源库。声明了这两行配置之后,我们就可以在项目中轻松引用任何google和jcenter仓库中的依赖库了。
dependencies
闭包中使用classpath
声明了一个Gradle插件。为什么要声明Gradle插件呢?因为Gradle并不是专门为构建Android项目而开发的,Java、C++等很多种项目也可以使用Gradle来构建,因此如果我们要想使用它来构建Android项目,则需要声明com.android.tools.build:gradle:3.5.2这个插件。其中,最后面的部分是插件的版本号,它通常和当前Android Studio的版本是对应的,比如我现在使用的是Android Studio 3.5.2版本,那么这里的插件版本号就应该是3.5.2。
13.Module:build.gradle
plugins {
id 'com.android.application'
}
android {
compileSdkVersion 30
buildToolsVersion "30.0.3"
defaultConfig {
applicationId "com.example.myapplication"
minSdkVersion 16
targetSdkVersion 30
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.2.1'
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
闭包,在这个闭包中我们可以配置项目构建的各种属性。其中,compileSdkVersion
用于指定项目的编译版本,这里指表示使用Android系统的SDK编译。buildToolsVersion
用于指定项目构建工具的版本。
然后我们看到,android
闭包中又嵌套了一个defaultConfig
闭包,defaultConfig
闭包中可以对项目的更多细节进行配置。其中,applicationId
是每一个应用的唯一标识符,绝对不能重复,默认会使用我们在创建项目时指定的包名,如果你想在后面对其进行修改,那么就是在这里修改的。minSdkVersion
用于指定项目最低兼容的Android系统版本,这里指定成21表示最低兼容到Android 5.0系统。targetSdkVersion
指定的值表示你在该目标版本上已经做过了充分的测试,系统将会为你的应用程序启用一些最新的功能和特性。比如Android 6.0系统中引入了运行时权限这个功能,如果你将targetSdkVersion
指定成23或者更高,那么系统就会为你的程序启用运行时权限功能,而如果你将targetSdkVersion
指定成22,那么就说明你的程序最高只在Android 5.1系统上做过充分的测试,Android 6.0系统中引入的新功能自然就不会启用了。接下来的两个属性都比较简单,versionCode
用于指定项目的版本号,versionName
用于指定项目的版本名。最后,testInstrumentationRunner
用于在当前项目中启用JUnit测试,你可以为当前项目编写测试用例,以保证功能的正确性和稳定性。
分析完了defaultConfig
闭包,接下来我们看一下buildTypes
闭包。buildTypes
闭包中用于指定生成安装文件的相关配置,通常只会有两个子闭包:一个是debug
,一个是release
。debug
闭包用于指定生成测试版安装文件的配置,release
闭包用于指定生成正式版安装文件的配置。另外,debug
闭包是可以忽略不写的,因此我们看到上面的代码中就只有一个release
闭包。下面来看一下release
闭包中的具体内容吧,minifyEnabled
用于指定是否对项目的代码进行混淆,true
表示混淆,false
表示不混淆。proguardFiles
用于指定混淆时使用的规则文件,这里指定了两个文件:第一个proguard-android-optimize.txt
是在<Android SDK>/tools/proguard目录下的,里面是所有项目通用的混淆规则;第二个proguard-rules.pro
是在当前项目的根目录下的,里面可以编写当前项目特有的混淆规则。需要注意的是,通过Android Studio直接运行项目生成的都是测试版安装文件,关于如何生成正式版安装文件,我们将会在第15章中学习。
这样整个android
闭包中的内容就都分析完了,接下来还剩一个dependencies
闭包。这个闭包的功能非常强大,它可以指定当前项目所有的依赖关系。通常Android Studio项目一共有3种依赖方式:本地依赖、库依赖和远程依赖。本地依赖可以对本地的jar包或目录添加依赖关系,库依赖可以对项目中的库模块添加依赖关系,远程依赖则可以对jcenter仓库上的开源项目添加依赖关系。
观察一下dependencies
闭包中的配置,第一行的implementation fileTree
就是一个本地依赖声明,它表示将libs目录下所有.jar后缀的文件都添加到项目的构建路径中。而implementation
则是远程依赖声明,androidx.appcompat:appcompat:1.1.0
就是一个标准的远程依赖库格式,其中androidx.appcompat
是域名部分,用于和其他公司的库做区分;appcompat
是工程名部分,用于和同一个公司中不同的库工程做区分;1.1.0
是版本号,用于和同一个库不同的版本做区分。加上这句声明后,Gradle在构建项目时会首先检查一下本地是否已经有这个库的缓存,如果没有的话则会自动联网下载,然后再添加到项目的构建路径中。至于库依赖声明这里没有用到,它的基本格式是implementation project
后面加上要依赖的库的名称,比如有一个库模块的名字叫helper,那么添加这个库的依赖关系只需要加入implementation project(':helper')
这句声明即可。关于这部分内容,我们将在本书的最后一章学习。另外剩下的testImplementation
和androidTestImplementation
都是用于声明测试用例库的,这个我们暂时用不到,先忽略它就可以了。
14.日志工具Log
Log.v()
。用于打印那些最为琐碎的、意义最小的日志信息。对应级别verbose,是Android日志里面级别最低的一种。Log.d()
。用于打印一些调试信息,这些信息对你调试程序和分析问题应该是有帮助的。对应级别debug,比verbose高一级。Log.i()
。用于打印一些比较重要的数据,这些数据应该是你非常想看到的、可以帮你分析用户行为的数据。对应级别info,比debug高一级。Log.w()
。用于打印一些警告信息,提示程序在这个地方可能会有潜在的风险,最好去修复一下这些出现警告的地方。对应级别warn,比info高一级。Log.e()
。用于打印程序中的错误信息,比如程序进入了catch
语句中。当有错误信息打印出来的时候,一般代表你的程序出现严重问题了,必须尽快修复。对应级别error,比warn高一级。
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
Log.d("MainActivity", "onCreate execute")
}
}
Log.d()
方法中传入了两个参数:第一个参数是tag
,一般传入当前的类名就好,主要用于对打印信息进行过滤;第二个参数是msg
,即想要打印的具体内容。
Logcat中可以很轻松地添加过滤器,你可以在图1.28中看到我们目前所有的过滤器。
目前只有3个过滤器,Show only selected application表示只显示当前选中程序的日志; Firebase是Google提供的一个开发者工具和基础架构平台,我们可以不用管它;No Filters相当于没有过滤器,会把所有的日志都显示出来。那可不可以自定义过滤器呢?当然可以,我们现在就来添加一个过滤器试试。
点击图1.28中的“Edit Filter Configuration”,会弹出一个过滤器配置界面。我们给过滤器起名叫data,并且让它对名为data的tag进行过滤,如图1.29所示
点击“OK”,你会发现多出了一个data过滤器。当选中这个过滤器的时候,刚才在onCreate()
方法里打印的日志就不见了,这是因为data这个过滤器只会显示tag名称为data的日志。你可以尝试在onCreate()
方法中把打印日志的语句改成Log.d("data", "onCreate execute")
,然后再次运行程序,你就会在data过滤器下看到这行日志了。
Logcat中的日志级别
当前我们选中的级别是Verbose,也就是最低等级。这意味着不管我们使用哪一个方法打印日志,这条日志都一定会显示出来。而如果我们将级别选中为Debug,这时只有我们使用Debug及以上级别方法打印的日志才会显示出来,以此类推。你可以做一下实验,当你把Logcat中的级别选中为Info、Warn或者Error时,我们在onCreate()
方法中打印的语句是不会显示的,因为我们打印日志时使用的是Log.d()
方法。
日志级别控制的好处就是,你可以很快地找到你所关心的那些日志。
关键字输入框
我们可以在输入框里输入关键字的内容,这样只有符合关键字条件的日志才会显示出来,从而能够快速定位到任何你想查看的日志。另外,还有一点需要注意,关键字过滤是支持正则表达式的,有了这个特性,我们就可以构建出更加丰富的过滤条件。