文章目录


android项目目录结构
  • .gradle和.idea
  • app
  • build
  • libs
  • androidTest
  • java
  • res
  • AndroidManifest.xml
  • test
  • .gitignore
  • app.iml
  • build.gradle
  • proguard-rules.pro
  • HelloWorld项目中的Activity代码:
  • build
  • gradle
  • .gitignore
  • build.gradle
  • gradle.properties
  • gradlew和gradlew.bat
  • HelloWorld.iml
  • local.properties
  • settings.gradle
  • 日志
  • 用法
  • Intent
  • 显式intent
  • 隐式intent
  • 使用intent调用系统浏览器
  • 使用Intent在activity之间互传消息
  • 解决gradle同步失败问题
  • 安卓开发之Toast
  • 接下来我们给app设计mean
  • 在res(resource)目录下创建一个mean文件夹
  • 在mean文件夹中新建一个资源文件main.xml
  • 我们再来说一下如何销毁一个活动,其实销毁一个活动可以很简单,直接点击手机上的返回按钮即可
  • 当然了,一个正儿八经的app,肯定是不止一个活动的,下面我们就来说一下,如何在多个activity间进行切换
  • 隐式Intent
  • 下面我们来编写自己的Intent
  • 隐式Intent还有其他的玩法
  • data标签的schema有很多候选值,除了http,还有其他的,geo表示显示地理位置、 tel表示拨打电话
  • 在活动之间传递数据
  • 在上面我们已经实现了从当前活动向第二个活动传递字符串,下面我们来讲一下如何给上一个活动回传消息
  • 作为第二章的结束,我们来讲一下活动的生命周期:
  • 活动的生存周期activity的七个方法,覆盖了活动周期的每个时期
  • 我们来用一个项目演示一下生命周期的完整过程
  • 活动的启动模式
  • standard模式
  • singleTop模式
  • singleTask模式
  • singleInstance模式
  • 用户界面设计
  • 常见控件的使用方法
  • TextView
  • Button
  • EditText



android项目目录结构

[外链图片转存失败(img-eKexNEYB-1562759891041)(https://i.imgur.com/3Qxl3gr.png)]

.gradle和.idea

AS(Android Studio)默认生成的文件

app

项目中的代码以及各种资源

目录讲解

android  安卓开发讲解_AndroidStudio

build

和外头的那个build目录一样,保存的也是一些编译时自动生成的文件

libs

程序中使用的第三方jar包都会放在这个目录下面,该目录下的jar包会被自动添加到构建路径中去

androidTest

编写AndroidTest测试用例,对项目进行一些自动化测试

java

放置我们项目中的java代码,我们的​​HelloWorldActivity​​​类就在这个文件夹的​​com.example.a123.helloworld​​包下

res

android  安卓开发讲解_AndroidStudio_02

项目中会用到的资源:图片、布局、字符串…

图片会放在drawable目录下,布局会放在layout目录下,字符串会放在values目录下

该目录下所有以mipmap开头的文件夹都是用来放​应用图标​的,所有以​value​开头的文件夹都是用来放​字符串、样式、颜色​等配置的,​layout​文件夹是用来​放布局文件​的

之所以会有那么多的mipmap开头的文件夹,主要是为了让程序能够更好地兼容各种设备

AndroidManifest.xml

我们的Android项目的配置文件,程序中定义的​四大组件都需要在这个文件里注册​,还​可以在这个文件中给应用程序添加权限声明

test

这个目录是用来编写UnitTest测试用例的,是​对项目进行自动化测试的另一种方式

.gitignore

用于将app模块内的指定目录和文件排除在版本控制外

app.iml

InteliJ IDEA项目自动生成的文件

build.gradle

app模块的gradle构建脚本,这个文件会指定很多项目构建相关的配置

AS使用Gradle来构建项目,Gradle是一个非常先进的项目构建工具,它使用一种基于Groovy的领域特定语言(DSL)来声明项目设置,摒弃了传统基于XML(如Ant/Maven)的各种繁琐配置

外层也有一个build.gradle文件,但是两者的内容是不一样的,对外层的介绍放在了外层文件详解部分,文件内容如下:

apply plugin: 'com.android.application'

android {
compileSdkVersion 24
buildToolsVersion "28.0.3"
defaultConfig {
applicationId "com.example.a123.helloworld"
minSdkVersion 15
targetSdkVersion 24
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}

dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
compile 'com.android.support:appcompat-v7:24.2.1'
testCompile 'junit:junit:4.12'
}

第一行用用了一个插件,一般有两种选择,一种是​​com.android.application​​​,另一种是​​com.android.library​​,前者标识这是一个​应用程序模块​,后者表示这是一个​库模块

下面的android闭包中,声明了我们的项目构建的各种属性,其中的​​buildTypes​​​闭包用于指定生成的安装文件的相关配置,release闭包和debug闭包分别指定正式版本和发布版本的配置,debug是可以忽略不写的,​​minifyEnabled​​​指定是否对代码进行混淆,​​proguardFiles​​​指定代码混淆规则,后面跟了两个文件,SDK中自带的和当前目录下的自定义的​​proguard-rules.pro​

最后一个闭包是​​dependencies​​​,他的功能比较强大,他可以指定当前项目所有的依赖关系,通常​​AndroidStudio​​项目​一共有3中依赖方式:本地依赖、库依赖和远程依赖


  • 本地依赖可以对本地的jar包或目录添加依赖关系
  • 库依赖可以对项目中的库模块添加依赖关系
  • 远程依赖则可以对​​jcenter​​库上的开源项目添加依赖关系

我们观察上面的​​build.gradle​​​中的​​dependencies​​闭包,其中


  • 第一行就是一个本地依赖,他表示将libs目录下的所有jar文件都添加到项目的构建路径中
  • 第​​行的compile是远程依赖声明,​​com.android.support​​​是域名部分,用于区分公司,​​appcompat-v7​​​是组名称,用于区分同一个公司的不同库,​​24.2.1​​是版本号,用于区分同一个库中的不同版本,​我们在构建项目时,Gradle会检查本地是否有该库的缓存,如果没有就需要联网下载并添加到项目的构建路径中

​proguard-rules.pro​

这个是用来指定代码的混淆规则的,​进行代码混淆的目的就是为了不希望被别人破解,使得代码的阅读性几乎为0

HelloWorld项目中的Activity代码:

//继承的这个Activity是一种向下兼容的Activity,最低兼容到Android2.1
public class HelloWorldActivity extends AppCompatActivity {

@Override
//该方法必须进行重写,他是一个活动启动时首先调用的方法
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//所谓逻辑和视图分离,就意味着我们尽最大可能的不在业务代码中进行硬编码
//比如这个,我们并没有把HelloWorld写到代码中,而是放到了布局文件中
setContentView(R.layout.hello_world_layout);
}
}

build

编译时自动生成的文件

gradle

graddle wrapper的配置文件,gradle wrapper默认处于关闭状态

.gitignore

用于将指定的目录或文件排除在版本控制之外,属于版本控制系统的配置文件

build.gradle

项目的全局gradle构建脚本,一般不进行改动

在我们的HelloWorld项目中,外层的build.grandle文件内容如下:

buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.2.0'
}
}

allprojects {
repositories {
jcenter()
}
}

task clean(type: Delete) {
delete rootProject.buildDir
}

里面的jcenter是一个Android开源代码托管仓库,声明了这个之后我们就可以在自己的Android项目中引入任何jcenter上的开源项目了

上面的dependencies闭包中使用classpath声明了一个Gradle插件,因为Gradle并不是专门为Android开发的,它是一个通用的构建工具,其他语言开发的项目也可以使用Gradle,如果我们想要使用Gradle来构建Android项目,就需要声明这个插件

gradle.properties

全局的gradle配置文件,这个文件的改动会影响到项目中所有的gradle编译脚本

gradlew和gradlew.bat

用于在命令行界面中执行gradle命令,其中​​gradlew​​是在Mac/Linux中使用的脚本

HelloWorld.iml

iml文件是所有​​IntelliJ IDEA​​​项目都会自动生成的一个文件,AS是基于​​IntelliJ IDEA​​​开发的,它就是用来标识该项目是一个​​IntelliJ IDEA​​项目,IDEA中的A代表Apache许可证

local.properties

用于指定本机中的Android SDK路径,通常为自动生成的文件,无需改动,​除非我们的SDK的位置发生了变化

settings.gradle

用于指定项目中所有引入的模块,由于HelloWorld项目中就只有一个app模块,因此该文件中也就只能引入这一个app模块,通常情况下模块都是自动引入的,因此该文件也不需要我们手动更改

日志

Android中的日志工具类是Log(android.util.Log) , 这个类中提供了如下5个方法来供我们打印

日志。

Log.v() 。 用于打印那些最为琐碎的、 意义最小的日志信息。 对应级别verbose, 是

Android日志里面级别最低的一种。

Log.d() 。 用于打印一些调试信息, 这些信息对你调试程序和分析问题应该是有帮助的。

对应级别debug, 比verbose高一级。

Log.i() 。 用于打印一些比较重要的数据, 这些数据应该是你非常想看到的、 可以帮你分

析用户行为数据。 对应级别info, 比debug高一级。

Log.w() 。 用于打印一些警告信息, 提示程序在这个地方可能会有潜在的风险, 最好去修

复一下这些出现警告的地方。 对应级别warn, 比info高一级。

Log.e() 。 用于打印程序中的错误信息, 比如程序进入到了catch语句当中。 当有错误信息

打印出来的时候, 一般都代表你的程序出现严重问题了, 必须尽快修复。 对应级别error,

比warn高一级。

用法

protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.hello_world_layout);
Log.d("HelloWorldActivity", "onCreate execute");
}

Log.d() 方法中传入了两个参数:第一个参数是tag , 一般传入当前的类名就好, 主要用于对

打印信息进行过滤;第二个参数是msg , 即想要打印的具体的内容。

在android monitor处查看logcat日志

android  安卓开发讲解_ide_03

Intent

显式intent

button1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(FirstActivity.this, SecondActivity.class);
startActivity(intent);
}
});

直接显示调用,指明了让secondActivity来响应该intent,intent构造方法中的第一个参数为上下文环境

隐式intent

隐式intent就是在manifest.xml文件中添加intent-filter标签

<intent-filter>
//action标签指定当前活动可以响应哪些action
<action android:name="com.example.a123.myapplication.ACTION_START" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="com.example.a123.myapplication.MY_CATEGORY"/>
</intent-filter>

然后在别的activity的业务代码中

button1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent("com.example.a123.myapplication.ACTION_START");
intent.addCategory("com.example.a123.myapplication.MY_CATEGORY");
startActivity(intent);
}
});

生成一个Intent对象,如果有满足该条件的activity存在,那么该activity就会对该intent进行响应

使用intent调用系统浏览器

import android.net.Uri;

button1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse("http://www.baidu.com"));
startActivity(intent);
}
});

调用拨号程序

Intent intent = new Intent(Intent.ACTION_DIAL);
intent.setData(Uri.parse("tel:10086"));
startActivity(intent);

使用Intent在activity之间互传消息

activity1:

button1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String data = "Hello SecondActivity";
Intent intent = new Intent(FirstActivity.this, SecondLayoutActivity.class);
intent.putExtra("caonima", data);
startActivity(intent);
}
});

activity2:

Intent intent = getIntent();
String data = intent.getStringExtra("caonima");
Log.d("SecondActivity", data);

caonima是键,data变量是键值

在activity1中点击按钮之后,可以在logcat的debug看到activity1发送给activity2的消息

android  安卓开发讲解_控件_04

解决gradle同步失败问题

最近把电脑重装成了ubunut,然后安装了android-studio,但是gradle sync老是失败

解决方式如下:

vi ~/.gradle.gradle.properties

内容如下:

## For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
#
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
# Default value: -Xmx1024m -XX:MaxPermSize=256m
# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
#
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
#Mon May 06 19:39:05 CST 2019
#systemProp.https.proxyPort=1080
systemProp.http.proxyHost=127.0.0.1
#systemProp.https.proxyHost=127.0.0.1
systemProp.http.proxyPort=1080
~

注释掉https部分即可

安卓开发之Toast

toast意为干杯,祝酒,在android中他就是一个用来向用户通知消息的小东西,当我们在app中定义完toast按钮后,点击这个按钮,就会在屏幕上显示一段我们自己定义的消息

在mainactivity.java的onCreate方法中添加如下代码:

super.onCreate(savedInstanceState);
setContentView(R.layout.first_layout);
Button button1 = (Button) findViewById(R.id.button_1);
button1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(FirstActivity.this, "You clicked Button 1",
Toast.LENGTH_SHORT).show();
}
});

创建一个按钮监听,触发的事件是onClick方法中的行为

我们定义一个onClick方法,调用Toast的静态方法:makeText,创建一个Toast对象,并调用该对象的show方法将其显示出来

效果如下:

android  安卓开发讲解_android_05

接下来我们给app设计mean

在res(resource)目录下创建一个mean文件夹

在mean文件夹中新建一个资源文件main.xml

内容如下:

<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/add_item"
android:title="Add"/>
<item
android:id="@+id/remove_item"
android:title="Remove"/>
</menu>

我们添加了两个菜单项,一个名为Add,一个名为Remove,我们还的在mainactivity.java文件中重写onCreateOptionsMenu方法来将我们的菜单显示出来,快捷键Ctrl+O,要先将光标定位到MainActivity类体才可以,然后选择我们要重写的方法即可

添加如下代码:

public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main, menu);
return true;
}

现在菜单应该可以显示出来了,但光显示是不够的,我们要让他有效果,就使用刚刚的Toast吧,为这两个菜单项设置监听:

public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.add_item:
Toast.makeText(this, "你点击了Add菜单", Toast.LENGTH_SHORT).show();
break;
case R.id.remove_item:
Toast.makeText(this, "你点击了Remove菜单", Toast.LENGTH_SHORT).show();
break;
default:
}
return true;
}

android  安卓开发讲解_ide_06

我们再来说一下如何销毁一个活动,其实销毁一个活动可以很简单,直接点击手机上的返回按钮即可

不过我们是程序员,当然要通过代码来实现活动的销毁,设置一下菜单按钮的监听函数:

public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.add_item:
Toast.makeText(this, "你点击了Add菜单", Toast.LENGTH_SHORT).show();
break;
case R.id.remove_item:
finish();
break;
default:
}
return true;
}

直接调用基类Activity的finish方法即可

android  安卓开发讲解_ide_07

当然了,一个正儿八经的app,肯定是不止一个活动的,下面我们就来说一下,如何在多个activity间进行切换

关键就是​Intent

intent意为趋势、意图、目的

首先,我们在原来的基础上再创建一个活动

然后我们在第一个活动的代码中编写启动第二个活动的代码:

启动活动可以使用startActivity方法,该方法需要提供一个Intent对象,该对象的主要作用就是指明具体要启动哪一个活动,所以我们就需要先构造一个Intent对象,构造方式如下:

Intent intent = new Intent(FirstActivity.this, Main2Activity.class);

第一个参数为上下文环境,其实就是将要启动的活动启动之前的那个活动的实例,第二个参数很明显就是我们想要启动的活动

android  安卓开发讲解_AndroidStudio_08

可以看到,这种Intent的意图已经非常明显了,直接指定了要启动的活动,那么下面我们来介绍一下隐式Intent

隐式Intent

隐式Intent不会直接指明要启动哪一个活动,​而是使用一系列的条件来限制要启动的活动需要满足的条件

具体一点就是在AndroidManifest.xml文件中的intent-filter标签内使用action和category标签来限制要启动的活动的属性,由系统来分析具体要启动哪一个活动,如果有多个活动匹配,那么系统就会列出所有候选项,效果大概是下面这样:

android  安卓开发讲解_android_09

下面我们来编写自己的Intent

我们在AndroidManifest.xml文件中添加的标签如下:

<intent-filter>
<action android:name="com.example.x.myapplication.ACTION_START" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>

intent-filter标签中的两个子标签:action和category,action标签约束了该活动需要能够响应ACTION_START

按钮代码:

Intent intent = new Intent("com.example.x.myapplication.ACTION_START");
startActivity(intent);

我们创建的Intent对象传入了​​com.example.x.myapplication.ACTION_START​​这个参数,说明我们想要启动的活动需要能够响应ACTION_START这个action,根据我们的设置,就会启动第二个活动

category标签允许我们自定义响应活动的规则,下面我们就通过一个例子来体会一下category的用法

我们将刚才的菜单响应代码改成下面这样:

Intent intent = new Intent("com.example.x.myapplication.ACTION_START");
intent.addCategory("com.example.x.myapplication.MY_CATEGORY");
startActivity(intent);

我们调用Intent对象的addCategory方法添加一个category,这样只有category标签的值为com.example.x.myapplication.MY_CATEGORY的活动才会被启动,运行结果如下:

程序如我们预料,崩溃掉了,并且AS返回如下错误:

​android.content.ActivityNotFoundException: No Activity found to handle Intent { act=com.example.x.myapplication.ACTION_START cat=[com.example.x.myapplication.MY_CATEGORY] }​

很明显,没有任何活动能够满足该Intent的要求,因为我们给第二个活动设置的category的值为DEFAULT

可以看到,我们是可以利用category来自定义规则的,要想程序运行正常,只需要将AndroidManifest.xml中的DEFAULT改为MY_CATEGORY即可

隐式Intent还有其他的玩法

使用Intent启动我们的程序意外的程序的活动

修改菜单点击事件如下:

Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse("http://www.csdn.net"));
startActivity(intent);

使用Uri对象的parse方法将一段字符串转换成一个Uri对象,并传给setData方法,这样当我们点击菜单,启动第二个活动时,就会使用浏览器打开该网址,效果如下:

android  安卓开发讲解_AndroidStudio_10

其实只要我们在自己的AndroidManifest.xml文件中声明了action标签的值为android.intent.action.VIEW,category值为android.intent.category.DEFAULT,那么我们自己的活动也是可以响应打开浏览器并浏览网页的活动的(​与我们能不能够真正做到无关,我们只要声明了,就会出现在候选应用的列表中​)

我们将第二个活动的intent-filter标签设置成上面那样

另外,我们还需要使用data标签指定活动能够响应什么样的数据类型

<data android:scheme="http" />

这个就是响应协议类型为http的数据(parse方法的参数中的协议部分为http才可以响应应)

声明之后在运行app的效果如下:

android  安卓开发讲解_ide_11

data标签的schema有很多候选值,除了http,还有其他的,geo表示显示地理位置、 tel表示拨打电话

举一个打电话的例子:

我们修改mainActivity中的菜单选项事件,代码如下:

Intent intent = new Intent(Intent.ACTION_DIAL);
intent.setData(Uri.parse("tel:110"));
startActivity(intent);

效果如下:

android  安卓开发讲解_AndroidStudio_12

在活动之间传递数据

我们可以使用Intent从当前活动传递数据到第二个活动,使用的方法是putExtra,下面我们演示一下,如何把一个字符串传递到第二个活动中:

String data = "Hello SecondActivity";
Intent intent = new Intent(MainActivity.this, Main2Activity.class);
intent.putExtra("存储数据的键", data);
startActivity(intent);

putExtra方法的使用相当简单,只需要输入​键和值​即可,键可以为中文,这里只是为了演示,实际开发中应以英文为佳

然后我们在第二个活动的OnCreate方法中使用getStringExtra方法来获取从第一个活动中传递过来的数据

super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
Intent intent = getIntent();
String data = intent.getStringExtra("存储数据的键");

Toast.makeText(Main2Activity.this, "从第一个活动中传递过来的数据为"+data,
Toast.LENGTH_SHORT).show();

效果如下:

android  安卓开发讲解_ide_13

在上面我们已经实现了从当前活动向第二个活动传递字符串,下面我们来讲一下如何给上一个活动回传消息

使用​startActivityForResult​方法可以让我们在当前活动被销毁的时候返回一个结果给上一个活动

我们在启动第二个活动的时候,使用startActivityForResult来进行启动

修改菜单按钮代码如下:

Intent intent = new Intent(FirstActivity.this, SecondActivity.class);
startActivityForResult(intent, 1);

该方法的第二个参是一个请求码,可以是任意的值,只要保持其唯一即可

我们在第二个活动中设置一个按钮,并在该按钮的相应逻辑中添加返回信息的代码

Intent intent = new Intent();
intent.putExtra("存储数据的键", "返回给上一个活动的字符串");
setResult(RESULT_OK, intent);
finish();

还是使用Intent对象的outExtra犯法,然后再使用Activity类的setResult方法向上一个活动返回带有数据的intent对象,第一个参数表示处理的结果

当第二个活动被销毁之后,上一个活动的onActivityResult方法会被调用,因此我们需要在第一个活动中重写该方法:

protected void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
case 1:
if (resultCode == RESULT_OK) {
Toast.makeText(MainActivity.this, "从第二个活动中返回的字符串"+data.getStringExtra("存储数据的键"),
Toast.LENGTH_SHORT).show();
}
break;
default:
}
}

switch case语句中的case 1就是我们的requestCode,这个是自定义的,这样就可以处理多个不同的消息

效果如下:

android  安卓开发讲解_ide_14

当然如果用户不是乖乖地点击按钮,而是直接使用返回按钮,也是可以接收到返回的字符串的,不过需要在​第二个活动中重写onBackPressed方法:

public void onBackPressed() {
Intent intent = new Intent();
intent.putExtra("data_return", "Hello FirstActivity");
setResult(RESULT_OK, intent);
finish();
}

作为第二章的结束,我们来讲一下活动的生命周期:

活动状态有四种:



运行状态:
顾名思义就是在栈顶,完全可见



暂停状态

活动不在栈顶,但是依然可见,比如现在弹出了一个对话框,那么这个对话框对应的活动就是在栈顶了,但是此时原来的活动还是可见的,因为对话框不会占用整个屏幕


停止状态
活动已经不再位于栈顶,而且处于完全不可见的状态,之后该活动就是处于​停止状态​,虽然系统还会为其保存相应的状态和成员变量,但是当其他地方需要内存的时候,该活动的内存空间是有可能被系统收回的



销毁状态
已经不在返回栈中,这种活动的内存空间是系统优先收回的



活动的生存周期activity的七个方法,覆盖了活动周期的每个时期


  • onCreate
    在活动第一次被创建时调用
  • onStart
    在活动由不可见变为可见时被调用
  • onResume
    比如说对话框完成之后被销毁
  • onPause
    在启动或者恢复另一个活动的时候调用
  • onStop (他和上面onPause的区别在于如果启动的是一个小对话框,那么onPause被调用,但是onStop是不会被调用的)
    在活动完全不可见的时候调用
  • onDestroy
    在活动被销毁之前调用,调用之后活动会变成销毁状态
    onRestart
    由停止状态变成运行状态时调用

除了onRestart方法外,其他的方法都是两两相对的create-destroy、start-stop、resume-pause因此我们又可以将活动的生存周期依据这几个方法分成三部分

  • 完整生存周期
    onCreate<—>onDestroy之间
    可见生存期
    onStart<—>onStop
    前台生存期
    onResume<—>onPause

android  安卓开发讲解_AndroidStudio_15

我们来用一个项目演示一下生命周期的完整过程

在该项目中我们创建一个主活动,然后再创建两个子活动,一个是普通的活动,一个是对话框活动

要将活动设置为对话框形式,将AndroidManifest.xml文件改动如下即可:

<activity android:name=".DialogActivity" 
android:theme="@style/Theme.AppCompat.Dialog">
</activity>

可以看到我们给DialogActivity的activity标签加了一个android:theme属性,并将其值设置为**@style/Theme.AppCompat.Dialog**,这样活动DialogActivity就是一个对话框了

使用的主题@style/Theme.AppCompat.Dialog是系统内置主题

我们在MainActivity的代码中重写了7个生命周期方法,在每次生命周期方法被调用时都会进行输出:

@Override
protected void onStart() {
super.onStart();
Log.d(TAG, "onStart");
}

@Override
protected void onResume() {
super.onResume();
Log.d(TAG, "onResume");
}

@Override
protected void onPause() {
super.onPause();
Log.d(TAG, "onPause");
}

@Override
protected void onStop() {
super.onStop();
Log.d(TAG, "onStop");
}

@Override
protected void onDestroy() {
super.onDestroy();
Log.d(TAG, "onDestroy");
}

@Override
protected void onRestart() {
super.onRestart();
Log.d(TAG, "onRestart");
}

android  安卓开发讲解_android_16

下面讨论生命周期中的一种特殊情况,比如上面的例子,我们点击START NORMALACTIVITY按钮之后,mainActivity就进入​停止​状态了,此时如果系统内存不足,则mainActivity是有可能被系统回收的,这样当NORMALACTIVITY返回后,会产生什么样的后果呢?这时候系统会重新启动mainActivity,如果真的是上面的例子,那还好一些,但是如果mainActivity有输入信息呢?这样一来mainActivity先前输入的信息就都没了,解决办法就是​使用onSaveInstanceState()方法来保存数据​,该方法携带一个Bundle类型的参数,并将数据保存在该参数中,该参数提供了保存数据的方法:putString()、putInt()等,使用键值对的方式进行数据的保存

@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
String tempData = "需要进行保存的数据";
outState.putString("键", tempData);
}

系统在活动启动的时候会自动调用onCreate方法,该方法接收一个Bundle类型的参数,我们就可以从这个参数来恢复我们的数据

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, "onCreate");
setContentView(R.layout.activity_main);
if (savedInstanceState != null) {
String tempData = savedInstanceState.getString("键");
Log.d(TAG, tempData);
}
}

经测试,onSaveInstanceState方法会在onPause和onStop之间被调用

活动的启动模式

在实际项目中,我们需要根据实际需要来选择使用不同的方式来启动活动,启动模式一共有4种

standard模式

  • 默认的启动模式
  • 系统不会理会该活动是否在返回栈中(也就是说系统不管该活动是否已经启动(拥有了一个实例),每次启动都会再为该活动创建一个新的实例)
    我们的测试代码:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d("standard模式测试", this.toString());
setContentView(R.layout.activity_main);
Button button1 = (Button) findViewById(R.id.button1);
button1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(MainActivity.this, MainActivity.class);
startActivity(intent);
}
});
}

在MainActivity中启动MainActivity,logcat输出如下:

06-11 20:57:44.938 15493-15493/com.example.activitylifecycletest D/standard模式测试: com.example.activitylifecycletest.MainActivity@2012912
06-11 21:00:18.090 15493-15493/com.example.activitylifecycletest D/standard模式测试: com.example.activitylifecycletest.MainActivity@bc6a1e9
06-11 21:00:21.693 15493-15493/com.example.activitylifecycletest D/standard模式测试: com.example.activitylifecycletest.MainActivity@c0726a6
06-11 21:00:22.725 15493-15493/com.example.activitylifecycletest D/standard模式测试: com.example.activitylifecycletest.MainActivity@55937bf

可以清楚地看到,每一次点击按钮都会再生成一个MainActivity实例,这当然是我们要避免的,方法就是使用单例模式

singleTop模式

使用这个模式来启动活动就不会出现standard那样的情况,当系统发现返回栈中已经存在该活动时,就不会再重新创建该活动的实例了

我们只需要在AndroidManifest.xml中设置活动的启动模式为singleTop即可:

<activity android:name=".HelloWorldActivity"
android:launchMode="singleTop">

android  安卓开发讲解_ide_17

可以看到,无论我们怎么点击按钮,HelloWorldActivity还是只有一个实例

singleTask模式

上面的singleTop模式可以解决活动位于栈顶时被多次创建出新实例的问题,但是如果活动不在栈顶呢?​使用singleTask模式启动可以限制活动在整个应用程序中只会有一个实例

将启动模式修改为singleTask,观察效果:

android  安卓开发讲解_ide_18

可以看到主活动调用的是onRestart方法而不是onCreate方法,在第二个活动中点击完按钮后,会直接退回到主活动中,也就是说第二个活动点击完按钮后就被弹出返回栈的栈顶了,再次点击返回按钮,应用程序就会退出.

singleInstance模式

该模式是四种模式中最复杂的一个,指定singleInstance启动方式的活动会使用一个全新的返回栈来管理自己,这么设计的意义如下:

如果我们的陈序中的某个活动会被其他的程序调用,这时我们的程序就要和别的程序共享这个活动的实例,那么这时singleInstance启动方式就派上用场了,他会新开辟出一个返回栈,专门用来与其他的程序共享

06-12 11:02:47.803 22060-22060/? D/ffffflag第一个活动:: Task id is 13
06-12 11:02:59.187 22060-22060/com.example.a123.helloworld D/ffffflag第二个活动:: Task id is 14
06-12 11:03:01.158 22060-22060/com.example.a123.helloworld D/ffffflag第三个活动:: Task id is 13

可以看到,由于第二个活动是使用singleInstance模式启动的,task id是不同于活动一和活动三的

android  安卓开发讲解_AndroidStudio_19

而且当我们从活动三那里按返回键之后,会直接退回到活动一,这是因为活动三和活动一是在一个返回栈中的,与活动二无关,我们到了活动一之后,再按返回键就会到活动二,因为活动一所在的返回栈已经空了,所以就直接显示了另一个返回栈的栈顶活动,也就是活动二

用户界面设计

常见控件的使用方法

TextView

其实使用很简单我们主要介绍控件的几个主要属性:


  • ​android:id="@+id/text_view"​
  • ​id​​​属性为控件分配一个唯一的​​ID​
  • ​android:layout_width="match_parent"​
  • ​android:layout_height="wrap_content"​
  • 上面两个属性分别指定宽高为​适应父控件和适应文字内容(高度能够包裹文本即可)
  • ​android:text="测试TextView"​
  • ​text​​用于指定控件中的文本内容
  • ​android:gravity="center"​
  • ​gravity​​用于指定控件中文字的对齐方式

我们将​​activity_main.xml​​改成如下内容:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/text_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="TextView testing..."
android:gravity="center"/>
</LinearLayout>

运行效果如下:

android  安卓开发讲解_ide_20

因为我们将​​TextView​​的高度改成了适应屏幕,且对齐方式为居中,所以控件中的文字就会显示在屏幕正中央


  • ​android:textSize="100sp"​

    • 指定文字大小

  • ​android:textColor="#00ff00"​

    • 指定文字颜色
      感受一下​​100sp​​有多大:
      android  安卓开发讲解_控件_21


可以说是很大了

其他的属性不介绍了,到时间查阅文档即可

Button

可以说Button是一个1很重要的控件了,在真正的应用程序中我们会很频繁地使用到这个控件

不过他的属性和​​TextView​​差不了多少,也是那几个

需要注意的一点是,​​Button​​​控件的​​Text​​属性,​默认是会把其中的文本中的字母全部转换成大写字母

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="测试but默认ton控件" />
</LinearLayout>

运行效果如下:

android  安卓开发讲解_ide_22

可以看到我们的小写字母都被转换成了大写字母

我们可以通过配置​​textAllCaps​​​属性来禁用​​android:textAllCaps="false"​

通常我们都是按照如下的方法来为按钮设置监听的:

Button button = (Button) findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 在此处添加逻辑
}
});

一旦我们点击了按钮,​​onClick​​方法中的代码就会被执行,这是我们通常的实现方法,通过​匿名类​来实现​​OnClickListener​​​接口中的​​onClick​​方法,当然我们也可以直接使用正常的形式来实现接口中的方法,​不过要在我们的主类上面加上这一条声明:​​implements View.OnClickListener​

@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.button:
// 在此处添加逻辑
break;
default:
break;
}
}

EditText

通俗地说,就是一个编辑框

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<EditText
android:id="@+id/edit_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
</LinearLayout>

android  安卓开发讲解_android_23

我们知道在​​html​​​中,输入框有一个​​placeholder​​​属性,同样的,​​android​​​的​​EditText​​​也有类似的属性:​​android:hint​

android  安卓开发讲解_AndroidStudio_24

现在有个问题,就是当我们一直不停的输入时,就会变成下面这样:

android  安卓开发讲解_android_25

可以看到我们输入的文本直接全部都显示出来了,我们可以使用​​maxLines​​属性来限制文本的行数,比如我们限制为两行,当我们输入的内容超过两行时就会变成上下滚动的效果:

android  安卓开发讲解_ide_26

下面再来说一下如何获取​​EditText​​中的文本

private EditText editText;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button button = (Button) findViewById(R.id.button);
editText = (EditText) findViewById(R.id.edit_text);
button.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.button:
String inputText = editText.getText().toString();
Toast.makeText(MainActivity.this, inputText,
Toast.LENGTH_SHORT).show();
break;
default:
break;
}
}

只要使用​​EditText​​的​​getText​​方法即可获取到​​EditText​​控件中的文本:

android  安卓开发讲解_ide_27