今天,我们来研究一下Intent,没错,就是前面说过的比较难理解的那个东西,希望通过这篇文章之后,你发现前面那句话其实是不对的。
前文中说过,Intent就像Activity之间的双面胶,就字面意思而言:“意图, 意向, 目的”, 基本上可以诠释这个对象的作用。它里面包含的就是一些信息,这些信息能够告诉我们当前发生了什么,以及想要干什么。我觉得和前面的事件驱动中的事件非常 像,不同的是,它不仅仅包含事件,还包含一些数据信息。Intent或者也可以说像一个需求说明一样,说明了当前的事件以及一些数据,接下 来,Android会依据这个Intent的说明,为其找到一个Activity,并把这个Intent交给这个Activity。看起来Intent好 像是征婚广告一样哦,Intent提出自己的一些描述(不同的是,他描述的是自己,而不是描述的别人),然后Android根据这些描述找到何时的 Activity。
Intent有两种,一种是显式Intent,另一种是隐式Intent。显式Intent会在自己的说明中直接声明由谁(哪个Activity)来接收 处理这个Intent。显式的Intent是说,我已经有了意中人了,告诉Android不用费劲找了,可以直接定位到意中人,例如前文中的这个例子:
Intent intent = new Intent();
intent.putExtra("name", editText.getText().toString());
intent.setClass(ActivityLifecycle.this, AlertDialog.class);
通过setClass方法,直接告诉Android,此Intent交由AlertDialog处理。也可以通过setComponent()、setClassName()等方法来做同样的事情。
在一般的程序中, 我觉得使用显式Intent已经足够了。这样程序看起来简单易懂,毕竟大多数的Android程序都不会有太复杂的Activity之间的关系。
在前文中的另外一个方法中,使用的就是隐式的Intent。隐式Intent呢,比较含蓄,比较糊涂,比较模糊,还没有明确的目标,并且比较悲观,不会对对方提一些要求,只是描述了一下自己的情况,意思就是说,我就是我所描述的这样的一个人,谁能接受, 谁就可以应征。:)
Intent intent = new Intent();
intent.putExtra("name", editText.getText().toString());
intent.setAction("com.roiding.sample.action.MAIN");
Intent说明自己的方式有:
action 用来指明执行的动作,比如说ACTION_VIEW, ACTION_EDIT, ACTION_MAIN,这都是在Android中定义的标准action,开发者可以自己定义自己需要的action,用字符串来表示。更多标准 action参看这里。
category 描述action的附加信息,这个属性我一直不太明白,它被设计出来的用意到底是为什么,到目前为止,我对他的理解是:它就像一个action类别,例 如:CATEGORY_LAUNCHER、CATEGORY_ALTERNATIVE。属于CATEGORY_LAUNCHER这个类别的,可以被 Android放到顶级列表(类似于九宫格的那个界面)中被执行,属于CATEGORY_DEFAULT这个类别的,可以用 startActivity(intent)来启动Activity,等等。而这些行为都是Android内定的,所以自定义category不知道会有 什么意义,更多标准category参看这里。
data 动作操作的数据,这部分数据会像嫁妆一样,携带给接收它的Activity,Uri格式。Uri的概念和传统的URI的概念是一致的,遵循RFC 2396。 而Uri最终是由ContentProvider提供的,所以会涉及到ContentProvider的概念,这里也暂时不去解释了,只需要知道这个 data所声明的数据,就像一个url一样,通过这个url,可以找到提供这个url的ContentProvider提供的数据,然后就可以操作这些数据了。这里面的data似乎和前面的extras功能有重叠?按照我的理解,data确实可以放到extras中传递,但是单独把它提出来确有它的用意。在后面的IntentFilter中会有交代。
既然已经比喻为征婚广告了,把Intent比喻为征婚的需求,那么由Android系统根据什么挑选的候选人呢?
前面中说过这个文件:AndroidManifest.xml,而且当时将其比喻为户口簿,这下好了,所有的适龄青年(Activity)都在这个大的户口簿上了,按照Intent的需求直接在户口簿中寻找就可以了。那又是如何知道哪个适龄青年是否符合要求呢?
在AndroidManifest文件中,对每个适龄青年(Activity)都做了说明,就是intent-filter,它说明了当前这个适龄青年愿意接收什么样的需求,这样当Intent与这个Intent-filter中的说明一致时,Intent就找到了Activity。
这样,当一个Intent的所有说明,都被Activity的intent-filter接受时,这个Activity就是候选人之一。如果有多个 Activity成为候选人,那么在程序运行的时候Android提示你从多个Activity中手动选择一个来作为最终的intent接收执行者。
对于Activity来说,在它的intent-filter中,可以有多个action,多个category,多个data,这样可以有多个Intent可以与之匹配。
说到这里,前面的那个关于data和extras功能重叠的问题似乎还没有解决,所以这个data的重要的作用就是,它提供了一种反向挑选的机制,使 Activity对Intent也可以提出要求,要求Intent的data必须是intent-filter中声明的data中之一(因为可以有多个 data)。这点是非常重要的,不然又会出现ActivityNotFoundException之类的错误了。
到此篇文章为止,一般的Android界面编程应该已经够用了,而对于ContentProvider、Adapter、Services等,在一般的应用中,可以完全不用考虑。等到必须要使用的时候再说吧。
在前面的文章中,已经创建好了一个用户演示Activity生命周期的示例,但是由于那篇文章篇幅过长,所以分成两部分来写,在这篇文章中,将详细介绍Activity的生命周期。
了解Activity的生命周期是非常有必要的,在后面的文章中会涉及到数据的保存(也就是持久化)等功能,而保存时机的选择关乎用户体验,而了解Activity的生命周期会对时机的选择有更好的理解。
在文档中,已经有了比较清晰的说明:
如果一个Activity显示在最前端,它的状态时active或者是running
如果一个Activity失去焦点,但是还能在屏幕上看到它,比如说,被一个非全屏的或者是透明的另一个Activity遮盖,它此时的状态是paused。一个处于paused状态的Activity,保持着所有自身的状态和信息,秉持着与window manager的联系,但是当系统内存不足时,可能会被系统终止
如果一个Activity完全看不见了,被其他的Activity完全挡住,它的状态变成stopped。处于stopped的Activity虽然也保留着如同paused状态一样的特性,但是此时的它更容易被系统终止
如果一个Activity处于paused或者stopped状态时,系统可以通过要求Activity结束或者强制kill的方式从内存中删除这个Activity,当它在此需要显示时,就需要restart和重新加载以前的数据。(可见,适时的保存数据的重要性)
用一个状态装换图来说明问题:
仔细品味这张图,和上面的说明,会很好的理解Activity的生命周期。接下来,用前面的示例程序来具体说明一下。在前面的示例程序中,写了很多空方法,方法中只是打印了一行日志,这些方法都是Activity在进行状态转换的时候要使用的方法。
程序启动
INFO/ActivityLifecycle: onCreate …
INFO/ActivityLifecycle: onStart …
INFO/ActivityLifecycle: onResume …
点击一下第一个按钮,会弹出一个小对话框,主Activity在对话框的后面还是能看的见,此时的Activity的状态时paused
INFO/ActivityLifecycle: onPause …
然后,点击一下模拟器的返回按键,小对话框消失,返回到主Activity
INFO/ActivityLifecycle: onResume …
点击一下第二个按钮,一个新的Activity会代替当前的Activity
INFO/ActivityLifecycle: onPause …
INFO/ActivityLifecycle: onStop …
这时候,主Activity的状态就变成了stopped了
点击一下模拟器的返回按键,返回到主Activity
INFO/ActivityLifecycle: onRestart …
INFO/ActivityLifecycle: onStart …
INFO/ActivityLifecycle: onResume …
再次点击一下模拟器的返回按键,退出程序
INFO/ActivityLifecycle: onPause …
INFO/ActivityLifecycle: onStop …
INFO/ActivityLifecycle: onDestroy …
注意一下第一个按钮和第二个按钮被点击之后,所执行的过程是不同的。然后可以进行各种组合的前进退出,通过观察日志了解Activity的生命周期。
前面的文章已经讲到了如何在一个Activity中与用户进行交互。这篇文章将解释如何创建多个Activity的用户界面,以及了解Activity的生命周期。这次是一个完整的示例,可以在这里下载源代码。
这个多Activity的示例程序的功能大概是这样的:
在主页面上有三个元素:一个文本输入框,两个按钮。用户可以在文本框中输入一些字符
点击第一个按钮,将弹出一个对话框形式的Activity,显示”Hello, …”
如果点击的是第二个按钮,将显示另一个全屏的Activity,显示”Hello, …”
OK,按照前面的规矩,分成几步:
第1步,先完成UI的设计,创建布局。
从程序来看,似乎有三个Activity,但是第二个和第三个几乎是相同的,于是,只创建两个布局:
res/layout/main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<EditText
android:id="@+id/edittext_input"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
/>
<Button
android:id="@+id/button_sayhello_dialog"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/hello_dialog"
/>
<Button
android:id="@+id/button_sayhello_activity"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/hello_activity"
/>
</LinearLayout>
res/layout/dialog.xml
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:id="@+id/textview_hello"
android:text="@string/hello"
/>
第2步,然后呢,需要把上面提到的字符变量声明一下:
res/values/strings.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="hello">Hello, </string>
<string name="hello_dialog">Say Hello(Dialog)</string>
<string name="hello_activity">Say Hello(Activity)</string>
<string name="app_name">Activity Lifecycle</string>
</resources>
通过前面的文章,上面的东西应该都比较好懂。
第3步,创建Activity,这里和第一步一样,虽然有三个Activity,但是看起来用两个其实就够了。然而事实并非如此,具体原因后续,我们需要定义三个Activity:分别将他们定义为:ActivityLifecycle(用来显示主画面)、AlertDialog(用来显示对话框)和AlertActivity(用来显示那个全屏的Activity)。
主Activity:
package com.roiding.sample;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
public class ActivityLifecycle extends Activity {
private static final String TAG = "ActivityLifecycle";
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.i(TAG, "onCreate …");
setContentView(R.layout.main);
final EditText editText = (EditText) findViewById(R.id.edittext_input);
// try to display a dialog
Button [...]
通过上一篇文章,大概了解了Android用户界面的设计方式,以及如何通过XML来设计界面。并且完成了一个非常简单的示例。
在上一篇文章中,涉及到了一些资源文件,直观点说,就是res目录下的那些目录和文件,常用的有:
res/drawable/ 用来存放图片文件
res/layout/ 用来存放布局定义文件
res/values/ 用来存放一些变量、参数等文件
这些资源文件都是独立于代码而存在的,那么在代码中又是如何访问到这些资源的呢?
在 Android的代码中,有一个文件比较奇怪,第一次接触到Android代码的人都应该迷惑过,这个文件就是R.java,在R.java的注释部分, 有这样一段话:“AUTO-GENERATE FILE. DO NOT MODIFY”,这个文件是自动生成的,不要修改。
这个文件就是Java代码和独立于Java代码的资源文件之间的访问窗口,Java程序可以通过R.java来访问那些资源文件。
这个文件是在程序编译过程中由aapt(Android Asset Packaging Tool) 自动生成
这个文件中的属性都是public static final的,所以可以直接在Java代码中引用,每个属性都代表一个资源,看起来就像为每个资源都定义了一个ID,然后通过这个ID来访问资源
所有的资源文件都被编译进最后生成的二进制包中,这样能够带来很高的性能和效率
按照前面的示例,那么在R.java中会存在像这样一段代码:
public static final class layout {
public static final int main=0×7f030001;
}
这里面的main就是由res/layout/main.xml生成的。
接下来在看一下,如何在Java代码中使用这个R.java,创建一个文件Main.java :
package com.roiding.study;
import android.app.Activity;
import android.os.Bundle;
public class Main extends Activity {
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
}
注意这其中的R.layout.main,通过setContextView(),被Java程序调用。
这段代码中引入了另外一个非常重要的概念:Activity,到目前为止,已经介绍的另外两个比较重要的概念是ViewGroup和View。如果套用时髦的MVC来理解的话:ViewGroup和View属于View领域的东西,Activity可以理解为Control领域的东西,虽然可以这 么类比着来理解,但是Activity其实做了更多的事情,由它将View和一些数据、操作等结合在一起,完成一个相对完整的行为、活动。所以,任何 View要被显示,必须通过Activity来进行。这样每个Activity都会包含特定的行为,多个Activity互相合作,最终组成一个完整的 Android程序。
在示例的Activity中,有一个onCreate()方法,这个方法会在此Activity被创建的时刻调用。在方法内部,设置此Activity的视图为R.layout.main中定义的视图,也就是res/layout/main.xml中定义的视图 (视图、布局、GUI、UI在这里已经混合使用了,并且他们意义相同,后文统一用View表示)。
Activity所包含的内容很多,会用单独的一篇文章来详细介绍。
说到此处,我们应该知道如何创建一个View,并通过Activity将其显示在屏幕上。接下来事情,就是用户如何和这个Activity交互。
[待续]
Recent Comments