Friday, July 23, 2010

复杂的一些菜单代码(来自系出名门)

<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="hello">Hello Layout</string>
<string name="app_name">webabcd_layout</string>
</resources>

Main.java
package com.webabcd.layout;

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);
}
}

2、上下文菜单,选项菜单,子菜单
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">

<TextView android:id="@+id/txt1" android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/hello_contextMenu" />

<TextView android:id="@+id/txt2" android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/hello_subMenu" />

</LinearLayout>

res/values/strings.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="hello_contextMenu">Hello Context Menu</string>
<string name="hello_subMenu">Hello Context Sub Menu</string>
<string name="app_name">webabcd_menu</string>
</resources>

Main.java
package com.webabcd.menu;

import android.app.Activity;
import android.os.Bundle;
import android.view.ContextMenu;
import android.view.Menu;
import android.view.MenuItem;
import android.view.SubMenu;
import android.view.View;
import android.view.ContextMenu.ContextMenuInfo;
import android.widget.TextView;
import android.widget.Toast;

// 演示两种菜单的实现方式:上下文菜单(通过在某元素上长按,来呼出菜单)和选项菜单(通过按手机上的菜单按钮,来呼出菜单)
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.id.txt1 注册一个上下文菜单(在此 TextView 上长按,则会呼出上下文菜单)
// 具体呼出的菜单内容需要重写 onCreateContextMenu 来创建
TextView txt1 = (TextView) this.findViewById(R.id.txt1);
this.registerForContextMenu(txt1);

// 为 R.id.txt2 注册一个上下文菜单
TextView txt2 = (TextView) this.findViewById(R.id.txt2);
this.registerForContextMenu(txt2);
}

// 重写 onCreateContextMenu 用以创建上下文菜单
// 重写 onContextItemSelected 用以响应上下文菜单
@Override
public void onCreateContextMenu(ContextMenu menu, View v,
ContextMenuInfo menuInfo) {
super.onCreateContextMenu(menu, v, menuInfo);

// 创建 R.id.txt1 的上下文菜单
if (v == (TextView) this.findViewById(R.id.txt1)) {

// ContextMenu.setIcon() - 设置菜单的图标
// ContextMenu.setHeaderTitle() - 设置菜单的标题
menu.setHeaderIcon(R.drawable.icon01);
menu.setHeaderTitle("我是菜单");

// 用 ContextMenu.add() 来增加菜单项,返回值为 MenuItem
// 第一个参数:组ID
// 第二个参数:菜单项ID
// 第三个参数:顺序号
// 第四个参数:菜单项上显示的内容
menu.add(1, 0, 0, "菜单1");

// MenuItem - 新增菜单项后的返回类型,针对菜单项的其他设置在此对象上操作
menu.add(1, 1, 1, "菜单2").setCheckable(true);

}
// 创建 R.id.txt2 的上下文菜单(多级上下文菜单)
else if (v == (TextView) this.findViewById(R.id.txt2)) {

// ContextMenu.addSubMenu("菜单名称") - 用来添加子菜单。子菜单其实就是一个特殊的菜单
SubMenu sub = menu.addSubMenu("父菜单1");
sub.setIcon(R.drawable.icon01);
sub.add(0, 0, 0, "菜单1");
sub.add(0, 1, 1, "菜单2");
sub.setGroupCheckable(1, true, true);

SubMenu sub2 = menu.addSubMenu("父菜单2");
sub2.setIcon(R.drawable.icon01);
sub2.add(1, 0, 0, "菜单3");
sub2.add(1, 1, 1, "菜单4");
sub2.setGroupCheckable(1, true, false);

}
}


// 重写 onCreateOptionsMenu 用以创建选项菜单
@Override
public boolean onCreateOptionsMenu(Menu menu) {

MenuItem menuItem = menu.add(0, 0, 0, "菜单111111111111111111111");

// MenuItem.setIcon() - 设置菜单项的图标
// MenuItem.setTitleCondensed() - 菜单的简标题,如果指定了简标题的话,菜单项上的标题将会以此简标题为准
// MenuItem.setAlphabeticShortcut() - 设置选中此菜单项的快捷键
// 注:菜单项超过 6 个的话,第 6 个菜单将会变为 More 菜单,多余的菜单会在单击 More 菜单之后显示出来
menuItem.setIcon(R.drawable.icon01);
menuItem.setTitleCondensed("菜单1");
menuItem.setAlphabeticShortcut('a');

menu.add(0, 1, 1, "菜单2").setIcon(R.drawable.icon02);
menu.add(0, 2, 2, "菜单3").setIcon(R.drawable.icon03);
menu.add(0, 3, 3, "菜单4");
menu.add(0, 4, 4, "菜单5");
menu.add(0, 5, 5, "菜单6");
menu.add(0, 6, 6, "菜单7").setIcon(R.drawable.icon04);
menu.add(0, 7, 7, "菜单8").setIcon(R.drawable.icon05);

return true;
}

// 重写 onOptionsItemSelected 用以响应选项菜单
@Override
public boolean onOptionsItemSelected(MenuItem item) {
super.onOptionsItemSelected(item);

Toast.makeText(Main.this, "被单击的菜单项为:" +
String.valueOf(item.getItemId()), Toast.LENGTH_SHORT).show();

return false;
}
}

OK

创建一个上下文菜单

创建一个上下文菜单的代码如下,注意要先定义EDIT_ID与DELETE_ID的值.
但完成代码后,在界面中长按,并没有弹出相应的菜单,不知是为什么.
@Override
public void onCreateContextMenu(ContextMenu menu, View v,
ContextMenuInfo menuInfo) {
super.onCreateContextMenu(menu, v, menuInfo);
menu.add(0, EDIT_ID, 0,"Edit");
menu.add(0, DELETE_ID, 0,"Delete");
}
@Override
public boolean onContextItemSelected(MenuItem item) {
AdapterContextMenuInfo info = (AdapterContextMenuInfo)
item.getMenuInfo();
switch (item.getItemId()) {
case EDIT_ID:
// editNote(info.id);
return true;
case DELETE_ID:
// deleteNote(info.id);
return true;
default:
return super.onContextItemSelected(item);
}
}
答案是,在使用content菜单时,要先进行注册。以表示该上下文菜单是对应于哪一个控件。函数是:
registerForContextMenu。
这个函数一般被加在OnCreate中,示例如下:
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
TextView tView=(TextView)findViewById(R.id.hello);
registerForContextMenu(tView);
}
这是给系统创建的默认页面的默认元素(是一个TextView)加一个上下文菜单的例子。加入这些代码后,就可在程序的主页面中测试到上下文菜单了。

Monday, July 19, 2010

intent 用法(来自eoe)

Android Intent的几种用法全面总结

Intent, 用法
Intent应该算是Android中特有的东西。你可以在Intent中指定程序要执行的动作(比如:view,edit,dial),以及程序执行到该动作时所需要的资料。都指定好后,只要调用startActivity(),Android系统会自动寻找最符合你指定要求的应用程序,并执行该程序。

下面列出几种Intent的用法
显示网页:
Uri uri = Uri.parse("http://www.google.com");
Intent it = new Intent(Intent.ACTION_VIEW,uri);
startActivity(it);

显示地图:
Uri uri = Uri.parse("geo:38.899533,-77.036476");
Intent it = new Intent(Intent.Action_VIEW,uri);
startActivity(it);

路径规划:
Uri uri = Uri.parse("http://maps.google.com/maps?f=d&saddr=startLat%20startLng&daddr=endLat%20endLng&hl=en");
Intent it = new Intent(Intent.ACTION_VIEW,URI);
startActivity(it);

拨打电话:
调用拨号程序
Uri uri = Uri.parse("tel:xxxxxx");
Intent it = new Intent(Intent.ACTION_DIAL, uri);
startActivity(it);

Uri uri = Uri.parse("tel.xxxxxx");
Intent it =new Intent(Intent.ACTION_CALL,uri);
要使用这个必须在配置文件中加入<uses-permission id="android.permission.CALL_PHONE" />

发送SMS/MMS
调用发送短信的程序
Intent it = new Intent(Intent.ACTION_VIEW);
it.putExtra("sms_body", "The SMS text");
it.setType("vnd.android-dir/mms-sms");
startActivity(it);

发送短信
Uri uri = Uri.parse("smsto:0800000123");
Intent it = new Intent(Intent.ACTION_SENDTO, uri);
it.putExtra("sms_body", "The SMS text");
startActivity(it);

发送彩信
Uri uri = Uri.parse("content://media/external/images/media/23");
Intent it = new Intent(Intent.ACTION_SEND);
it.putExtra("sms_body", "some text");
it.putExtra(Intent.EXTRA_STREAM, uri);
it.setType("image/png");
startActivity(it);

发送Email

Uri uri = Uri.parse("mailto:xxx@abc.com");
Intent it = new Intent(Intent.ACTION_SENDTO, uri);
startActivity(it);

Intent it = new Intent(Intent.ACTION_SEND);
it.putExtra(Intent.EXTRA_EMAIL, "me@abc.com");
it.putExtra(Intent.EXTRA_TEXT, "The email body text");
it.setType("text/plain");
startActivity(Intent.createChooser(it, "Choose Email Client"));

Intent it=new Intent(Intent.ACTION_SEND);
String[] tos={"me@abc.com"};
String[] ccs={"you@abc.com"};
it.putExtra(Intent.EXTRA_EMAIL, tos);
it.putExtra(Intent.EXTRA_CC, ccs);
it.putExtra(Intent.EXTRA_TEXT, "The email body text");
it.putExtra(Intent.EXTRA_SUBJECT, "The email subject text");
it.setType("message/rfc822");
startActivity(Intent.createChooser(it, "Choose Email Client"));

添加附件
Intent it = new Intent(Intent.ACTION_SEND);
it.putExtra(Intent.EXTRA_SUBJECT, "The email subject text");
it.putExtra(Intent.EXTRA_STREAM, "file:///sdcard/mysong.mp3");
sendIntent.setType("audio/mp3");
startActivity(Intent.createChooser(it, "Choose Email Client"));

播放多媒体

Intent it = new Intent(Intent.ACTION_VIEW);
Uri uri = Uri.parse("file:///sdcard/song.mp3");
it.setDataAndType(uri, "audio/mp3");
startActivity(it);

Uri uri = Uri.withAppendedPath(MediaStore.Audio.Media.INTERNAL_CONTENT_URI,
"1");
Intent it = new Intent(Intent.ACTION_VIEW, uri);
startActivity(it);

Uninstall 程序
Uri uri = Uri.fromParts("package", strPackageName, null);
Intent it = new Intent(Intent.ACTION_DELETE, uri);
startActivity(it);

eoeAndroid
Android中的Intent详细讲解
不错,我再补充几个:
uninstall apk
Uri uninstallUri = Uri.fromParts("package", "xxx", null);

returnIt = new Intent(Intent.ACTION_DELETE, uninstallUri);


install apk
Uri installUri = Uri.fromParts("package", "xxx", null);

returnIt = new Intent(Intent.ACTION_PACKAGE_ADDED, installUri);

play audio
Uri playUri = Uri.parse("file:///sdcard/download/everything.mp3");

returnIt = new Intent(Intent.ACTION_VIEW, playUri);

//发送附件
Intent it = new Intent(Intent.ACTION_SEND);
it.putExtra(Intent.EXTRA_SUBJECT, "The email subject text");
it.putExtra(Intent.EXTRA_STREAM, "file:///sdcard/eoe.mp3");
sendIntent.setType("audio/mp3");
startActivity(Intent.createChooser(it, "Choose Email Client"));

market相关

再来一个market相关的:
market相关

//搜索应用
Uri uri = Uri.parse("market://search?q=pname:pkg_name");
Intent it = new Intent(Intent.ACTION_VIEW, uri);
startActivity(it);
//where pkg_name is the full package path for an application

//显示指定应用的详细页面(这个好像不支持了,找不到app_id)
Uri uri = Uri.parse("market://details?id=app_id");
Intent it = new Intent(Intent.ACTION_VIEW, uri);
startActivity(it);
//where app_id is the application ID, find the ID
//by clicking on your application on Market home
//page, and notice the ID from the address bar

Salute


原作者:
来源Datatypes In SQLite Version 3
译者边之龙

大多数 SQL 数据库引擎(据我们所知的、SQLite 以外的所有 SQL 数据库引擎)使用静态的、严格的类型。对于静态类型,数据类型的值由它的容器(存储值的特定列)来决定。

SQLite 使用的是一个更加普通的动态类型系统。在 SQLite 中,数据类型的值是与值的本身有关联,而不是它的容器。SQLite 的动态类型系统对于更多其它数据库引擎常见的静态类型系统是向后兼容的,在这个意义上,工作在静态类型数据库上的 SQL 语句同样也能工作在 SQLite 中。然而,SQLite 中的动态类型允许它做一些在传统严格类型数据库中不能做的事情。

1.0 存储类和数据类型

存储在 SQLite 数据库中的每个值(或者由数据库引擎操纵的值)都是以下的存储类之一:
  • NULL。该值是一个 NULL 值。
  • INTEGER。该值是一个有符号的整数,根据值的大小,存储 1、2、3、4、5、6 或 8 字节内容。
  • REAL。该值是一个浮点值,存储一个 8 字节的 IEEE 浮点数。
  • TEXT。该值是一个文本字符串,使用数据库编码(UTF-8、 UTF-16BE 或 UTF-16LE)进行存储。
  • BLOB。该值是一个数据块,按照它的输入直接存储。

注意,一个存储类要略普遍于一个数据类型。INTEGER 存储类,例如,包含不同长度的 6 个不同的整数类型。这在磁盘上是不同的。但是一旦 INTEGER 值从磁盘读取到内存进行处理,它们都将被转换为最普通的数据类型(8 字节有符号的整数)。因此大部分"存储类"是无异于"数据类型"的,并且有 2 个条款能被互换地使用。

在一个 SQLite 版本 3 数据库中的任何列,除了 INTEGER PRIMARY KEY 列,都可以被用来存储一个任意存储类的值。

SQL 语句中的所有值,无论它们是嵌入在 SQL 语句文本里的字面量,还是绑定到预编译 SQL 语句的参数,都有一个内在存储类。在以下描述的情况中,数据库引擎会在执行查询过程中,在数字存储类(INTEGER 和 REAL)和 TEXT 之间进行转换。

1.1 Boolean 数据类型

SQLite 没有一个分开的 Boolean 存储类。替代的,Boolean 值被存储为整数 0(false)和 1(true)。

1.2 Date 和 Time 数据类型

SQLite 没有一个预留的存储日期和/或时间的存储类。替代的,SQLite 内置的 Date 和 Time 函数有能力把日期和时间存储为 TEXT、REAL 或 INTEGER 值:

  • TEXT 为 ISO8601 字符串(YYYY-MM-DD HH:MM:SS.SSS)。
  • REAL 为"朱利安"天数,天数从"格林威治"公元前4714年11月24日中午到预期的公历。
  • INTEGER 为 Unix Time,自 1970-01-01 00:00:00 UTC 以来的秒数。

应用程序可以选择任何格式来存储日期和时间,并且使用内置的日期和时间函数在格式之间自由地转换。

2.0 亲和类型

为了最大化 SQLite 和其它数据库引擎之间的兼容性,SQLite 支持列上的"亲和类型"概念。一个列的亲和类型是存储在该列的数据的推荐类型。在这里最重要的是,类型是被推荐的,而不是必须的。所有列仍可以存储任意类型的数据。这只是一些列,给出选择,将会使用一个存储类覆盖原先的。一个列首选的存储类称为它的"亲和类型"。

SQLite 3 数据库中的每个列被分配了以下亲和类型之一:

  • TEXT
  • NUMERIC
  • INTEGER
  • REAL
  • NONE

一个带有 TEXT 亲和类型的列,可以使用存储类 NULL、TEXT、BLOB 来存储所有的数据。如果数值数据被插入到一个带有 TEXT 亲和类型的列中,它将从原来被存储的格式转换为现在的文本格式。

一个带有 NUMERIC 亲和类型的列可以包含所有的 5 种存储类。当文本数据被插入到一个 NUMERIC 列中,如果转换是无损和可逆的,文本存储类将被转换为 INTEGER 或 REAL(优先顺序)。对于 TEXT 和 REAL 存储类之间的转换,如果数的前 15 位是有意义的十进制数字,SQLite 考虑到转换要无损和可逆,都将会被保留。如果 TEXT 到 INTEGER 或 REAL 的无损转换是不可能的,那么值将使用 TEXT 存储类进行存储。不要尝试将其转换为 NULL 或 BLOB 值。

一个字符串可能看起来像一个带有小数点和/或指数符号的浮点数字面量,但只要该值能够被表示为一个整数,NUMERIC 亲和类型会将转换它为一个整数。因此,在一个 NUMERIC 亲和类型的列中,字符串 '3.0e+5' 被存储为整数 300000,而不是浮点值 300000.0。

一个使用 INTEGER 亲和类型的列与一个使用 NUMERIC 亲和类型的列在行为上是相同的。INTEGER 和 NUMERIC 亲和类型不同点只表现在一个 CAST 表达式上。

一个带有 REAL 亲和类型的列与一个带有 NUMERIC 亲和类型的列在行为上是相同的,除了它强制把整数值作为浮点数来表示。(作为一个内部的优化,没有小数部分的小浮点数值存储到带有 REAL 亲和类型的列中时,将被作为一个整数写入到磁盘,为的是占用更少的空间和在它被读出时能被自动地转换回浮点数。该优化在 SQL 级别是完全不可见的,并且只有通过研究数据库文件的原始数据才能被检测。)

一个带有 NULL 亲和类型的列不建议将一个存储类覆盖到另一个存储类,并且不要尝试强制将数据从一个存储类变为另一个存储类。

2.1 亲和列的测定

一个列的亲和类型是由列的声明类型决定的,根据下面展示的有顺序的规则:

  1. 如果类型声明包含字符串"INT",它将被分配为 INTEGER 亲和类型。
  2. 如果列的类型声明包含字符串"CHAR"、"CLOB"或"TEXT"的任意之一,列将拥有 TEXT 亲和类型。
  3. 如果一个列的类型声明包含字符串"BLOB",或者如果没有类型被指定,列将拥有 NONE 亲和类型。
  4. 如果一个类的类型声明包含字符串"REAL"、"FLOA"或"DOUB"的任意之一,列将拥有 REAL 亲和类型。
  5. 否则,亲和类型为 NUMERIC。

注意,决定列的亲和类型的规则的顺序是十分重要的。一个列的类型声明若是"CHARINT",则将匹配规则 1 和 2,但是第一个规则更加优先,因此列的亲和类型为 INTEGER。

2.2 亲和名称的例子

下表展示了,如何通过前一章节的 5 个规则,将传统的 SQL 实现的通用数据类型名称转换为亲和的类型。该表只展示了 SQLite 允许的数据类型名称的一个小子集。注意,类型名称后面括号中的数字参数(如:"VARCHAR(255)")将被 SQLite 忽略 - SQLite 不会在全局 SQLITE_MAX_LENGTH 以外,再对字符串、BLOBs 或数字值的长度上强加任何的长度限制。


来自 CREATE TABLE 语句 CAST 表达式中
类型名称的例子
亲和类型结果被用来决定亲和类型的规则
INT
INTEGER
TINYINT
SMALLINT
MEDIUMINT
BIGINT
UNSIGNED BIG INT
INT2
INT8
INTEGER1
CHARACTER(20)
VARCHAR(255)
VARYING CHARACTER(255)
NCHAR(55)
NATIVE CHARACTER(70)
NVARCHAR(100)
TEXT
CLOB
TEXT2
BLOB
no datatype specified
NONE3
REAL
DOUBLE
DOUBLE PRECISION
FLOAT
REAL4
NUMERIC
DECIMAL(10, 5)
BOOLEAN
DATE
DATETIME
NUMERIC5

注意,一个"FLOATING POINT"类型声明会给出 INTEGER 亲和类型,而不是 REAL 亲和类型,由于"POINT"的结尾是"INT"。并且类型声明"STRING"将是一个 NUMBERIC 亲和类型,而不是 TEXT。

2.3 列亲和行为的例子

下面的 SQL 演示了 SQLite 在值被插入到一个表时,如何使用列的亲和类型来进行类型转换。

CREATE TABLE t1(
    t  TEXT,     -- 规则 2 的文本亲和类型
    nu NUMERIC,  -- 规则 5 的数字亲和类型
    i  INTEGER,   -- 规则 1 的整数亲和类型
    r  REAL,     -- 规则 4 的浮点数亲和类型
    no BLOB      -- 规则 3 的无亲和类型
);
  
-- 值被存储为 TEXT、INTEGER、INTEGER、REAL、TEXT。
INSERT INTO t1 VALUES('500.0', '500.0', '500.0', '500.0', '500.0');
SELECT typeof(t), typeof(nu), typeof(i), typeof(r), typeof(no) FROM t1;
text|integer|integer|real|text
  
-- 值被存储为 TEXT、INTEGER、INTEGER、REAL、REAL。
DELETE FROM t1;
INSERT INTO t1 VALUES(500.0, 500.0, 500.0, 500.0, 500.0);
SELECT typeof(t), typeof(nu), typeof(i), typeof(r), typeof(no) FROM t1;
text|integer|integer|real|real
  
-- 值被存储为 TEXT、INTEGER、INTEGER、REAL、INTEGER。
DELETE FROM t1;
INSERT INTO t1 VALUES(500, 500, 500, 500, 500);
SELECT typeof(t), typeof(nu), typeof(i), typeof(r), typeof(no) FROM t1;
text|integer|integer|real|integer
  
-- 无论列的亲和类型,BLOBs 总是被存储为 BLOBs。
DELETE FROM t1;
INSERT INTO t1 VALUES(x'0500', x'0500', x'0500', x'0500', x'0500');
SELECT typeof(t), typeof(nu), typeof(i), typeof(r), typeof(no) FROM t1;
blob|blob|blob|blob|blob
  
-- NULLs 也不会被亲和类型所影响。
DELETE FROM t1;
INSERT INTO t1 VALUES(NULL,NULL,NULL,NULL,NULL);
SELECT typeof(t), typeof(nu), typeof(i), typeof(r), typeof(no) FROM t1;
null|null|null|null|null

3.0 比较表达式

SQLite 版本 3 有通常的 SQL 比较操作符集,包括"="、"<"、"<="、">="、"!="、"IN"、"BETWEEN"和"IS"等。

3.1 排序顺序

一个比较的结果是依靠操作数的存储类来决定的,根据下面的规则:

  • 存储类 NULL 的值被认为是小于其它任何值的(包括其它存储类 NULL 的值)。
  • 一个 INTEGER 或 REAL 值小于任何 TEXT 或 BLOB 值。当一个 INTEGER 或 REAL 与其它 INTEGER 或 REAL 比较时,一个数值的比较将被执行。
  • 一个 TEXT 值是小于一个 BLOB 值的。当两个 TEXT 值进行比较时,一个适当的集合序列将被用来决定其结果。
  • 当两个 BLOB 值进行比较时,使用 memcmp() 来决定结果。

3.2 比较操作数的亲和类型

SQLite 在执行一个比较之前,会尝试在存储类 INTEGER、REAL 和/或 TEXT 之间执行值的转换。在比较发生之前,无论尝试任何转换,都是依靠操作数的亲和类型而进行的。操作数的亲和类型是由以下规则决定的:

  • 一个表达式是一个对列值的简单引用,则拥有与列相同的亲和类型。注意,如果 X 和 Y.Z 是列名,那么 +X 和 +Y.Z 将被认为是表达式,目的是为了确定亲和类型。
  • 一个"CAST(expr TO type)"格式的表达式,拥有与一个列的"type"类型声明相同的亲和类型。
  • 否则,表达式为 NONE 亲和类型。

3.3 比较之前的类型转换

要"应用亲和类型"意思是转换一个操作数为一个特定的存储类,当且仅当转换是无损和可逆的。要在比较之前将亲和类型应用到一个比较操作符的操作数中,根据以下展示的规则顺序:

  • 如果一个操作数为 INTEGER、REAL 或 NUMERIC 亲和类型,并且另一个操作数为 TEXT 或 NONE 亲和类型,NUMERIC 亲和类型将被应用到另一个操作数上。
  • 如果一个操作数为 TEXT 亲和类型,并且另一个操作数为 NONE 亲和类型,TEXT 亲和类型将被应用到另一个操作数上。
  • 否则,无亲和类型将被应用到两个比较的操作数上。

表达式"a BETWEEN b AND c"被视为两个分开的二元比较"a >= b AND a <= c",即使这意味着不同的亲和类型被应用到每个比较的"a"上。格式"x IN (SELECT y ...)"比较中的数据类型转换,被处理为"x = y"是否为真。表达式"a IN (x, y, z, ...)"相当于"a = +x, a = +y, a = +z, ..."。换句话讲,IN 操作符右边的值(该例中为"x"、"y"和"z"的值)将被视为无亲和类型,即使它们碰巧是列值或 CAST 表达式。

3.4 比较示例

CREATE TABLE t1(
    a TEXT,      -- 文本亲和类型
    b NUMERIC,   -- 数字亲和类型
    c BLOB,      -- 无亲和类型
    d            -- 无亲和类型
);

-- 值将分别被存储为 TEXT、INTEGER、TEXT 和 INTEGER。
INSERT INTO t1 VALUES('500', '500', '500', 500);
SELECT typeof(a), typeof(b), typeof(c), typeof(d) FROM t1;
text|integer|text|integer

-- 因为列"a"是文本亲和类型,右手边的数字值在发生比较之前,就被转换为文本了。
SELECT a < 40,   a < 60,   a < 600 FROM t1;
0|1|1

-- 文本亲和类型被应用到右手边的操作数,但因为它们已经是 TEXT,这将是一个空操作;不会发生转换。
SELECT a < '40', a < '60', a < '600' FROM t1;
0|1|1

-- 列"b"是数字亲和类型,因此数字亲和类型将被应用给右边的操作数。因为操作数已经是数字,应用程序的亲和类型将是空操作;不会发生转换。所有值进行数值比较。
SELECT b < 40,   b < 60,   b < 600 FROM t1;
0|0|1

-- 数字亲和类型将被应用到右边的操作数,将它们从文本转换为整数。然后产生一个数字比较。
SELECT b < '40', b < '60', b < '600' FROM t1;
0|0|1

-- 发生无亲和类型转换。右手边的值都是存储类 INTEGER,它们总是小于左边的 TEXT 值。
SELECT c < 40,   c < 60,   c < 600 FROM t1;
0|0|0

-- 发生无亲和类型转换。值作为 TEXT 进行比较。
SELECT c < '40', c < '60', c < '600' FROM t1;
0|1|1

-- 发生无亲和类型转换。右手边的值都是存储类 INTEGER,它们与左边的 INTEGER 值进行数值比较。
SELECT d < 40,   d < 60,   d < 600 FROM t1;
0|0|1

-- 发生无亲和类型转换。左边的 INTEGER 值总是小于右边的 TEXT 值。
SELECT d < '40', d < '60', d < '600' FROM t1;
1|1|1
  

如果比较被换算,该例中所有的结果都是相同的 - 如果表达式为"a < 40"格式,将被重写为"40 > a"。

4.0 操作符

所有数学运算操作符(+、-、*、/、%、<<、>>、& 和 |)两边的操作数在执行之前都被转换为 NUMERIC 存储类。即使它是有损的和不可逆的,转换都会通过。数学运算操作符中的一个 NULL 操作数,产生一个 NULL 结果。数学运算操作符中的一个操作数看起来不像任何数字或不是 NULL,它将被转换为 0 或 0.0。

5.0 排序、分组和复合的 SELECTs

当查询结果被 ORDER BY 子句进行排序,存储类 NULL 的值将被置顶,其后是穿插的以数字为序 INTEGER 和 REAL 值,再后是以序列进行排序的 TEXT 值,最后是 memcmp() 顺序的 BLOB 值。在排序之前不会发生任何存储类的转换。

当使用 GROUP BY 子句对值进行分组时,不同存储类的值被认为是不同的,除了被认为是相等的 INTEGER 和 REAL 值,如果它们在数值上是相等的话。没有亲和类型会被应用到 GROUP BY 子句的结果中。

复合 SELECT 操作符 UNION、INTERSECT 和 EXCEPT 执行内部的值比较。没有亲和类型被应用到与 UNION、INTERSECT 或 EXCEPT 关联的内部比较操作数上 - 值被直接进行比较。

6.0 校对序列

当 SQLite 比较两个字符串时,如果两个字符串相等,它使用一个校对序列或校对函数来决定哪个字符串更大一些。SQLite 有 3 个内置的校对函数:BINARY、NOCASE 和 RTRIM。

  • BINARY - 使用 memcmp() 对字符串数据进行比较,无论文本编码。
  • NOCASE - 与 BINARY 相同,除了在比较执行之前会将 ASCII 码的 26 个大写字母被折换为与其等值的小写字母。注意,只有 ASCII 码的字符进行大小写折换。由于所需表的大小,SQLite 不会尝试对完整 UTF 的大小写进行折换。
  • RTRIM - 与 BINARY 相同,除了尾随的空格将被忽略。

使用 sqlite3_create_collation() 接口,一个应用程序可以注册附加的校对函数。

6.1 从 SQL 分配校对序列

每个表的每个列都有一个相关联的校对函数。如果没有校对函数被明确定义,校对规则默认为 BINARY。列定义的 COLLATE 子句被用来替代地为一个列定义校对函数。

为一个二元比较操作符(=、<、>、<=、>=、!=、IS 和 IS NOT)决定使用何种校对函数,按照下面展示的规则顺序进行:

  1. 如果两个操作数都使用 COLLATE 后缀操作符分配了一个明确的校对函数,那么明确的校对函数被用在比较中,左边操作数的校对函数有较高的优先级。
  2. 如果两个操作数都是一个列,左边列的校对函数有较高的优先级。前一句的目的是,带有一个或多个一元"+"操作符的列名仍被认为是一个列名。
  3. 否则,比较将会使用 BINARY 校对函数。

表达式"x BETWEEN y AND z"在逻辑上等价于两个比较"x >= y AND x <= z",并且与校对函数一起工作,就像这是有两个分开的比较一样。表达式"x IN (SELECT y ...)"与"x = y"的处理方式一样,目的是为了确定校对序列。用在"x IN (y, z, ...)"格式表达式上的校对序列,就是 x 的校对序列。

ORDER BY 子句是一个 SELECT 语句的一部分,其规则是,可使用 COLLATE 操作符为 SELECT 语句分配一个校对序列,在这种情况下,特定的校对函数被用于排序。否则,如果通过 ORDER BY 子句进行排序的表达式是一个列,列的校对序列将被用来确定排序顺序。如果表达式不是一个列并且没有 COLLATE 子句,则 BINARY 校对序列将被使用。

6.2 校对序列示例

下面的例子用来辨认校对序列,这可被用来确定由各 SQL 语句执行的文本比较的结果。注意,在 NUMERIC、BLOB 或 NULL 值的情况,一个文本的比较并不是必须的,并且不会使用校对序列。

CREATE TABLE t1(
    x INTEGER PRIMARY KEY,
    a,                 /* 校对序列 BINARY */
    b COLLATE BINARY,  /* 校对序列 BINARY */
    c COLLATE RTRIM,   /* 校对序列 RTRIM */
    d COLLATE NOCASE   /* 校对序列 NOCASE */
);
                   /* x   a     b     c       d */
INSERT INTO t1 VALUES(1,'abc','abc', 'abc  ','abc');
INSERT INTO t1 VALUES(2,'abc','abc', 'abc',  'ABC');
INSERT INTO t1 VALUES(3,'abc','abc', 'abc ', 'Abc');
INSERT INTO t1 VALUES(4,'abc','abc ','ABC',  'abc');

/* 使用 BINARY 校对序列执行文本比较 a = b。 */
SELECT x FROM t1 WHERE a = b ORDER BY x;
-- 结果 1 2 3

/* 使用 RTRIM 校对序列执行文本比较 a = b。 */
SELECT x FROM t1 WHERE a = b COLLATE RTRIM ORDER BY x;
-- 结果 1 2 3 4

/* 使用 NOCASE 校对序列执行文本比较 d = a。 */
SELECT x FROM t1 WHERE d = a ORDER BY x;
-- 结果 1 2 3 4

/* 使用 BINARY 校对序列执行文本比较 a = d。 */
SELECT x FROM t1 WHERE a = d ORDER BY x;
-- 结果 1 4

/* 使用 RTRIM 校对序列执行文本比较 'abc' = c。 */
SELECT x FROM t1 WHERE 'abc' = c ORDER BY x;
-- 结果 1 2 3

/* 使用 RTRIM 校对序列执行文本比较 c = 'abc'。 */
SELECT x FROM t1 WHERE c = 'abc' ORDER BY x;
-- 结果 1 2 3

/* 使用 NOCASE 校对序列进行分组(值"abc"、"ABC"和"Abc"被放在相同的分组中)。 */
SELECT count(*) FROM t1 GROUP BY d ORDER BY 1;
-- 结果 4

/* 使用 BINARY 校对序列执行分组。"abc"、"ABC"和"Abc"来自不同的分组。 */
SELECT count(*) FROM t1 GROUP BY (d || '') ORDER BY 1;
-- 结果 1 1 2

/* 使用 RTRIM 校对序列或列 c 执行排序。 */
SELECT x FROM t1 ORDER BY c, x;
-- 结果 4 1 2 3

/* 使用 BINARY 校对序列或 (c || '') 执行排序。 */
SELECT x FROM t1 ORDER BY (c||''), x;
-- 结果 4 2 3 1

/* 使用 NOCASE 校对序列或列 c 执行排序。 */
SELECT x FROM t1 ORDER BY c COLLATE NOCASE, x;
-- 结果 2 4 3 1

添加新评论

相关文章:

  SQLite 对比其它数据库引擎的 NULL 处理

  SQLite 没有实现的 SQL 特征

发自我的 iPhone

阅读SDK示例程序Note的一点笔记

也可以在主程序中使用Intent
intent=getIntent();来设置当前要显示的Activity,这个由AndroidManifest.xml中的下面语句来定义:
<activity android:name="NotesList"
android:label="@string/title_notes_list">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
上面的语句定义默认显示NotesList这个布局。
在调试器中查看intent的值如下所示:
intent Intent (id=830066438464)
mAction "android.intent.action.MAIN" (id=830066438512)
count 26
hashCode -1173447682
offset 0
value (id=830066438544)
mCategories HashSet (id=830066438880)
mComponent ComponentName (id=830066438728)
mData null
mExtras null
mFlags 268435456
mPackage null
mSourceBounds null
mType null
在mAction变量中,保存了要打开的Activity的值(在前所述的文件中定义了)。

我的第一个使用按钮打开另一个Activity的程序

android 按钮打开另一个activity的示例程序
首先,是布局排列问题。一般来说,有经验的用户会选择使用java代码生成界面,但是我对界面代码不太熟悉,所以,我使用了xml文件来生成界面。
打开res->layout,选择main.xml,这是系统默认生成的界面布置xml文件。双击该项,会打开该文件,在页面底部选择layout,出现页面布置页面。在页面上右击鼠标,选择Add菜单项。选择"Button"项,这样就在主界面中加入了一个按钮。
其次要创建一个该按钮打开的activity,也可以理解为打开一个对话框。在Android中,一个Activity由一个java文件和一个xml文件组成。
要创建一个java文件比较简单,单击工具栏中的新建按钮,选择class。在弹出的对话框中,单击"Source
Folder:"后面的"Browse",选择要保存文件的工程名称,一般来说应是当前工程。
在Name编辑框中,输入要创建的java文件名,也就是类名称。如wnAbout.
单击Superclass编辑框后面的"Browse"按钮,选择Activity类。
单击"Finish"按钮结束创建。
这样就创建了一个类。
然后开始创建xml布局文件。
单击工具栏新建按钮,选择Android XML file项。
在弹出的对话框中,单击"Project"后面的"Browse"按钮,选择要创建布局文件的项目。
在File文本框中,输入文件名称。如about.xml。要注意,必须要输入xml这个扩展名。
在"What type of Resource Would you like create?"单选框中,选择Layout项,意思是创建一个布局的xml文件。
在"Select the root element for the XML file"下拉框中,选择LinearLayout项,意思是选择线性布局模式。
单击finish按钮完成创建。

为了让程序可以响应按钮的单击事件,需要建立一个监听器。
现在,单击res->layout,然后双击main.xml项,打开主界面设置页面。单击页面底部的main.xml,直接编辑该xml文件。将其更改成如下内容:
<Button android:text="@string/strAbout"
android:id="@+id/btnAbout"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
</Button>
意思是让按钮显示字符串strAbout的内容,并且将按钮的id设置为btnAbout。
单击res->values,双击打开strings.xml文件,在其中加入:
<string name="strAbout">about</string>
意思是将strAbout字符串的内容设置为about。
这样,就做好了准备工作。可以单击工具栏的运行按钮或者按"Ctrl+F11"来启动模拟器,并在模拟器中运行该程序。应该可以看到一个问候语字符串和一个显示了About字符的按钮。
现在开始输入代码。
单击src->项目空间名称,双击项目名称为名的java文件。
修改该文件,使其像如下样子:
public class wnReader extends Activity {

private Button btnAbout;//定义一个按钮对象
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);

btnAbout=(Button)findViewById(R.id.btnAbout);
btnAbout.setOnClickListener(new View.OnClickListener()//设置监听器
{
public void onClick(View v) {
About();//按钮单击时触发About函数。
}
}); protected void About() {
Intent i = new Intent(this, wnAbout.class);
startActivity(i);
}

}

protected void About()//这个是按钮单击时触发的事件,注意应该定义为protected类型。
{
Intent i = new Intent(this, wnAbout.class);
startActivity(i);
}
现在建立了按钮单击事件的监听器,可以响应该按钮的单击事件。为了让程序可以打开新建的activity,首先需要修改一下AAndroidManifest.xml文件。双击该文件,打开。
在其中加入如下一段内容:
<activity android:name=".wnAbout"
android:label="@string/strAbout">
</activity>
修改后的内容应该如下所示:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.wnReader"
android:versionCode="1"
android:versionName="1.0">
<application android:icon="@drawable/icon" android:label="@string/app_name">
<activity android:name=".wnReader"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

<activity android:name=".wnAbout"
android:label="@string/strAbout">
</activity>

</application>
<uses-sdk android:minSdkVersion="7" />

</manifest>
但是由于系统自动建立的java文件和xml布局文件其实是空白的,所以应该在其上加入一些控件。
单击src->com.wnReader,双击其中的wnAbout.java,打开该文件。修改其内容,修改后内容应该如下所示:
public class wnAbout extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.about);//意思是显示名为about.xml的布局文件
}
}
单击res->layout,双击about.xml文件。

我的第一个菜单程序

创建菜单
一般来说,菜单有选项菜单、上下文菜单,以及子菜单。
选项菜单是指程序运行时,按Menu键弹出的菜单,一般来说,一屏最多显示六个菜单项而且参照惯例,应该会给每个菜单项添加图标。而上下文菜单,是指长按某项后出现的菜单。子菜单,顾名思义,就是一个菜单的下级菜单。
创建菜单可以通过代码menu.add来实现。创建菜单应该重写函数onCreateOptionsMenu.使用代码创建菜单的示例如下:
/* Creates the menu items */
public boolean onCreateOptionsMenu(Menu menu) {
menu.add(0, MenuOpenBook, 0, "Open book");
menu.add(0, MenuOption, 0, "Option");
return true;
}

/* 如果要让这些菜单做些工作的话,可以使用如下函数。要注意的是,这些功能都是在重写函数onOptionsItemSelected中实现 */
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {//使用getItemId来获得选中的菜单项。
case MenuOpenBook:
OpenBook();
return true;
case MenuOption:
Option();
return true;
}
return false;
}
如果要让这个菜单在不同的时候有不同的内容,可以重写onPrepareOptionsMenu()函数来实现。该函数在每次菜单被加载时都会运行。
不过,为了方便起见,还是建议使用xml文件来创建菜单。下面来介绍一下使用xml文件创建菜单的方法。
首先,右键单击res项,选择new菜单项,在弹出的级联菜单中选择folder,在Folder
name中输入menu,创建存放菜单文件的目录。这个位置和名称都是不能更改的。必须存放在res目录中,子目录名称必须为menu。
然后,单击工具栏的新建按钮,选择Android xml file。
在File文本框中输入要创建的菜单文件的名称,如menuopenbook.xml。注意,这里的名字一般应该采用小写字母与数字的组合,不应该会采用大写字母。
在"What type of resource would you like to create?"单选框中选择Menu项。意思是创建一个菜单XML文件。
Folder文本框会自动定位到res/menu目录。
单击Finish完成创建。
现在虽然已经创建了一个菜单文件,但其中各项内容还是空白,需要向其中添加内容。
依次单击res->menu->menuopenbook.xml,打开文件准备编辑。
单击页面下方的layout切换到设计视图。
单击Add按钮,在弹出的对话框中选择Create a new element at the top level ,in Menu.意思是创建一个菜单项。
在中间的文本框中输入菜单项的名称。如menuopen。
在列表栏中选择Item项。
单击Ok。
将Id更改为@+id/menuopen,将Title更改为open book。
现在就新建了一个菜单项,id为menuopen,而显示的文字是open book。

打开项目的主文件,wnReader.java,在其中添加代码,将菜单应用到项目中去。
只需要添加如下代码即可:
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.menuopenbook, menu);
return true;
}
然后在import区域中添加如下两行:
import android.view.Menu;
import android.view.MenuInflater;
按Ctrl+F11运行程序,在主界面出现时,按模拟器的Menu键,刚才设计的菜单就会从屏幕下方弹出。

Tuesday, July 13, 2010

android学习小结2

文章分类:移动开发
1 在1。5的android中,可以指定edittext中只输入数字,比如可以这样,就弹出小键盘了:
android:inputType="numberDecimal",这样还可以接受输入包含小数点的数字了。
2 读取资源文件中的内容
result.setText(getText(R.String.bmi_result));
注意使用getText函数,读取资源文件中的内容.

3 做一个象"关于我们"的对话框:
new AlertDialog.Builder(Bmi.this)
.setTitle(R.string.about_title)
.setMessage(R.string.about_msg)
.setPositiveButton("确认",
new DialogInterface.OnClickListener(){
public void onClick(
DialogInterface dialoginterface, int i){
}
})
.show();
4 toast组件:显示短的提示消息,过几秒之后就消失:
Toast.makeText(Bmi.this, "关于我们", Toast.LENGTH_SHORT).show();
5 url的打开
new AlertDialog.Builder(Bmi.this)
.setNegativeButton(R.string.homepage_label,
new DialogInterface.OnClickListener(){
public void onClick(
DialogInterface dialoginterface, int i){
//go to url
Uri uri = Uri.parse("http://sites.google.com/site/gasodroid/");
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
startActivity(intent);
}
})
.show();
也可以把URI写到资源文件中去,
Uri uri=uri.parase(getString(R.string.homepage_uri));
6 menu菜单
public boolean onCreateOptionsMenu(Menu menu) {
// TODO Auto-generated method stub
menu.add(0, MENU_ABOUT, 0, "关于").setIcon(R.drawable.help_browser);
menu.add(0, MENU_Quit, 0, "结束").setIcon(R.drawable.emblem_unreadable);
return super.onCreateOptionsMenu(menu);
}
处理menu的点击动作
public boolean onOptionsItemSelected(MenuItem item) {
// TODO Auto-generated method stub
switch(item.getItemId()) {
case MENU_ABOUT:
openOptionsDialog();
break;
case MENU_Quit:
finish();
break;
}
return super.onOptionsItemSelected(item);
}
7 <activity android:name=".Bmi"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>、
这里指出了启动的类是Bmi这个类,,<intent-filter>讲明了这个程序的性质,其中
<action android:name="android.intent.action.MAIN" />讲明了这个BMI是程序的切入点,
<category android:name="android.intent.category.LAUNCHER"
/>讲明这个出现在程序的lanucher列表中
8 intent之间传递数据
Intent intent = new Intent();
intent.setClass(Bmi.this, Report.class);
Bundle bundle = new Bundle();
bundle.putString("KEY_HEIGHT",
field_height.getText().toString());
bundle.putString("KEY_WEIGHT",
field_weight.getText().toString());
intent.putExtras(bundle);
startActivity(intent);
接收信息:
Bundle bunde = this.getIntent().getExtras();
double height = Double.parseDouble(bunde.getString("KEY_HEIGHT"))/100;
9 使用状态拦信息
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;

NotificationManager barManager = (NotificationManager)
getSystemService(NOTIFICATION_SERVICE);

Notification barMsg = new Notification(
R.drawable.icon_128,
"HI�",
System.currentTimeMillis()
);
barMsg.defaults |= Notification.DEFAULT_SOUND;
//barMsg.defaults |= Notification.DEFAULT_ALL;

PendingIntent contentIntent = PendingIntent.getActivity(
this,
0,
new Intent(this, Bmi.class),
PendingIntent.FLAG_UPDATE_CURRENT);

barMsg.setLatestEventInfo(
Report.this,
"HI",
"OK",
contentIntent);

barManager.notify(0, barMsg);
NotificationManager barManager = (NotificationManager)
getSystemService(NOTIFICATION_SERVICE);
声明管理器,
Notification barMsg = new Notification(
R.drawable.icon_128,
"HI�",
System.currentTimeMillis()
);
中声明提醒信息,System.currentTimeMillis()表示立刻显示;
barMsg.setLatestEventInfo(
Report.this,
"HI",
"OK",
contentIntent);
这里是添加状态栏的详细信息,这里的"HI"是显示的主题,第三个参数,这里的'OK'是说明,
contentIntent这个参数指明当点状态栏时,相应打开的intent.
10 保存优先级的设置:
private void restorePrefs() {
SharedPreferences settings = getSharedPreferences(PREF, 0);
String pref_height = settings.getString(PREF_HEIGHT, "");

}
这里是找系统中是否以"BMI_PREF"字符串作为文件名的优先级设置的文件,有的话,以settings做为代号来操作
当用户离开activity时,把值进行保存,重写onPause()函数:
super.onPause();
// Save user preferences. use Editor object to make changes.
SharedPreferences settings = getSharedPreferences(PREF, 0);
settings.edit()
.putString(PREF_HEIGHT, field_height.getText().toString())
.commit();
11 多语言界面并存:
比如原来的是英文,则res目录下放values,中文的话,在res目录下再放一个values-zh-rTW
其中zh是主语系,-r是必须的,后面跟分支
Resources res = getResources();
Configuration conf = res.getConfiguration();
conf.locale = Locale.TRADITIONAL_CHINESE;
DisplayMetrics dm = res.getDisplayMetrics();
res.updateConfiguration(conf, dm);
这里是强制使用中文界面,在程序中,针对不同语言提供不同的界面:
if (conf.locale==Local.TRADITIONAL_CHINESE)
12 Spinner下拉菜单中的选择事件:
field_feet.setOnItemSelectedListener(getFeet);
private Spinner.OnItemSelectedListener getFeet = new
Spinner.OnItemSelectedListener() {
public void onItemSelected(AdapterView parent, View v, int
position, long id) {
。。。。。。
}
public void onNothingSelected(AdapterView parent) {
}
};

android学习小结3

文章分类:移动开发
1 activity可以继承扩展 ListActivity
比如:
class DummyNote extends ListActivity {
private String[] note_array = {

"gasolin",
"crota",
"louk",
"magicion"

};
ListAdapter adapter = new ArrayAdapter<String>(this,
android.R.layout.simple_list_item_1,
note_array);
setListAdapter(adapter);

}
2 使用sqlite
在新建立工程后,打开模拟器后,在sdk的tools下
运行adb shell
cd data/data/
ls
cd 工程名
mkdir databases
cd databases
sqlite3 notes.db (建立了一个notes.db数据库)
create talbe notes
......;
sqlite>.databases (查看当前目录下的数据库列表)
sqllite>.tables (查看所有数据表)
.schema notes (查看指定表的结构)
离开sqllite: .exit

3 CRUD的典型例子
public class NotesDbAdapter {

private static final String DATABASE_NAME = "notes.db";
private static final int DATABASE_VERSION = 1;
private static final String DATABASE_TABLE = "notes";
private static final String DATABASE_CREATE =
"create table notes("
+"_id INTEGER PRIMARY KEY,"
+"note TEXT,"
+"created INTEGER,"
+"modified INTEGER"
+");";
private static class DatabaseHelper extends SQLiteOpenHelper {
public DatabaseHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
// TODO Auto-generated constructor stub
}
@Override
public void onCreate(SQLiteDatabase db) {
// TODO Auto-generated method stub
db.execSQL(DATABASE_CREATE);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// TODO Auto-generated method stub
db.execSQL("DROP TABLE IF EXISTS "+DATABASE_TABLE);
onCreate(db);
}
}

private Context mCtx = null;
private DatabaseHelper dbHelper ;
private SQLiteDatabase db;
/** Constructor */
public NotesDbAdapter(Context ctx) {
this.mCtx = ctx;
}
public NotesDbAdapter open () throws SQLException {
dbHelper = new DatabaseHelper(mCtx);
db = dbHelper.getWritableDatabase();
return this;
}
public void close() {
dbHelper.close();
}
//start query
public static final String KEY_ROWID = "_id";
public static final String KEY_NOTE = "note";
public static final String KEY_CREATED = "created";
String[] strCols = new String[] {
KEY_ROWID,
KEY_NOTE,
KEY_CREATED
};
/*
public Cursor getall() {
return db.rawQuery("SELECT * FROM notes", null);
}
*/

/*
// get all entries
public Cursor getall() {
return db.query(DATABASE_TABLE, //Which table to Select
strCols,// Which columns to return
null, // WHERE clause
null, // WHERE arguments
null, // GROUP BY clause
null, // HAVING clause
null //Order-by clause
);
}
*/

// get all entries
public Cursor getall() {
return db.query(DATABASE_TABLE,
new String[] {KEY_ROWID, KEY_NOTE, KEY_CREATED},
null, null, null, null, null);
}
// add an entry
public long create(String Note) {
Date now = new Date();
ContentValues args = new ContentValues();
args.put(KEY_NOTE, Note);
args.put(KEY_CREATED, now.getTime());
return db.insert(DATABASE_TABLE, null, args);
}
//remove an entry
public boolean delete(long rowId) {
return db.delete(DATABASE_TABLE, KEY_ROWID + "=" + rowId, null) > 0;
}

//query single entry
public Cursor get(long rowId) throws SQLException {
Cursor mCursor = db.query(true,
DATABASE_TABLE,
new String[] {KEY_ROWID, KEY_NOTE, KEY_CREATED},
KEY_ROWID + "=" + rowId,
null, null, null, null, null);
if (mCursor != null) {
mCursor.moveToFirst();
}
return mCursor;
}
//update
public boolean update(long rowId, String note) {
ContentValues args = new ContentValues();
args.put(KEY_NOTE, note);
return db.update(DATABASE_TABLE, args, KEY_ROWID + "=" +
rowId, null) > 0;
}

}
主程序调用:
private NotesDbAdapter mDbHelper;
private Cursor mNotesCursor;

private void setAdapter() {
mDbHelper = new NotesDbAdapter(this);
mDbHelper.open();
fillData();
}
private void fillData() {
mNotesCursor = mDbHelper.getall();
startManagingCursor(mNotesCursor);
String[] from = new String[]{"note"};
int[] to = new int[]{android.R.id.text1};
// Now create a simple cursor adapter
SimpleCursorAdapter adapter =
new SimpleCursorAdapter(this,
android.R.layout.simple_list_item_1, mNotesCursor,
from, to);
setListAdapter(adapter);
}

新增记录:
public long create(String Note) {
Date now = new Date();
ContentValues args = new ContentValues();
args.put(KEY_NOTE, Note);
。。。。。。
return db.insert(DATABASE_TABLE, null, args);
}
删除记录:
public boolean delete(long rowId) {
return db.delete(DATABASE_TABLE, KEY_ROWID + "=" + rowId, null) > 0;
}
mDbHelper.delete(getListView().getSelectedItemId());//这里用getListView().getSelectedItemId()获得选定
删除哪一条记录
查询记录:
//query single entry
public Cursor get(long rowId) throws SQLException {
Cursor mCursor = db.query(true,
DATABASE_TABLE,
new String[] {KEY_ROWID, KEY_NOTE, KEY_CREATED},
KEY_ROWID + "=" + rowId,
null, null, null, null, null);
if (mCursor != null) {
mCursor.moveToFirst();
}
return mCursor;
}
更新记录:
//update
public boolean update(long rowId, String note) {
ContentValues args = new ContentValues();
args.put(KEY_NOTE, note);
return db.update(DATABASE_TABLE, args, KEY_ROWID + "=" +
rowId, null) > 0;
}
4 activity中的相关,比如选了A中的记录,然后打开B来编辑,B编辑完后,再返回A。
首先在B中,接收BUNDLE:
private void showViews(Bundle savedInstanceState) {
//mRowId = savedInstanceState != null ?
savedInstanceState.getLong(NotesDbAdapter.KEY_ROWID) :
null;
if (mRowId == null) {
Bundle extras = getIntent().getExtras();
mRowId = extras != null ?
extras.getLong(NotesDbAdapter.KEY_ROWID) : null;
}
//把编辑的数据拿出来
if (mRowId != null) {
Cursor note = mDbHelper.get(mRowId);
startManagingCursor(note);
field_note.setText(note.getString(
note.getColumnIndexOrThrow(NotesDbAdapter.KEY_NOTE)
));
}
当编辑成功提交后,
public void onClick(View view) {
mDbHelper.update(mRowId, field_note.getText().toString());
setResult(RESULT_OK);
finish();
这里的setResult,表示这个activity成功,返回
在调用方中,如果是一个LISTVIEW的列表的话,点某一个列
protected void onListItemClick(ListView l, View v, int position, long id) {
super.onListItemClick(l, v, position, id);
Intent intent = new Intent(this, NoteEdit.class);
intent.putExtra(NotesDbAdapter.KEY_ROWID, id);
startActivityForResult(intent, ACTIVITY_EDIT);
}
这里的startActivityForResult表示的是要调用另外一个activity,并且要求结果返回
同时:
protected void onActivityResult(int requestCode, int resultCode,
Intent intent) {
super.onActivityResult(requestCode, resultCode, intent);
fillData();
}
5 长按菜单
A 在oncreate函数中,注册
registerForContextMenu(getListView());//说明点listview时会使用长按菜单
B public void onCreateContextMenu(ContextMenu menu, View v,
ContextMenuInfo menuInfo) {
// TODO Auto-generated method stub
menu.add(0, MENU_DELETE, 0, "删除记事");
menu.setHeaderTitle("要怎么处理这个项目");
super.onCreateContextMenu(menu, v, menuInfo);
}

thanks party ,this post by email.

android学习小结4

文章分类:移动开发

1 用一个VIEW做为消息提示
btn3.setOnClickListener(new Button.OnClickListener() {
public void onClick(View v) {
View view = inflateView(R.layout.view);
TextView txtMsg = (TextView) view.findViewById(R.id.txtMsg);
txtMsg.setText("提示内容");

Toast toast = new Toast(Main.this);
toast.setView(view);
toast.setDuration(Toast.LENGTH_LONG);
toast.show();
}
});
2 状态栏通知
public void onClick(View v) {
// 实例化通知管理器
NotificationManager nm = (NotificationManager) getSystemService

(NOTIFICATION_SERVICE);

// 指定单击通知后所打开的详细的通知页面(单击通知后打开 NotificationView


PendingIntent contentIntent = PendingIntent.getActivity(
Main.this, 0, new Intent(Main.this,

NotificationView.class), 0);

// 实例化一个通知,并指定其图标和标题(在提示栏上显示)
Notification n = new Notification(R.drawable.icon01, "我是滚动的通知信息

我是滚动的通知信息我是滚动的通知信息", System.currentTimeMillis());

// 设置通知的发送人和通知的详细内容(打开提示栏后在通知列表中显示)
n.setLatestEventInfo(Main.this, "通知发送人", "我是详细的通知信息我是详

细的通知信息我是详细的通知信息", contentIntent);

// 100 毫秒延迟后,震动 250 毫秒,暂停 100 毫秒后,再震动 500 毫秒
n.vibrate = new long[] { 100, 250, 100, 500 };

// 发出通知(其中第一个参数为通知标识符)
nm.notify(0, n);
}
});

3 图片按钮:
ImageButton imgButton = (ImageButton) this.findViewById(R.id.imageButton);
// 设置图片按钮的背景
imgButton.setBackgroundResource(R.drawable.icon01);
4 图片显示:
ImageView imgView = (ImageView) this.findViewById(R.id.imageView);
// 指定需要显示的图片
imgView.setBackgroundResource(R.drawable.icon01);
5 checkbox:
CheckBox chk = (CheckBox) this.findViewById(R.id.chk1);
// setOnCheckedChangeListener() - 响应复选框的选中状态改变事件
chk.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {

public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
TextView txt = (TextView) _CheckBox.this.findViewById(R.id.textView);
txt.setText("CheckBox01 的选中状态:" + String.valueOf(isChecked));


}
});

6 ToggleButton:
<!--
ToggleButton - 双状态按钮控件
textOn - 当按钮状态为 true 时所显示的文本
textOff - 当按钮状态为 false 时所显示的文本
-->
<ToggleButton android:id="@+id/toggleButton"
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:textOn="关闭" android:textOff="打开" />

final ToggleButton btn = (ToggleButton)
this.findViewById(R.id.toggleButton);
// setOnClickListener() - 响应按钮的鼠标单击事件
btn.setOnClickListener(new Button.OnClickListener(){
@Override
public void onClick(View v) {
TextView txt = (TextView)
_ToggleButton.this.findViewById(R.id.textView);
// ToggleButton.isChecked() - 双状态按钮的按钮状态
txt.setText("按钮状态:" + String.valueOf(btn.isChecked()));
}
});
7 进度条:
<!--
进度条控件(条状)的演示
style - 进度条的样式,本例使用内置样式
max - 进度的最大值
progress - 第一进度位置
secondaryProgress - 第二进度位置
-->
<ProgressBar android:id="@+id/progress_horizontal"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="200px"
android:layout_height="wrap_content" android:max="100"
android:progress="50" android:secondaryProgress="75" />

// 设置特性以允许在应用程序的标题栏上显示进度条(条状)
requestWindowFeature(Window.FEATURE_PROGRESS);
// 设置特性以允许在应用程序的标题栏上显示进度条(圆圈状)
requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);

this.setContentView(R.layout.progressbar);

setTitle("ProgressBar");

// 在标题栏上显示进度条(条状)
setProgressBarVisibility(true);
// 在标题栏上显示进度条(圆圈状)
setProgressBarIndeterminateVisibility(true);

// 指定进度条的进度
setProgress(50 * 100);
setSecondaryProgress(75 * 100);

9 SeekBar:
<!--
SeekBar - 可拖动的进度条控件
max - 进度的最大值
progress - 第一进度位置
secondaryProgress - 第二进度位置
-->
<SeekBar android:id="@+id/seekBar" android:layout_width="fill_parent"
android:layout_height="wrap_content" android:max="100"
android:progress="50" android:secondaryProgress="75" />

mSeekBar = (SeekBar) findViewById(R.id.seekBar);
// setOnSeekBarChangeListener() - 响应拖动进度条事件
mSeekBar.setOnSeekBarChangeListener(this);
// 拖动进度条后,进度发生改变时的回调事件
public void onProgressChanged(SeekBar seekBar, int progress,
boolean fromTouch) {
mProgressText.setText(progress + "%");
}

// 拖动进度条前开始跟踪触摸
public void onStartTrackingTouch(SeekBar seekBar) {
mTrackingText.setText("开始跟踪触摸");
}

// 拖动进度条后停止跟踪触摸
public void onStopTrackingTouch(SeekBar seekBar) {
mTrackingText.setText("停止跟踪触摸");
}

10 放大缩小控件:
ZoomControls zoomControls = (ZoomControls)
this.findViewById(R.id.zoomControls);
// setOnZoomInClickListener() - 响应单击放大按钮的事件
zoomControls.setOnZoomInClickListener(new OnClickListener() {
public void onClick(View v) {
Toast.makeText(_ZoomControls.this, "单击了放大按钮",

Toast.LENGTH_SHORT).show();
}
});

// setOnZoomOutClickListener() - 响应单击缩小按钮的事件
zoomControls.setOnZoomOutClickListener(new OnClickListener() {
public void onClick(View v) {
Toast.makeText(_ZoomControls.this, "单击了缩小按钮",

Toast.LENGTH_SHORT).show();
}
});

11 videoview:
VideoView videoView = (VideoView) findViewById(R.id.videoView);

// 指定需要播放的视频的地址
videoView.setVideoURI(Uri.parse("android.resource://com.webabcd.view/"
+ R.raw.demo));
// videoView.setVideoPath();

// 设置播放器的控制条
videoView.setMediaController(new MediaController(this));
// 开始播放视频
videoView.start();

12 tab控件:
<!-- Tab 1 的内容 -->
<TextView android:id="@+id/view1" android:layout_width="fill_parent"
android:layout_height="fill_parent" android:text="tab1 content" />

<!-- Tab 2 的内容 -->
<TextView android:id="@+id/view2" android:layout_width="fill_parent"
android:layout_height="fill_parent" android:text="tab2 content" />

// 实现 Tab 功能的话要继承 TabActivity
public class _Tab extends TabActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);

TabHost tabHost = getTabHost();
LayoutInflater.from(this).inflate(R.layout.tab,
tabHost.getTabContentView(), true);

// Tab 1 的内容
tabHost.addTab(tabHost.newTabSpec("tab1")
.setIndicator("tab1")
.setContent(R.id.view1));

// Tab 2 的内容(设置了 Tab 图片)
tabHost.addTab(tabHost.newTabSpec("tab2")
.setIndicator("tab2", getResources().getDrawable(R.drawable.icon01))
.setContent(R.id.view2));

// Tab 3 的内容(设置 Tab 的内容为指定的 Activity)
tabHost.addTab(tabHost.newTabSpec("tab3")
.setIndicator("tab3")
.setContent(new Intent(this, _TextView.class)));

}
}

13 GALLERY缩略图组件:
<!--
Gallery - 缩略图浏览器控件
spacing - 缩略图列表中各个缩略图之间的间距
-->
<Gallery android:id="@+id/gallery" android:layout_width="fill_parent"
android:layout_height="wrap_content" android:spacing="20px" />

Gallery gallery = (Gallery) findViewById(R.id.gallery);
// 为缩略图浏览器指定一个适配器
gallery.setAdapter(new ImageAdapter(this));
// 响应 在缩略图列表上选中某个缩略图后的 事件
gallery.setOnItemSelectedListener(new
AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View v,
int position, long id) {
Toast.makeText(_Gallery.this,
String.valueOf(position), Toast.LENGTH_SHORT).show();
}

@Override
public void onNothingSelected(AdapterView<?> arg0) {

}
});
}

// 继承 BaseAdapter 用以实现自定义的图片适配器
public class ImageAdapter extends BaseAdapter {

private Context mContext;

public ImageAdapter(Context context) {
mContext = context;
}

public int getCount() {
return mThumbIds.length;
}

public Object getItem(int position) {
return position;
}

public long getItemId(int position) {
return position;
}

public View getView(int position, View convertView, ViewGroup parent) {
ImageView image = new ImageView(mContext);

image.setImageResource(mThumbIds[position]);
image.setAdjustViewBounds(true);
image.setLayoutParams(new Gallery.LayoutParams(
LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));

return image;
}
}

// 需要显示的图片集合
private Integer[] mThumbIds = { R.drawable.icon01, R.drawable.icon02,
R.drawable.icon03, R.drawable.icon04, R.drawable.icon05 };

android beginning :button and OnClick

Android 入门到精通 (界面编程#1-从Button说起)
Android 界面编程有两种基本的方法,一种是在代码中,动态创建一个个组件,及把这些组件用Layout来进行组合成复杂的界面展现。一种是用图形化的方式来编写 布局Layout,这些布局被保存在XML文件中,会编译成资源,被程序中的Activity来加载(setContentView()), 再通过findViewById方式来获得每一个界面组件的引用进行操作。对于大多数人来说,喜欢最直观的方式,既代码中动态生成的方式。我们就先从这里说起,至于可视化编程及布局资源的方式以后专门来讲述。
一,布局管理(Layout)
      每一个界面组件都是View的子类,都可以单独占用一个屏幕,但是真正的有用的界面都是这些组件的组合,在Android中都是用各种Layout来进行布局管理,这与传统的J2SE中的一些AWT,SWING界面方式基本相同,这里就不多说。
二,一个单独的界面元素:
     在前面说到Hello World例子中,讲过这样一段代码。在Activity中.
   public class HelloActivity extends Activity {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        TextView tv = new TextView(this);
        tv.setText("Hello, World!");
        this.setContentView(tv);
       
    }
}
这里并没有用到Layout,这就是单独的组件方式。也可以改为:
super.onCreate(savedInstanceState);
        Button btn = new Button(this);
        btn.setText("TestButton");
        this.setContentView(btn);
编译运行,会有一个全屏的Button,当然这不是你想要的实用的界面.那我们就用Layout来布局
        super.onCreate(savedInstanceState);
        Button btn = new Button(this);
        btn.setText("TestButton");
        Button btn2 = new Button(this);
        btn2.setText("TestButton2");
        LinearLayout layout = new LinearLayout(this);
        layout.setOrientation(LinearLayout.VERTICAL);
        layout.addView(btn);
        layout.addView(btn2);
        this.setContentView(layout);
编译运行,你就可以看到了两个上下排列的按钮,当然对于布局管理器的使用,做过PC 上AWT,SWING的人都不陌生,这里就不赘述。
       那如何响应事件呢: 大家猜一猜?想必大家不难猜到,在AWT中,在手机的J2ME中,都是用Listener 来处理事件响应,Android也未能脱俗。这与Blackberry,Symbian中的Observer是同一个道理。都是使用了设计模式的观察者模式。下面来看一个能响应事件的例子。

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.LinearLayout;


public class HelloActivity extends Activity implements OnClickListener {
  Button btn = null;
  Button btn2 = null;
    public void onClick(View v) {    
  if (v ==  btn)
  {
      this.setTitle("You Clicked Button1");      
  }
  if (v ==  btn2)
  {
      this.setTitle("You Clicked Button2");
  }  
 } 
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        btn = new Button(this);
        btn2 = new Button(this);
        btn.setText("TestButton1");        
        btn2.setText("TestButton2");
        btn.setOnClickListener(this);
        btn2.setOnClickListener(this);
        LinearLayout layout = new LinearLayout(this);
        layout.setOrientation(LinearLayout.VERTICAL);
        layout.addView(btn);
        layout.addView(btn2);
        this.setContentView(layout);       
    }
}

步骤是:
一,生成两个Button,配置Click事件监听者为HelloActivity ,此类实现了OnClickListener接口。
二,放入布局,按布局显示两个Button
三,按下其中一个Button,生成Click事件,调用HelloActivity 的OnClick接口函数。
四,对于View参数的值,判断是哪个View(Button)。改写Activity的Titile内容。注意,可别去对比View.getId(),缺省情况下,每个组件的Id值都为-1,除非人为设定Id值,用可视化编程时,为自动为其生成一个Id值。

Monday, July 12, 2010

Android API – SMS handling

Android API – SMS handling

December 29th, 2008

Many new application will use SMS as data delivery platform. Reality shows, on-demand movies etc request users to send predefined formatted SMS. Similarly some applications are coming up which sends data to user using SMS. Let's see how such an application can be built using Android [^]platform.

Android API support developing applications that can send and receive SMS messages. The android emulator does not support sending of the SMS currently. But the emulator can receive SMS. Lets explore the android SMS support and develop a small program that listens to the SMSes received on the device (on emulator) and will show that message as notification.

The event handling on Android is done with the help of intents and intent receivers. The intents  announce (or broadcast) the event and intent receivers respond to the event. Intent receivers act as the event handlers.

Let's define an intent receiver that can handle the SMS received event:

  package com.wissen.sms.receiver; /** * The class is called when SMS is received. */ public class SMSReceiver extends BroadcastReceiver {  @Override public void onReceive(Context context, Intent intent) { // TODO } } 

We need to configure this intent receiver to receive SMS receive event. For SMS receive event android has defined an intent as ' android.provider.Telephony.SMS_RECEIVED '. The receiver can be configured in AndroidManifest.xml as follows:

       

To receive SMS, application also needs to specify permission for receiving SMS. The permission can be set in AndroidManifest.xml as follows:

   

Now our intent receiver is all set to be called when the android device will receive SMS. Now we only need to retrieve the received SMS and show the SMS text in a notification.

Here is the code of intent receiver that will read the SMS from intent received and show the first message (pdu).

  public void onReceive(Context context, Intent intent) { 		Bundle bundle = intent.getExtras();  		Object messages[] = (Object[]) bundle.get("pdus"); 		SmsMessage smsMessage[] = new SmsMessage[messages.length]; 		for (int n = 0; n &lt; messages.length; n++) { 		smsMessage[n] = SmsMessage.createFromPdu((byte[]) messages[n]); 		}  		// show first message 		Toast toast = Toast.makeText(context, 		"Received SMS: " + smsMessage[0].getMessageBody(), Toast.LENGTH_LONG); 		toast.show(); 		} 

The SMS received by the Android device is in the form of pdus (protocol description unit). Class SmsMessage, defined in android.telephony.gsm package, can store information about the SMS. The class can also be used to create SmsMessage object from received pdus. Toast widget is used to show the SMS body as an notification.

Running the Program:
Only remaining thing now is running the application and sending the SMS message to the emulator. An SMS message can be sent to the emulator in the DDMS eclipse perspective (Dalvik Debug Monitor Service). 'Emulator Control' window can be used to send SMS message (an incoming number has to be provided which can be anything).

Here is the application screen shot in action,

android 界面安排

这是我的一个Android程序的界面,很简单,一个输入用户名,一个输入密码,下面是一个提交按钮。

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
    <TextView  
    android:layout_width="fill_parent" 
    android:layout_height="wrap_content" 
    android:text="@string/hello"
    android:layout_centerHorizontal="true"/>  
    
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:orientation="horizontal"
     android:layout_width="wrap_content"
   android:layout_height="wrap_content"
     android:alwaysDrawnWithCache="true">

<TextView 
android:id="@+id/TextView01" 
android:layout_width="wrap_content" 
android:layout_height="wrap_content" 
android:editable="false" 
android:enabled="true" 
android:singleLine="true" android:text="name:" android:minWidth="80dip">
</TextView>
<EditText 
android:id="@+id/EditText01" 
android:layout_width="wrap_content" 
android:layout_height="wrap_content" 
android:minWidth="152dip">
</EditText>      
    
    
</LinearLayout>
        


<LinearLayout 
   android:orientation="horizontal"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   >
   
<TextView android:id="@+id/TextView02" 
android:layout_width="wrap_content" 
android:layout_height="wrap_content" 
android:text="password:" android:minWidth="80dip">
</TextView>
<EditText android:id="@+id/EditText02" 
android:layout_width="wrap_content" 
android:layout_height="wrap_content" 
android:password="true" 
android:minWidth="152dip"></EditText>
</LinearLayout>
<LinearLayout 
   android:orientation="horizontal"
   android:layout_width="fill_parent"
   android:layout_height="wrap_content"
   >    
    <Button android:id="@+id/Button01" 
android:layout_width="wrap_content" 
android:layout_height="wrap_content" 
android:onClick="btn_login" 
android:text="login" 
android:layout_centerInParent="true"></Button>
</LinearLayout>

</LinearLayout>

android应用程序基础(包括活动、服务、广播、内容提供等)

 Android开发指南-框架主题-基础知识 收藏

应用程序基础

关键类

  1. Activity
  2. Service
  3. BroadcastReceiver
  4. ContentProvider
  5. Intent

Android应用程序使用Java做为开发语言。aapt工具把编译后的Java代码连同其它应用程序需要的数据和资源文件一起打包到一个Android包文件中,这个文件使用.apk做为扩展名,它是分发应用程序并安装到移动设备的媒介,用户只需下载并安装此文件到他们的设备。单一.apk文件中的所有代码被认为是一个应用程序。

从很多方面来看,每个Android应用程序都存在于它自己的世界之中:

  • 默认情况下,每个应用程序均运行于它自己的Linux进程中。当应用程序中的任意代码开始执行时,Android启动一个进程,而当不再需要此进程而其它应用程序又需要系统资源时,则关闭这个进程。
  • 每个进程都运行于自己的Java虚拟机(VM)中。所以应用程序代码实际上与其它应用程序的代码是隔绝的。
  • 默认情况下,每个应用程序均被赋予一个唯一的Linux用户ID,并加以权限设置,使得应用程序的文件仅对这个用户、这个应用程序可见。当然,也有其它的方法使得这些文件同样能为别的应用程序所访问。

使两个应用程序共有同一个用户ID是可行的,这种情况下他们可以看到彼此的文件。从系统资源维护的角度来看,拥有同一个ID的应用程序也将在运行时使用同一个Linux进程,以及同一个虚拟机。

应用程序组件

Android的核心功能之一就是一个应用程序可以使用其它应用程序的元素(如果那个应用程序允许的话)。比如说,如果你的应用程序需要一个图片卷动列表,而另一个应用程序已经开发了一个合用的而又允许别人使用的话,你可以直接调用那个卷动列表来完成工作,而不用自己再开发一个。你的应用程序并没有吸纳或链接其它应用程序的代码,它只是在有需求的时候启动了其它应用程序的那个功能部分。

为达到这个目的,系统必须在一个应用程序的一部分被需要时启动这个应用程序,并将那个部分的Java对象实例化。与在其它系统上的应用程序不同,Android应用程序没有为应用准备一个单独的程序入口(比如说,没有main()方法), 而是为系统依照需求实例化提供了基本的组件。共有四种组件类型:

Activity

Activity是为用户操作而展示的可视化用户界面。比如说,一个activity可以展示一个菜单项列表供用户选择,或者显示一些包含说明的照片。一个短消息应用程序可以包括一个用于显示做为发送对象的联系人的列表的activity,一个给选定的联系人写短信的activity以及翻阅以前的短信和改变设置的activity。尽管它们一起组成了一个内聚的用户界面,但其中每个activity都与其它的保持独立。每个都是以Activity类为基类的子类实现。

一个应用程序可以只有一个activity,或者,如刚才提到的短信应用程序那样,包含很多个。每个activity的作用,以及其数目,自然取决于应用程序及其设计。一般情况下,总有一个应用程序被标记为用户在应用程序启动的时候第一个看到的。从一个activity转向另一个的方式是靠当前的activity启动下一个。

每个activity都被给予一个默认的窗口以进行绘制。一般情况下,这个窗口是满屏的,但它也可以是一个小的位于其它窗口之上的浮动窗口。一个activity也可以使用超过一个的窗口──比如,在activity运行过程中弹出的一个供用户反应的小对话框,或是当用户选择了屏幕上特定项目后显示的必要信息。

窗口显示的可视内容是由一系列视图构成的,这些视图均继承自 View 基类。每个视图均控制着窗口中一块特定的矩形空间。父级视图包含并组织它子视图的布局。叶节点视图(位于视图层次最底端)在它们控制的矩形中进行绘制,并对用户对其直接操作做出响应。所以,视图是activity与用户进行交互的界面。比如说,视图可以显示一个小图片,并在用户指点它的时候产生动作。Android有很多既定的视图供用户直接使用,包括按钮、文本域、卷轴、菜单项、复选框等等。

视图层次是由Activity.setContentView() 方法放入activity的窗口之中的。上下文视图是位于视图层次根位置的视图对象。(参见用户界面章节获取关于视图及层次的更多信息。)

服务

服务没有可视化的用户界面,而是在一段时间内在后台运行。比如说,一个服务可以在用户做其它事情的时候在后台播放背景音乐、从网络上获取一些数据或者计算一些东西并提供给需要这个运算结果的activity使用。每个服务都继承自Service基类。

一个媒体播放器播放播放列表中的曲目是一个不错的例子。播放器应用程序可能有一个或多个activity来给用户选择歌曲并进行播放。然而,音乐播放这个任务本身不应该为任何activity所处理,因为用户期望在他们离开播放器应用程序而开始做别的事情时,音乐仍在继续播放。为达到这个目的,媒体播放器activity应该启用一个运行于后台的服务。而系统将在这个activity不再显示于屏幕之后,仍维持音乐播放服务的运行。

你可以连接至(绑定)一个正在运行的服务(如果服务没有运行,则启动之)。连接之后,你可以通过那个服务暴露出来的接口与服务进行通讯。对于音乐服务来说,这个接口可以允许用户暂停、回退、停止以及重新开始播放。

如同activity和其它组件一样,服务运行于应用程序进程的主线程内。所以它不会对其它组件或用户界面有任何干扰,它们一般会派生一个新线程来进行一些耗时任务(比如音乐回放)。参见下述 进程和线程 

广播接收器

广播接收器是一个专注于接收广播通知信息,并做出对应处理的组件。很多广播是源自于系统代码的──比如,通知时区改变、电池电量低、拍摄了一张照片或者用户改变了语言选项。应用程序也可以进行广播──比如说,通知其它应用程序一些数据下载完成并处于可用状态。


应用程序可以拥有任意数量的广播接收器以对所有它感兴趣的通知信息予以响应。所有的接收器均继承自BroadcastReceiver基类。
广播接收器没有用户界面。然而,它们可以启动一个activity来响应它们收到的信息,或者用NotificationManager来通知用户。通知可以用很多种方式来吸引用户的注意力──闪动背灯、震动、播放声音等等。一般来说是在状态栏上放一个持久的图标,用户可以打开它并获取消息。

内容提供者

内容提供者将一些特定的应用程序数据供给其它应用程序使用。数据可以存储于文件系统、SQLite数据库或其它方式。内容提供者继承于ContentProvider 基类,为其它应用程序取用和存储它管理的数据实现了一套标准方法。然而,应用程序并不直接调用这些方法,而是使用一个 ContentResolver 对象,调用它的方法作为替代。ContentResolver可以与任意内容提供者进行会话,与其合作来对所有相关交互通讯进行管理。

参阅独立的内容提供者章节获得更多关于使用内容提供者的内容。

每当出现一个需要被特定组件处理的请求时,Android会确保那个组件的应用程序进程处于运行状态,或在必要的时候启动它。并确保那个相应组件的实例的存在,必要时会创建那个实例。

激活组件:intent

当接收到ContentResolver发出的请求后,内容提供者被激活。而其它三种组件──activity、服务和广播接收器被一种叫做intent的异步消息所激活。intent是一个保存着消息内容的Intent对象。对于activity和服务来说,它指明了请求的操作名称以及作为操作对象的数据的URI和其它一些信息。比如说,它可以承载对一个activity的请求,让它为用户显示一张图片,或者让用户编辑一些文本。而对于广播接收器而言,Intent对象指明了声明的行为。比如,它可以对所有感兴趣的对象声明照相按钮被按下。

对于每种组件来说,激活的方法是不同的:

  • 通过传递一个Intent对象至 Context.startActivity()Activity.startActivityForResult()以载入(或指定新工作给)一个activity。相应的activity可以通过调用 getIntent() 方法来查看激活它的intent。Android通过调用activity的onNewIntent()方法来传递给它继发的intent。
    一个activity经常启动了下一个。如果它期望它所启动的那个activity返回一个结果,它会以调用startActivityForResult()来取代startActivity()。比如说,如果它启动了另外一个activity以使用户挑选一张照片,它也许想知道哪张照片被选中了。结果将会被封装在一个Intent对象中,并传递给发出调用的activity的onActivityResult() 方法。
  • 通过传递一个Intent对象至Context.startService()将启动一个服务(或给予正在运行的服务以一个新的指令)。Android调用服务的 onStart()方法并将Intent对象传递给它。
    与此类似,一个Intent可以被调用组件传递给 Context.bindService()以获取一个正在运行的目标服务的连接。这个服务会经由onBind() 方法的调用获取这个Intent对象(如果服务尚未启动,bindService()会先启动它)。比如说,一个activity可以连接至前述的音乐回放服务,并提供给用户一个可操作的(用户界面)以对回放进行控制。这个activity可以调用 bindService() 来建立连接,然后调用服务中定义的对象来影响回放。
    后面一节:
    远程方法调用将更详细的阐明如何绑定至服务。
  • 应用程序可以凭借将Intent对象传递给 Context.sendBroadcast() ,Context.sendOrderedBroadcast(), 以及Context.sendStickyBroadcast()和其它类似方法来产生一个广播。Android会调用所有对此广播有兴趣的广播接收器的 onReceive()方法,将intent传递给它们。

欲了解更多intent消息的信息,请参阅独立章节 Intent和Intent滤过器

关闭组件

内容提供者仅在响应ContentResolver提出请求的时候激活。而一个广播接收器仅在响应广播信息的时候激活。所以,没有必要去显式的关闭这些组件。

而activity则不同,它提供了用户界面,并与用户进行会话。所以只要会话依然持续,哪怕对话过程暂时停顿,它都会一直保持激活状态。与此相似,服务也会在很长一段时间内保持运行。所以Android为关闭activity和服务提供了一系列的方法。

  • 可以通过调用它的finish()方法来关闭一个activity。一个activity可以通过调用另外一个activity(它用startActivityForResult() 启动的)的finishActivity()方法来关闭它。
  • 服务可以通过调用它的stopSelf()方法来停止,或者调用 Context.stopService()

系统也会在组件不再被使用的时候或者Android需要为活动组件声明更多内存的时候关闭它。后面的 组件的生命周期一节,将对这种可能及附属情况进行更详细的讨论。

manifest文件

当Android启动一个应用程序组件之前,它必须知道那个组件是存在的。所以,应用程序会在一个manifest文件中声明它的组件,这个文件会被打包到Android包中。这个.apk文件还将涵括应用程序的代码、文件以及其它资源。

这个manifest文件以XML作为结构格式,而且对于所有应用程序,都叫做AndroidManifest.xml。为声明一个应用程序组件,它还会做很多额外工作,比如指明应用程序所需链接到的库的名称(除了默认的Android库之外)以及声明应用程序期望获得的各种权限。

但manifest文件的主要功能仍然是向Android声明应用程序的组件。举例说明,一个activity可以如下声明:

<?xml version="1.0" encoding="utf-8"?>
<manifest . . . >
<application . . . >
<activity android:name="com.example.project.FreneticActivity"
android:icon="@drawable/small_pic.png"
android:label="@string/freneticLabel"
. . . >
</activity>
. . .
</application>
</manifest>

<activity>元素的name属性指定了实现了这个activity的 Activity的子类。icon和label属性指向了包含展示给用户的此activity的图标和标签的资源文件。

其它组件也以类似的方法声明──<service> 元素用于声明服务, <receiver> 元素用于声明广播接收器,而 <provider> 元素用于声明内容提供者。 manifest文件中未进行声明的activity、服务以及内容提供者将不为系统所见,从而也就不会被运行。然而,广播接收器既可以在manifest文件中声明,也可以在代码中进行动态的创建,并以调用Context.registerReceiver()的方式注册至系统。

欲更多了解如何为你的应用程序构建manifest文件,请参阅AndroidManifest.xml文件一章。

Intent过滤器

Intent对象可以被显式的指定目标组件。如果进行了这种指定,Android会找到这个组件(依据manifest文件中的声明)并激活它。但如果Intent没有进行显式的指定,Android就必须为它找到对于intent来说最合适的组件。这个过程是通过比较Intent对象和所有可能对象的intent过滤器完成的。组件的intent过滤器会告知Android它所能处理的intent类型。如同其它相对于组件很重要的信息一样,这些是在manifest文件中进行声明的。这里是上面实例的一个扩展,其中加入了针对activity的两个intent过滤器声明:

<?xml version="1.0" encoding="utf-8"?>
<manifest . . . >
<application . . . >
<activity android:name="com.example.project.FreneticActivity"
android:icon="@drawable/small_pic.png"
android:label="@string/freneticLabel"
. . . >
<intent-filter . . . >
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter . . . >
<action android:name="com.example.project.BOUNCE" />
<data android:type="image/jpeg" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
. . .
</application>
</manifest>

示例中的第一个过滤器──action “android.intent.action.MAIN”和类别“android.intent.category.LAUNCHER”的组合──是通常具有的。它标明了这个activity将在应用程序加载器中显示,就是用户在设备上看到的可供加载的应用程序列表。换句话说,这个activity是应用程序的入口,是用户选择运行这个应用程序后所见到的第一个activity。

第二个过滤器声明了这个activity能被赋予一种特定类型的数据。

组件可以拥有任意数量的intent过滤器,每个都会声明一系列不同的能力。如果它没有包含任何过滤器,它将只能被显式声明了目标组件名称的intent激活。

对于在代码中创建并注册的广播接收器来说,intent过滤器将被直接以 IntentFilter对象实例化。其它过滤器则在manifest文件中设置。

欲获得更多intent过滤器的信息,请参阅独立章节: Intent和Intent过滤器

Activity和任务

如前所述,一个activity可以启动另外一个,甚至包括与它不处于同一应用程序之中的。举个例子说,假设你想让用户看到某个地方的街道地图。而已经存在一个具有此功能的activity了,那么你的activity所需要做的工作就是把请求信息放到一个Intent对象里面,并把它传递给startActivity()。于是地图浏览器就会显示那个地图。而当用户按下BACK键的时候,你的activity又会再一次的显示在屏幕上。

对于用户来说,这看起来就像是地图浏览器是你activity所在的应用程序中的一个组成部分,其实它是在另外一个应用程序中定义,并运行在那个应用程序的进程之中的。Android将这两个activity放在同一个任务中来维持一个完整的用户体验。简单的说,任务就是用户所体验到的“应用程序”。它是安排在一个堆栈中的一组相关的activity。堆栈中的根activity就是启动了这整个任务的那个──一般情况下,它就是用户在应用程序加载器中所选择的。而堆栈最上方的activity则是当前运行的──用户直接对其进行操作的。当一个activity启动另外一个的时候,新的activity就被压入堆栈,并成为当前运行的activity。而前一个activity仍保持在堆栈之中。当用户按下BACK键的时候,当前activity出栈,而前一个恢复为当前运行的activity。

堆栈中保存的其实是对象,所以如果发生了诸如需要多个地图浏览器的情况,就会使得一个任务中出现多个同一Activity子类的实例同时存在,堆栈会为每个实例单独开辟一个入口。堆栈中的Activity永远不会重排,只会压入或弹出。

任务其实就是activity的堆栈,而不是manifest文件中的一个类或者元素。所以你无法撇开activity而为一个任务设置一个值。而事实上整个任务使用的值是在根activity中设置的。比如说,下一节我们会谈及“任务的affinity”,从affinity中读出的值将会设置到任务的根activity之中。

任务中的所有activity是作为一个整体进行移动的。整个的任务(即activity堆栈)可以移到前台,或退至后台。举个例子说,比如当前任务在堆栈中存有四个activity──三个在当前activity之下。当用户按下HOME键的时候,回到了应用程序加载器,然后选择了一个新的应用程序(也就是一个新任务)。则当前任务遁入后台,而新任务的根activity显示出来。然后,过了一小会儿,用户再次回到了应用程序加载器而又选择了前一个应用程序(上一个任务)。于是那个任务,带着它堆栈中所有的四个activity,再一次的到了前台。当用户按下BACK键的时候,屏幕不会显示出用户刚才离开的activity(上一个任务的根activity)。取而代之,当前任务的堆栈中最上面的activity被弹出,而同一任务中的上一个activity显示了出来。

上述的种种即是activity和任务的默认行为模式。但是有一些方法可以改变所有这一切。activity和任务的联系、任务中activity的行为方式都被启动那个activity的Intent对象中设置的一系列标记和manifest文件中那个activity中的<activity>元素的系列属性之间的交互所控制。无论是请求发出者和回应者在这里都拥有话语权。

我们刚才所说的这些关键Intent标记如下:

FLAG_ACTIVITY_NEW_TASK 
FLAG_ACTIVITY_CLEAR_TOP 
FLAG_ACTIVITY_RESET_TASK_IF_NEEDED 
FLAG_ACTIVITY_SINGLE_TOP

而关键的<activity>属性是:

taskAffinity 
launchMode 
allowTaskReparenting 
clearTaskOnLaunch 
alwaysRetainTaskState 
finishOnTaskLaunch

接下来的一节会描述这些标记以及属性的作用,它们是如何互相影响的,以及控制它们的使用时必须考虑到的因素。

Affinity(吸引力)和新任务

默认情况下,一个应用程序中的activity相互之间会有一种Affinity──也就是说,它们首选都归属于一个任务。然而,可以在<activity>元素中把每个activity的taskAffinity属性设置为一个独立的affinity。于是在不同的应用程序中定义的activity可以享有同一个affinity,或者在同一个应用程序中定义的activity有着不同的affinity。affinity在两种情况下生效:当加载activity的Intent对象包含了FLAG_ACTIVITY_NEW_TASK 标记,或者当activity的allowTaskReparenting属性设置为“true”。

FLAG_ACTIVITY_NEW_TASK标记

如前所述,在默认情况下,一个新activity被另外一个调用了startActivity()方法的activity载入了任务之中。并压入了调用者所在的堆栈。然而,如果传递给startActivity()的Intent对象包含了FLAG_ACTIVITY_NEW_TASK标记,系统会为新activity安排另外一个任务。一般情况下,如同标记所暗示的那样,这会是一个新任务。然而,这并不是必然的。如果已经存在了一个与新activity有着同样affinity的任务,则activity会载入那个任务之中。如果没有,则启用新任务。

allowTaskReparenting 属性

如果一个activity将allowTaskReparenting属性设置为“true”。它就可以从初始的任务中转移到与其拥有同一个affinity并转向前台的任务之中。比如说,一个旅行应用程序中包含的预报所选城市的天气情况的activity。它与这个应用程序中其它的activity拥有同样的affinity(默认的affinity)而且允许重定父级。你的另一个activity启动了天气预报,于是它就会与这个activity共处与同一任务之中。然而,当那个旅行应用程序再次回到前台的时候,这个天气预报activity就会被再次安排到原先的任务之中并显示出来。

如果在用户的角度看来,一个.apk文件中包含了多于一个的“应用程序”,你可能会想要为它们所辖的activity安排不一样的affinity。

加载模式

<activity>元素的launchMode属性可以设置四种不同的加载模式:

"standard" (默认值)
"singleTop
"singleTask
"singleInstance"

这些模式之间的差异主要体现在四个方面:

  • 哪个任务会把持对intent做出响应的activity。对“standard”和“singleTop”模式而言,是产生intent(并调用 startActivity())的任务──除非Intent对象包含FLAG_ACTIVITY_NEW_TASK标记。而在这种情况下,如同上面Affinitie和新任务一节所述,会是另外一个任务。

    相反,对“singleTask”和“singleInstance”模式而言,activity总是位于任务的根部。正是它们定义了一个任务,所以它们绝不会被载入到其它任务之中。

  • activity是否可以存在多个实例。一个“standard”或“singleTop”的activity可以被多次初始化。它们可以归属于多个任务,而一个任务也可以拥有同一activity的多个实例。

    相反,对“singleTask”和“singleInstance”的activity被限定于只能有一个实例。因为这些activity都是任务的起源,这种限制意味着在一个设备中同一时间只允许存在一个任务的实例。

  • 在实例所在的任务中是否会有别的activity。一个“singleInstance”模式的activity将会是它所在的任务中唯一的activity。如果它启动了别的activity,那个activity将会依据它自己的加载模式加载到其它的任务中去──如同在intent中设置了FLAG_ACTIVITY_NEW_TASK 标记一样的效果。在其它方面,“singleInstance”模式的效果与“singleTask”是一样的。

    剩下的三种模式允许一个任务中出现多个activity。“singleTask”模式的activity将是任务的根activity,但它可以启动别的activity并将它们置入所在的任务中。“standard”和“singleTop”activity则可以在堆栈的任意位置出现。

  • 是否要载入新的类实例以处理新的intent。对默认的"standard"模式来说,对于每个新intent都会创建一个新的实例以进行响应,每个实例仅处理一个intent。“singleTop”模式下,如果activity位于目的任务堆栈的最上面,则重用目前现存的activity来处理新的intent。如果它不是在堆栈顶部,则不会发生重用。而是创建一个新实例来处理新的intent并将其推入堆栈。

    举例来说,假设一个任务的堆栈由根activityA和activity B、C和位于堆栈顶部的D组成,即堆栈A-B-C-D。一个针对D类型的activity的intent抵达的时候,如果D是默认的“standard”加载模式,则创建并加载一个新的类实例,于是堆栈变为A-B-C-D-D。 然而,如果D的载入模式为“singleTop”,则现有的实例会对新intent进行处理(因为它位于堆栈顶部)而堆栈保持A-B-C-D的形态。

    换言之,如果新抵达的intent是针对B类型的activity,则无论B的模式是“standard”还是“singleTop” ,都会加载一个新的B的实例(因为B不位于堆栈的顶部),而堆栈的顺序变为A-B-C-D-B。

    如前所述,“singleTask”或“singleInstance”模式的activity永远不会存在多于一个实例。所以实例将处理所有新的intent。一个“singleInstance”模式的activity永远保持在堆栈的顶部(因为它是那个堆栈中唯一的一个activity),所以它一直坚守在处理intent的岗位上。然而,对一个“singleTask”模式的activity来说,它上面可能有,也可能没有别的activity和它处于同一堆栈。在有的情况下,它就不在能够处理intent的位置上,则那个intent将被舍弃。(即便在intent被舍弃的情况下,它的抵达仍将使这个任务切换至前台,并一直保留)

当一个现存的activity被要求处理一个新的intent的时候,会调用onNewIntent()方法来将intent对象传递至activity。(启动activity的原始intent对象可以通过调用getIntent()方法获得。)

请注意,当一个新的activity实例被创建以处理新的intent的时候,用户总可以按下BACK键来回到前面的状态(回到前一个activity)。但当使用现存的activity来处理新intent的时候,用户是不能靠按下BACK键回到当这个新intent抵达之前的状态的。

想获得更多关于加载模式的内容,请参阅 <activity> 元素的描述。

清理堆栈

如果用户离开一个任务很长一段时间,系统会清理该任务中除了根activity之外的所有activity。当用户再次回到这个任务的时候,除了只剩下初始化activity尚存之外,其余都跟用户上次离开它的时候一样。这样做的原因是:在一段时间之后,用户再次回到一个任务的时候,他们更期望放弃他们之前的所作所为,做些新的事情。

这些属于默认行为,另外,也存在一些activity的属性用以控制并改变这些行为:

alwaysRetainTaskState 属性

如果一个任务的根activity中此属性设置为“true”,则上述默认行为不会发生。任务将在很长的一段时间内保留它堆栈内的所有activity。

clearTaskOnLaunch属性

如果一个任务的根activity中此属性设置为“true”,则每当用户离开这个任务和返回它的时候,堆栈都会被清空至只留下rootactivity。换句话说,这是alwaysRetainTaskState的另一个极端。哪怕仅是过了一小会儿,用户回到任务时,也是见到它的初始状态。

finishOnTaskLaunch属性

这个属性与clearTaskOnLaunch属性相似,但它仅作用于单个的activity,而不是整个的task。而且它可以使任意activity都被清理,甚至根activity也不例外。当它设置为“true”的时候,此activity仅做为任务的一部分存在于当前回话中,一旦用户离开并再次回到这个任务,此activity将不复存在。

此外,还有别的方式从堆栈中移除一个activity。如果一个intent对象包含FLAG_ACTIVITY_CLEAR_TOP标记,而且目标任务的堆栈中已经存在了一个能够响应此intent的activity类型的实例。则这个实例之上的所有activity都将被清理以使它位于堆栈的顶部来对intent做出响应。如果此时指定的activity的加载模式为“standard”,则它本身也会从堆栈中移除,并加载一个新的实例来处理到来的intent。这是因为加载模式为“standard”的activity总会创建一个新实例来处理新的intent。

FLAG_ACTIVITY_CLEAR_TOPFLAG_ACTIVITY_NEW_TASK经常合并使用。这时,这些标记提供了一种定位其它任务中现存的activity并将它们置于可以对intent做出响应的位置的方法。

启动任务

当一个activity被指定一个“android.intent.action.MAIN”做为动作,以及“android.intent.category.LAUNCHER”做为类别的intent过滤器之后(在前述intent过滤器一节中已经有了这个示例),它就被设置为一个任务的入口点。这样的过滤器设置会在应用程序加载器中为此activity显示一个图标和标签,以供用户加载任务或加载之后在任意时间回到这个任务。

第二个能力相当重要:用户必须可以离开一个任务,并在一段时间后返回它。出于这个考虑,加载模式被设定为“singleTask”和“singleInstance”的activity总是会初始化一个新任务,这样的activity仅能用于指定了一个MAINLAUNCHER过滤器的情况之下。我们来举例说明如果没指定过滤器的情况下会发生的事情:一个intent加载了一个“singleTask”的activity,初始化了一个新任务,用户在这个任务中花费了一些时间来完成工作。然后用户按下了HOME键。于是任务被要求转至后台并被主屏幕所掩盖。因为它并没有在应用程序加载器中显示图标,这将导致用户无法再返回它。

类似的困境也可由FLAG_ACTIVITY_NEW_TASK标记引起。如果此标记使一个activity启动了一个新任务继而用户按下了HOME键离开了它,则用户必须要有一些方法再次回到这个任务。一些实体(诸如通知管理器)总是在另外的任务中启动新activity,而不是做为它们自己的一部分,所以它们总是将FLAG_ACTIVITY_NEW_TASK标记包含在intent里面并传递给startActivity()。如果你写了一个能被外部实体使用这个标记调用的activity,你必须注意要给用户留一个返回这个被外部实体启动的任务的方法。

当你不想让用户再次返回一个activity的情况下,可以将 <activity> 元素的 finishOnTaskLaunch设置为“true”。参见前述清理堆栈。.

进程和线程

当一个应用程序开始运行它的第一个组件时,Android会为它启动一个Linux进程,并在其中执行一个单一的线程。默认情况下,应用程序所有的组件均在这个进程的这个线程中运行。

然而,你也可以安排组件在其他进程中运行,而且可以为任意进程衍生出其它线程。

进程

组件运行所在的进程由manifest文件所控制。组件元素——<activity>, <service>, <receiver><provider>——都有一个 process 属性来指定组件应当运行于哪个进程之内。这些属性可以设置为使每个组件运行于它自己的进程之内,或一些组件共享一个进程而其余的组件不这么做。它们也可以设置为令不同应用程序的组件在一个进程中运行——使应用程序的组成部分共享同一个Linux用户ID并赋以同样的权限。<application>元素也有一个process属性,以设定所有组件的默认值。

所有的组件实例都位于特定进程的主线程内,而对这些组件的系统调用也将由那个线程进行分发。一般不会为每个实例创建线程。因此,某些方法总是运行在进程的主线程内,这些方法包括诸如View.onKeyDown()这样报告用户动作以及后面 组件生命周期一节所要讨论的生命周期通告的。这意味着组件在被系统调用的时候,不应该施行长时间的抑或阻塞的操作(诸如网络相关操作或是循环计算),因为这将阻塞同样位于这个进程的其它组件的运行。你应该如同下面线程一节所叙述的那样,为这些长时间操作衍生出一个单独的线程进行处理。

在可用内存不足而又有一个正在为用户进行服务的进程需要更多内存的时候,Android有时候可能会关闭一个进程。而在这个进程中运行着的应用程序也因此被销毁。当再次出现需要它们进行处理的工作的时候,会为这些组件重新创建进程。

在决定结束哪个进程的时候,Android会衡量它们对于用户的相对重要性。比如说,相对于一个仍有用户可见的activity的进程,它更有可能去关闭一个其activity已经不为用户所见的进程。也可以说,决定是否关闭一个进程主要依据在那个进程中运行的组件的状态。这些状态将在后续的一节组件生命周期中予以说明。

线程

尽管你可以把你的应用程序限制于一个单独的进程中,有时,你仍然需要衍生出一个线程以处理后台任务。因为用户界面必须非常及时的对用户操作做出响应,所以,控管activity的线程不应用于处理一些诸如网络下载之类的耗时操作。所有不能在瞬间完成的任务都应安排到不同的线程中去。

线程在代码中是以标准Java Thread对象创建的。Android提供了很多便于管理线程的类: Looper用于在一个线程中运行一个消息循环, Handler用于处理消息,HandlerThread 用于使用一个消息循环启用一个线程。

远程过程调用

Android有一个轻量级的远程过程调用(RPC)机制:即在本地调用一个方法,但在远程(其它的进程中)进行处理,然后将结果返回调用者。这将方法调用及其附属的数据以系统可以理解的方式进行分离,并将其从本地进程和本地地址空间传送至远程过程和远程地址空间,并在那里重新装配并对调用做出反应。返回的结果将以相反的方向进行传递。Android提供了完成这些工作所需的所有的代码,以使你可以集中精力来实现RPC接口本身。

RPC接口可以只包括方法。即便没有返回值,所有方法仍以同步的方式执行(本地方法阻塞直至远程方法结束)。

简单的说,这套机制是这样工作的:一开始,你用简单的IDL(界面描绘语言)声明一个你想要实现的RPC接口。然后用 aidl 工具为这个声明生成一个Java接口定义,这个定义必须对本地和远程进程都可见。它包含两个内部类,如下图所示:

RPC mechanism.

内部类中有管理实现了你用IDL声明的接口的远程方法调用所需要的所有代码。两个内部类均实现了 IBinder接口。一个用于系统在本地内部使用,你些的代码可以忽略它;另外一个,我们称为Stub,扩展了Binder类。除了实现了IPC调用的内部代码之外,它还包括了你声明的RPC接口中的方法的声明。你应该如上图所示的那样写一个Stub的子类来实现这些方法。

一般情况下,远程过程是被一个服务所管理的(因为服务可以通知系统关于进程以及它连接到别的进程的信息)。它包含着 aidl工具产生的接口文件和实现了RPC方法的Stub的子类。而客户端只需要包括aidl工具产生的接口文件。

下面将说明服务与其客户端之间的连接是如何建立的:

  • 服务的客户端(位于本地)应该实现 onServiceConnected() 和 onServiceDisconnected() 方法。这样,当至远程服务的连接成功建立或者断开的时候,它们会收到通知。这样它们就可以调用 bindService() 来设置连接。
  • 而服务则应该实现 onBind() 方法以接受或拒绝连接。这取决于它收到的intent(intent将传递给bindService())。如果接受了连接,它会返回一个Stub的子类的实例。
  • 如果服务接受了连接,Android将会调用客户端的onServiceConnected() 方法,并传递给它一个IBinder对象,它是由服务所管理的Stub的子类的代理。通过这个代理,客户端可以对远程服务进行调用。

线程安全方法

在一些情况下,你所实现的方法有可能会被多于一个的线程所调用,所以它们必须被写成线程安全的。

对于我们上一节所讨论的RPC机制中的可以被远程调用的方法来说,这是必须首先考虑的。如果针对一个IBinder对象中实现的方法的调用源自这个IBinder对象所在的进程时,这个方法将会在调用者的线程中执行。然而,如果这个调用源自其它的进程,则这个方法将会在一个线程池中选出的线程中运行,这个线程池由Android加以管理,并与IBinder存在于同一进程内;这个方法不会在进程的主线程内执行。反过来说,一个服务的 onBind() 方法应为服务进程的主线程所调用,而实现了由 onBind() 返回的对象(比如说,一个实现了RPC方法的Stub的子类)的方法将为池中的线程所调用。因为服务可以拥有多于一个的客户端,而同一时间,也会有多个池中的线程调用同一个IBinder方法。因此IBinder方法必须实现为线程安全的。

类似的,一个内容提供者能接受源自其它进程的请求数据。尽管ContentResolver和ContentProvider类隐藏了交互沟通过程的管理细节,ContentProvider会由query(), insert(), delete(), update()getType()方法来相应这些请求,而这些方法也都是由那个内容提供者的进程中所包涵的线程池提供的,而不是进程的主线程本身。所以这些有可能在同一时间被很多线程调用的方法也必须被实现为线程安全的。

组件生命周期

应用程序组件有其生命周期──由Android初始化它们以相应intent直到这个实例被摧毁。在此之间,它们有时是激活的有时则相反。或者,如果它是一个activity,则是可为用户所见或者不能。这一节讨论了activity、服务以及广播接收器的生命周期,包括它们在生命周期中的状态、在状态之间转变时通知你的方法、以及当这些进程被关闭或实例被摧毁时,这些状态产生的效果。

Activity生命周期

一个activity主要有三个状态:

  • 当在屏幕前台时(位于当前任务堆栈的顶部),它是活跃运行的状态。它就是相应用户操作的activity。
  • 当它失去焦点但仍然对用户可见时,它处于暂停状态。即是:在它之上有另外一个activity。这个activity也许是透明的,或者未能完全遮蔽全屏,所以被暂停的activity仍对用户可见。暂停的activity仍然是存活状态(它保留着所有的状态和成员信息并连接至窗口管理器),但当系统处于极低内存的情况下,仍然可以杀死这个activity。

  • 如果它完全被另一个activity覆盖是,它处于停止状态。它仍然保留所有的状态和成员信息。然而它不在为用户可见,所以它的窗口将被隐藏,如果其它地方需要内存,则系统经常会杀死这个activity。

如果一个activity处于暂停或停止状态,系统可以通过要求它结束(调用它的 finish() 方法)或直接杀死它的进程来将它驱出内存。当它再次为用户可见的时候,它只能完全重新启动并恢复至以前的状态。

当一个activity从这个状态转变到另一个状态时,它被以下列protected方法所通知:

void onCreate(Bundle savedInstanceState) 
void onStart() 
void onRestart() 
void onResume() 
void onPause() 
void onStop() 
void onDestroy()

你可以重载所有这些方法以在状态改变时进行合适的工作。所有的activity都必须实现 onCreate() 用以当对象第一次实例化时进行初始化设置。很多activity会实现 onPause()以提交数据变化或准备停止与用户的交互。

调用父类

所有activity生命周期方法的实现都必须先调用其父类的版本。比如说:

总得来说,这七个方法定义了一个activity完整的生命周期。实现这些方法可以帮助你监察三个嵌套的生命周期循环:

  • 一个activity 完整的生命周期 自第一次调用 onCreate()开始,直至调用onDestroy()为止。activity在onCreate()中设置所有“全局”状态以完成初始化,而在onDestroy()中释放所有系统资源。比如说,如果activity有一个线程在后台运行以从网络上下载数据,它会以 onCreate()创建那个线程,而以 onDestroy()销毁那个线程。
  • 一个activity的 可视生命周期自 onStart() 调用开始直到相应的 onStop()调用。在此期间,用户可以在屏幕上看到此activity,尽管它也许并不是位于前台或者正在与用户做交互。在这两个方法中,你可以管控用来向用户显示这个activity的资源。比如说,你可以在onStart() 中注册一个BroadcastReceiver 来监控会影响到你UI的改变,而在onStop() 中来取消注册,这时用户是无法看到你的程序显示的内容的。onStart() 和 onStop() 方法可以随着应用程序是否为用户可见而被多次调用。

  • 一个activity的 前台生命周期 自 onResume() 调用起,至相应的 onPause()调用为止。在此期间,activity位于前台最上面并与用户进行交互。activity会经常在暂停和恢复之间进行状态转换──比如说当设备转入休眠状态或有新的activity启动时,将调用onPause() 方法。当activity获得结果或者接收到新的intent的时候会调用onResume() 方法。因此,在这两个方法中的代码应当是轻量级的。

下图展示了上述循环过程以及activity在这个过程之中历经的状态改变。着色的椭圆是activity可以经历的主要状态。矩形框代表了当activity在状态间发生改变的时候,你进行操作所要实现的回调方法。

State diagram for an Android activity lifecycle.

下表详细描述了这些方法,并在activity的整个生命周期中定位了它们。

方法描述 可被杀死下一个
onCreate() 在activity第一次被创建的时候调用。这里是你做所有初始化设置的地方──创建视图、绑定数据至列表等。如果曾经有状态记录(参阅后述Saving Activity State。),则调用此方法时会传入一个包含着此activity以前状态的包对象做为参数。

总继之以onStart()

onStart()
     onRestart() 在activity停止后,在再次启动之前被调用。

总继之以onStart()

onStart()
onStart() 当activity正要变得为用户所见时被调用。

当activity转向前台时继以onResume(),在activity变为隐藏时继以onStop()

onResume() 
or
onStop()
    onResume() 在activity开始与用户进行交互之前被调用。此时activity位于堆栈顶部,并接受用户输入。

继之以onPause()

onPause()
onPause() 当系统将要启动另一个activity时调用。此方法主要用来将未保存的变化进行持久化,停止类似动画这样耗费CPU的动作等。这一切动作应该在短时间内完成,因为下一个activity必须等到此方法返回后才会继续。

当activity重新回到前台是继以onResume()。当activity变为用户不可见时继以onStop()

onResume() 
or
onStop()
onStop() 当activity不再为用户可见时调用此方法。这可能发生在它被销毁或者另一个activity(可能是现存的或者是新的)回到运行状态并覆盖了它。

如果activity再次回到前台跟用户交互则继以onRestart(),如果关闭activity则继以onDestroy()

onRestart() 
or
onDestroy()
onDestroy() 在activity销毁前调用。这是activity接收的最后一个调用。这可能发生在activity结束(调用了它的 finish() 方法)或者因为系统需要空间所以临时的销毁了此acitivity的实例时。你可以用isFinishing() 方法来区分这两种情况。 nothing

请注意上表中可被杀死一列。它标示了在方法返回后,还没执行activity的其余代码的任意时间里,系统是否可以杀死包含此activity的进程。三个方法(onPause()、 onStop()onDestroy())被标记为“是”。onPause()是三个中的第一个,它也是唯一一个在进程被杀死之前必然会调用的方法──onStop() 和 onDestroy() 有可能不被执行。因此你应该用 onPause() 来将所有持久性数据(比如用户的编辑结果)写入存储之中。

可被杀死一列中标记为“否”的方法在它们被调用时将保护activity所在的进程不会被杀死。所以只有在onPause()方法返回后到onResume() 方法被调用时,一个activity才处于可被杀死的状态。在onPause()再次被调用并返回之前,它不会被系统杀死。

如后面一节进程和生命周期所述,即使是在这里技术上没有被定义为“可杀死”的activity仍然有可能被系统杀死──但这仅会发生在实在没有其它方法的极端情况之下。

保存activity状态

当系统而不是用户自己出于回收内存的考虑,关闭了一个activity之后。用户会期望当他再次回到那个activity的时候,它仍保持着上次离开时的样子。

为了获取activity被杀死前的状态,你应该为activity实现onSaveInstanceState() 方法。Android在activity有可能被销毁之前(即onPause() 调用之前)会调用此方法。它会将一个以名称-值对方式记录了activity动态状态的Bundle 对象传递给该方法。当activity再次启动时,这个Bundle会传递给onCreate()方法和随着onStart()方法调用的onRestoreInstanceState(),所以它们两个都可以恢复捕获的状态。

onPause()或先前讨论的其它方法不同,onSaveInstanceState() 和 onRestoreInstanceState() 并不是生命周期方法。它们并不是总会被调用。比如说,Android会在activity易于被系统销毁之前调用 onSaveInstanceState(),但用户动作(比如按下了BACK键)造成的销毁则不调用。在这种情况下,用户没打算再次回到这个activity,所以没有保存状态的必要。

因为onSaveInstanceState()不是总被调用,所以你应该只用它来为activity保存一些临时的状态,而不能用来保存持久性数据。而是应该用onPause()来达到这个目的。

协调activity

当一个activity启动了另外一个的时候,它们都会经历生命周期变化。一个会暂停乃至停止,而另一个则启动。这种情况下,你可能需要协调好这些activity:

生命周期回调顺序是已经定义好的,尤其是在两个activity在同一个进程内的情况下:

  1. 调用当前activity的 onPause() 方法。
  2. 接着,顺序调用新启动activity的onCreate()、 onStart()onResume()方法。
  3. 然后,如果启动的activity不再于屏幕上可见,则调用它的onStop()方法。

服务生命周期

服务以两种方式使用:

  • 它可以启动并运行,直至有人停止了它或它自己停止。在这种方式下,它以调用Context.startService()启动,而以调用Context.stopService()结束。它可以调用Service.stopSelf() 或 Service.stopSelfResult()来自己停止。不论调用了多少次startService()方法,你只需要调用一次stopService()来停止服务。
  • 它可以通过自己定义并暴露出来的接口进行程序操作。客户端建立一个到服务对象的连接,并通过那个连接来调用服务。连接以调用Context.bindService()方法建立,以调用 Context.unbindService()关闭。多个客户端可以绑定至同一个服务。如果服务此时还没有加载,bindService()会先加载它。

这两种模式并不是完全分离的。你可以绑定至一个用 startService()启动的服务。比如说,一个后台音乐播放服务可以调用startService()并传递给它一个包含欲播放的音乐列表的Intent对象来启动。不久,当用户想要对播放器进行控制或者查看当前播放曲目的详情时,会启用一个activity,调用bindService()连接到服务来完成操作。在这种情况下,直到绑定连接关闭stopService() 才会真正停止一个服务。

与activity一样,服务也有一系列你可以实现以用于监控其状态变化的生命周期方法。但相对于activity要少一些,只有三个,而且,它们是public属性,并非protected:

void onCreate() 
void onStart(Intent intent) 
void onDestroy()

倚仗实现这些方法,你监控服务的两个嵌套的生命周期循环:

  • 服务的完整生命周期始于调用onCreate()而终于onDestroy()方法返回。如同activity一样,服务在onCreate()里面进行它自己的初始化,而在onDestroy()里面释放所有资源。比如说,一个音乐回放服务可以在onCreate()中创建播放音乐的线程, 而在onDestroy()中停止这个线程。
  • 服务的活跃生命周期始于调用onStart()。这个方法用于处理传递给startService()的Intent对象。音乐服务会打开Intent来探明将要播放哪首音乐,并开始播放。

    服务停止时没有相应的回调方法──不存在onStop()方法。

onCreate()onDestroy()方法在所有服务中都会被调用,不论它们是由Context.startService()还是由Context.bindService()所启动的。而onStart()仅会被startService()所启用的服务调用。

如果一个服务允许别的进程绑定,则它还会有以下额外的回调方法以供实现:

IBinder onBind(Intent intent) 
boolean onUnbind(Intent intent) 
void onRebind(Intent intent)

传递给bindService的Intent的对象也会传递给onBind()回调方法,而传递给unbindService()的Intent对象同样传递给onUnbind()。如果服务允许绑定,onBind()将返回一个供客户端与服务进行交互的通讯渠道。如果有新的客户端连接至服务,则onUnbind()方法可以要求调用onRebind() 。

下图描绘了服务的回调方法。尽管图中对由startService 和startService方法启动的服务做了区分,但要记住,不论一个服务是怎么启动的,它都可能允许客户端的连接,所以任何服务都可以接受onBind()onUnbind()调用。

State diagram for Service callbacks.

广播接收器生命周期

广播接收器只有一个回调方法:

void onReceive(Context curContext, Intent broadcastMsg)

当广播消息抵达接收器时,Android调用它的onReceive() 方法并将包含消息的Intent对象传递给它。广播接收器仅在它执行这个方法时处于活跃状态。当onReceive()返回后,它即为失活状态。

拥有一个活跃状态的广播接收器的进程被保护起来而不会被杀死。但仅拥有失活状态组件的进程则会在其它进程需要它所占有的内存的时候随时被杀掉。

这种方式引出了一个问题:如果响应一个广播信息需要很长的一段时间,我们一般会将其纳入一个衍生的线程中去完成,而不是在主线程内完成它,从而保证用户交互过程的流畅。如果onReceive()衍生了一个线程并且返回,则包涵新线程在内的整个进程都被会判为失活状态(除非进程内的其它应用程序组件仍处于活跃状态),于是它就有可能被杀掉。这个问题的解决方法是令onReceive()启动一个新服务,并用其完成任务,于是系统就会知道进程中仍然在处理着工作。

下一节中,我们会讨论更多进程易误杀的问题。

进程与生命周期

Android系统会尽可能长的延续一个应用程序进程,但在内存过低的时候,仍然会不可避免需要移除旧的进程。为决定保留或移除一个进程,Android将每个进程都放入一个“重要性层次”中,依据则是它其中运行着的组件及其状态。重要性最低的进程首先被消灭,然后是较低的,依此类推。重要性共分五层,依据重要性列表如下:

  1. 前台进程是用户操作所必须的。当满足如下任一条件时,进程被认为是处于前台的:
    • 它运行着正在与用户交互的activity(Activity对象的 onResume() 方法已被调用)。
    • 一个正在与用户交互的activity使用着它提供的一个服务。

    • 它包含着一个正在执行生命周期回调方法(onCreate()onStart()onDestroy())的Service对象。

    • 它包含着一个正在执行 onReceive() 方法的BroadcastReceiver对象。

    任一时间下,仅有少数进程会处于前台,仅当内存实在无法供给它们维持同时运行时才会被杀死。一般来说,在这种情况下,设备已然处于使用虚拟内存的状态,必须要杀死一些前台进程以用户界面保持响应。

  2. 可视进程没有前台组件,但仍可被用户在屏幕上所见。当满足如下任一条件时,进程被认为是可视的:、

    • 它包含着一个不在前台,但仍然为用户可见的activity(它的onPause()方法被调用)。这种情况可能出现在以下情况:比如说,前台activity是一个对话框,而之前的activity位于其下并可以看到。
    • 它包含了一个绑定至一个可视的activity的服务。

    可视进程依然被视为是很重要的,非到不杀死它们便无法维持前台进程运行时,才会被杀死。

  3. 服务进程是由 startService() 方法启动的服务,它不会变成上述两类。尽管服务进程不会直接为用户所见,但它们一般都在做着用户所关心的事情(比如在后台播放mp3或者从网上下载东西)。所以系统会尽量维持它们的运行,除非系统内存不足以维持前台进程和可视进程的运行需要。

  4. 背景进程包含目前不为用户所见的activity(Activity对象的 onStop() 方法已被调用)。这些进程与用户体验没有直接的联系,可以在任意时间被杀死以回收内存供前台进程、可视进程以及服务进程使用。一般来说,会有很多背景进程运行,所以它们一般存放于一个LRU(最后使用)列表中以确保最后被用户使用的activity最后被杀死。如果一个activity正确的实现了生命周期方法,并捕获了正确的状态,则杀死它的进程对用户体验不会有任何不良影响。

  5. 空进程不包含任何活动应用程序组件。这种进程存在的唯一原因是做为缓存以改善组件再次于其中运行时的启动时间。系统经常会杀死这种进程以保持进程缓存和系统内核缓存之间的平衡。

Android会依据进程中当前活跃组件的重要程度来尽可能高的估量一个进程的级别。比如说,如果一个进程中同时有一个服务和一个可视的activity,则进程会被判定为可视进程,而不是服务进程。

此外,一个进程的级别可能会由于其它进程依赖于它而升高。一个为其它进程提供服务的进程级别永远高于使用它服务的进程。比如说,如果A进程中的内容提供者为进程B中的客户端提供服务,或进程A中的服务为进程B中的组件所绑定,则A进程最低也会被视为与进程B拥有同样的重要性。

因为运行着一个服务的进程重要级别总高于一个背景activity。所以一个activity以启动一个服务的方式启动一个长时间运行过程比简单的衍生一个线程来进行处理要好。尤其是当处理过程比activity本身存在时间要长的情况之下。我们以背景音乐播放和上传一个相机拍摄的图片至网站上为例。使用服务则不论activity发生何事,都至少可以保证操作拥有“服务进程”的权限。如上一节广播接收器生命周期 所提到的,这也正是广播接收器使用服务,而不是使用线程来处理耗时任务的原因。

转自译言:http://www.yeeyan.com/articles/view/37503/34036

发表于 @ 2009年08月17日 22:03:00 | 评论( 1 )