Android开发教程


继续 android.app 中的几个类的学习,今天的内容是那几个 Dialog 的体验。 注意到 android.app 包下除了 Dialog(可用于制作复杂的对话框)以外,还包括了几个系 统定义好的对话框类,如 DatePickerDialog、TimePickerDialog 及 AlertDialog。 其中 AlertDialog 我上回用过一次,基本上就那样子了,今天看看另外两个对话框的使用吧。 首先是 DatePickerDialog 类,修改代码如下: public class HelloTwoC extends Activity implements OnClickListener, OnDateSetListener { public HelloTwoC() { super(); } public void onCreate(Bundle icicle) { super.onCreate(icicle); setTheme(android.R.style.Theme_Dark); setContentView(R.layout.mainc); Button btn = (Button)findViewById(R.id.date); btn.setOnClickListener(this); } @Override public void onClick(View v) { Calendar d = Calendar.getInstance(Locale.CHINA); d.setTime(new Date()); DatePickerDialog DatePickerDialog(this,this,d.get(Calendar.YEAR),d.get(Calendar.MONTH),d.get(Calendar.DAY_OF_MONTH),d.get(Calendar.DAY_OF_W dlg.show(); } @Override public void dateSet(DatePicker dp, int y, int m, int d) { TextView txt = (TextView)findViewById(R.id.text); txt.setText(Integer.toString(y)+"-"+Integer.toString(m)+"-"+Integer.toString(d)); } } 很简单的,无非是需要一个 OnDateSetListener 接口的实现而已,在它里面的 dateSet 方法中就可以得到选择的日期了。而 TimePickerDialog 与 DatePickerDialog 使用如出 一辙,就不多说了。 看看另一个 ProgressDialog 的用法吧,这个类与 AlertDialog 一样包含了多个 static 的 方法,所以使用起来是非常方便的。比如说,如果我们需要用它来表示一个长时间的操作, 很简单的用一句话就可以了: ProgressDialog.show(this,null, "operation running...",true,true); 今天先到这里,下回再看看 Service 和 Notification 的使用。 Google 的 Android SDK 发布也有一段时间了,一直想研究一下却苦于找不到时间。利用 这个周未,开始强迫自己再次进入学习状态,原因很简单:我看好开放的 gPhone。 SDK 的下载与安装并不复杂,网上也有不少同学已经进入状态了,我就不再重复了吧。 今天主要讨论的,还是永远不变的话题:Hello World. 1.最简单的 HelloWorld 安装了 SDK 后,直接生成一个 Android Project,一句代码不用写,就能跑出一个最简单 的 HelloWorld 例程。 我们看一下它的代码: public void onCreate(Bundle icicle) { super.onCreate(icicle); setTheme(android.R.style.Theme_Dark); setContentView(R.layout.main); }根据文档的解释,Activity 是 Android 开发中非常重要的一个基础类。我把它想像成 J2ME 中的 Display 类,或者是 Win32 平台上的 Form 类,也许不准确,但是它的重要性 我觉得应该是一样的(当然,如果我们写的是一个没有界面的应用,例如后台运行的服务之 类的,可以不用 Display 的)。 1.在一个 Activity 中使用多个 View 如果把 Activity 看作 MVC 中的 Control?它负责管理 UI 和接受事件(包括用户的输入), 虽然说一个 Activity 通常对应一个屏幕,但事实上,我们是可以只用一个 Activity 管理多 个不同的 View 来实现简单的逻辑。 首先,我们增加一个新的资源描述 layout/second.xml。 除了一个“Hello 中国”以外,增加一个按钮可以返回前一个界面。然后,在代码中我们要 为 helloTwo 增加两个方法,setViewOneCommand 和 setViewTwoCommand,分别 处理一下在不同界面时,从资源里加载组件并为组件绑定一个事件处理器。 public void setViewOneCommand() { Button btn = (Button)findViewById(R.id.go); btn.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { helloTwo.this.setContentView(R.layout.second); helloTwo.this.setViewTwoCommand(); } }); Button btnExit=(Button)findViewById(R.id.exit); btnExit.setOnClickListener(new View.OnClickListener(){ public void onClick(View v){ helloTwo.this.finish(); } }); } public void setViewTwoCommand() { Button btnBack=(Button)findViewById(R.id.go2); btnBack.setOnClickListener(new View.OnClickListener(){ public void onClick(View v){ helloTwo.this.setContentView(R.layout.main); helloTwo.this.setViewOneCommand(); } }); } 最后,我们需要在 onCreate 的时候,也就是启动后的 main 界面上设置一下按钮事件处理 器。新的 onCreate 方法如下: public void onCreate(Bundle icicle) { super.onCreate(icicle); setTheme(android.R.style.Theme_Dark); setContentView(R.layout.main); setViewOneCommand(); } 编译,运行,OK。 2.还是回到正道上,多个 Activity 之间的跳转 Android 中提供一个叫 Intent 的类来实现屏幕之间的跳转,按文档的说法,似乎他们也建 议采用这种方法,Intent 的用法比较复杂,现在我先看看它最简单的用法。 先在应用中增加两个 Activity,这需要修改 AndroidManifest.xml 文件了,如下: 很简单,就是加一个标签而已,新标签的 class 是.HelloThreeB,显示的应用 标题与前一个 Activity 一样而已,然后第二步就是修改一个 HelloThree 类的实现,在 onCreate 方法中绑定按钮的事件处理器: public void onCreate(Bundle icicle) { super.onCreate(icicle); setTheme(android.R.style.Theme_Dark); setContentView(R.layout.main); setViewOneCommand(); } public void setViewOneCommand() { Button btn = (Button)findViewById(R.id.go); btn.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { Intent intent = new Intent(); intent.setClass(HelloThree.this, HelloThreeB.class); startActivity(intent); finish(); } }); Button btnExit=(Button)findViewById(R.id.exit); btnExit.setOnClickListener(new View.OnClickListener(){ public void onClick(View v){ HelloThree.this.finish(); } }); } 这里的跳转功能用 Intent 来操作,它的最简单用法就是用函数 setClass()设置跳转前后两 个 Activity 类的实例,然后调用 Activity 自己的 startActivity(intent)即可。最后一句 finish()表示将当前 Activity 关掉(如果不关掉会如何?你可以自己试一下看效果,事实上 有时我们是不需要关掉当前 Activity 的)。 然后,我们同样弄一个 Activity 类 HelloThreeB,代码与前面的差不多,只是将 setClass 的两个参数反一下,这样就可以简单地实现在两个 Activity 界面中来回切换的功能了。 3.如果我想在两个 Activity 之间进行数据交换,怎么办? 前例中的 startActivity()只有一个参数,如果需要向新打开的 Activity 传递参数,我们得 换一个函数了, Android 提供了 startSubActivity(Intent,int)这个函数来实现这个功能。 函数原型为: public void startSubActivity(Intent intent, int requestCode) 这里的 requestCode 用来标识某一个调用,一般由我们定义一个常量。 如何把参数传过去呢?Intent 类在提供 setClass()函数的同时也提供了一个 setData()函 数。 函数原型为:public Intent setData(ContentURI data) 参数类型是 ContentURI,它的详细内容下回再分析,现在就把它当成一个 String 类型来 用吧。 参数带到新的 Activity 后,同样用 Activity.getIntent()函数可以得到当前过来的 Intent 对象,然后用 getData()就取到参数了。 把参数带回来的方法是 Activity.setResult(),它有几个形式,现在先看最简单的一个吧。 函数原型是:public final void setResult(int resultCode, String data) resultCode 是返回代码,同样用来标识一个返回类型,而 data 则是它要返回的参数。 在原来的 Activity 中的事件处理回调函数 onActivityResult,会被系统调用,从它的参数 里可以得到返回值。 函数原型为:protected void onActivityResult(int requestCode, int resultCode,String data, Bundle extras) 这里的 requestCode 就是前面启动新 Activity 时的带过去的 requestCode,而 resultCode 则关联上了 setResult 中的 resultCode,data 是参数,extras 也是一个很 重要的东西,后面再研究一下它的作用。 下面,我们来看一下代码吧,先看看 HelloThree 中的代码: public void setViewOneCommand() { Button btn = (Button)findViewById(R.id.go); btn.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { try { Intent intent = new Intent(); intent.setClass(HelloThree.this, HelloThreeB.class); intent.setData(new ContentURI("One")); startSubActivity(intent,REQUEST_TYPE_A); } catch(Exception ex){} } }); Button btnExit=(Button)findViewById(R.id.exit); btnExit.setOnClickListener(new View.OnClickListener(){ public void onClick(View v){ HelloThree.this.finish(); } }); } protected void onActivityResult(int requestCode, int resultCode, String data, Bundle extras) { if (requestCode == REQUEST_TYPE_A) { if (resultCode == RESULT_OK) { Log.v(TAG,data); TextView txt = (TextView)findViewById(R.id.txt); txt.setText(data); } } } 这里的 REQUEST_TYPE_A 是我们定义的一个常量。在 onActivityResult 中用它与 RESULT_OK 一起作为条件判断如何处理返回值,这里只是简单将 TextView 显示值换成 传来的字串。 再来看看另一个 HelloThreeB 类的实现代码: private Intent i; protected void onCreate(Bundle icicle) { super.onCreate(icicle); setContentView(R.layout.second); i = getIntent(); android.util.Log.v(TAG,"onCreate"); Button btn = (Button)findViewById(R.id.go); btn.setOnClickListener(new View.OnClickListener(){ public void onClick(View v){ String result=HelloThreeB.this.i.getData().toString()+" And Two"; HelloThreeB.this.setResult(RESULT_OK,result); finish(); } }); TextView v = (TextView)findViewById(R.id.txt); v.setText("Param is "+i.getData().toString()); } 在按钮处理事件中,从 Intent 取出参数,处理一下再用 setResult 返回给前一个 Activity 即可。 编译运行即可。 今天先到这里,下一回我再研究一下 Activity 的生命周期的问题。 看上去实在很简单,只有两句话而已。关键在这个 R.layout.main 上,凭直觉,这应该是 定义的资源。的确,在 R.java 中只是定义了一个 static int 而已,真正的资源描述在 res/layout/main.xml 文件里(注意:这里的 R.java 不要手工编辑,每次 build project 时它都会根据 res 下的资源描述被自动修改)。 这个文件很好读,一个 描述了这是一个线性排列的布局, android:orientation=vertical 表示所有组件将纵向排布。而经典的 Hello World 是用一 个 TextView 来展示的。 由此,我们知道,Android 的程序从一个 Activity 派生出来,并且从它的 onCreate 开始 启动;Android 里要显示的组件用 XML 文件描述而不用在代码中硬编码(这是一个好的习 惯,我们应该从一开始就坚持下去); 2.让 Button 来说 Hello World 上面的例子是 ADT 自动生成的代码,似乎与我们一点关系也没有。那我们来改一下代码, 因为在 windows 平台上的 Helloworld 经常是由一个按钮触发的,所以,我们想第二个 Helloworld 应该是这样的:加一个按钮和文本输入框,单击按钮后在原来的 TextView 后 面加上输入框中输入的文字。 第一步是,增加一个 Button 和一个 EditText,与 TextView 一样,它们也在 main.xml 里描述一下: 这里有两个地方要注意:id=@+id/go,这表示需要一个唯一的 UID 来作为 Button 的 ID, 它的引用名是 go。还有一个是 android:text=@string/go 表示这个按钮的文本不是直接 写有 main.xml 里了,而是来源于另一个资源描述文件 strings.xml 里,本例中的 strings.xml 如下: helloTwo 提示 你好,中国 确定 浏览 然后,在代码里(onCreate 函数中)我们加上以下代码(简单起见,用了嵌套类): Button btn = (Button)findViewById(R.id.go); btn.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { EditText edt=(EditText)helloTwo.this.findViewById(R.id.edt); TextView txt= (TextView)helloTwo.this.findViewById(R.id.txt); txt.setText(getString(R.string.msg_dialog)+edt.getText()); } }); 为铵钮增加一个 onClick 事件处理器,在点击事件中,设置 txt 的文本为 R.string.msg_dialgo+edt.getText()。 这里的关键是两个函数的使用: findViewById(R.id.go)可以根据资源的名称加载 View 类型的资源,同样用函数 getString(R.string.msg_dialog)可以加载字符串资源。 编译,run 一下看看效果。 3. 再让菜单 Say Hello 从 API 文档中我们看到 Activity 中有两个函数:onCreateOptionsMenu 和 onOptionsItemSelected,显示,这个 OptionsMenu 就是所谓的上下文菜单(在 GPhone 的模拟器上,有个键专用于弹出这个菜单)。下面我们就为这个 HelloWorld 例子加上一个 菜单,并且让它可以 Say hello。 这次,我们不涉及到资源的描述文件了,而是直接使用这两个函数来实现,其实代码也很简 单,所以,我们再增加一个退出应用的功能(否则每次都是按取消键退出应用显示太不专业 了)。 代码如下: public boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMenu(menu); menu.add(0,1,"say hello"); menu.add(0,2,"exit"); return true; } public boolean onOptionsItemSelected(Item item) { super.onOptionsItemSelected(item); int id = item.getId(); switch(id){ case 1: AlertDialog.show(this,getString(R.string.app_name), getString(R.string.msg_dialog), getString(R.string.ok_dialog), true); break; case 2: finish(); break; } 在 CreateOptionsMenu 时,我们简单地增加两个菜单项,menu.add(组 ID,项 ID,显示 文本) ,(注意:这里我直接将文字写在代码里,这并不提倡)。然后,在 OptionsItemSelected 事件中,我们根据选中的菜单项做相应处理,如果选中 1,则弹出 一个对话框显示资源文件中的“你好,中国”,如果选中 2 则退出应用。 AlertDialog.show 是一个静态方法,类似于我们在 WIN 平台上经常使用的 MessageBox 一样,很方便的。 好了,今天是第一天,先学到这里吧,下回我觉得我有必要仔细研究一个 Activity 的 API 了。 根据文档的解释,Activity 是 Android 开发中非常重要的一个基础类。我把它想像成 J2ME 中的 Display 类,或者是 Win32 平台上的 Form 类,也许不准确,但是它的重要性我觉得 应该是一样的(当然,如果我们写的是一个没有界面的应用,例如后台运行的服务之类的, 可以不用 Display 的)。 1.在一个 Activity 中使用多个 View 如果把 Activity 看作 MVC 中的 Control?它负责管理 UI 和接受事件(包括用户的输入), 虽然说一个 Activity 通常对应一个屏幕,但事实上,我们是可以只用一个 Activity 管理多 个不同的 View 来实现简单的逻辑。 首先,我们增加一个新的资源描述 layout/second.xml。 除了一个“Hello 中国”以外,增加一个按钮可以返回前一个界面。然后,在代码中我们要 为 helloTwo 增加两个方法,setViewOneCommand 和 setViewTwoCommand,分别 处理一下在不同界面时,从资源里加载组件并为组件绑定一个事件处理器。 public void setViewOneCommand() { Button btn = (Button)findViewById(R.id.go); btn.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { helloTwo.this.setContentView(R.layout.second); helloTwo.this.setViewTwoCommand(); } }); Button btnExit=(Button)findViewById(R.id.exit); btnExit.setOnClickListener(new View.OnClickListener(){ public void onClick(View v){ helloTwo.this.finish(); } }); } public void setViewTwoCommand() { Button btnBack=(Button)findViewById(R.id.go2); btnBack.setOnClickListener(new View.OnClickListener(){ public void onClick(View v){ helloTwo.this.setContentView(R.layout.main); helloTwo.this.setViewOneCommand(); } }); } 最后,我们需要在 onCreate 的时候,也就是启动后的 main 界面上设置一下按钮事件处理 器。新的 onCreate 方法如下: public void onCreate(Bundle icicle) { super.onCreate(icicle); setTheme(android.R.style.Theme_Dark); setContentView(R.layout.main); setViewOneCommand(); } 编译,运行,OK。 2.还是回到正道上,多个 Activity 之间的跳转 Android 中提供一个叫 Intent 的类来实现屏幕之间的跳转,按文档的说法,似乎他们也建 议采用这种方法,Intent 的用法比较复杂,现在我先看看它最简单的用法。 先在应用中增加两个 Activity,这需要修改 AndroidManifest.xml 文件了,如下: 很简单,就是加一个标签而已,新标签的 class 是.HelloThreeB,显示的应用 标题与前一个 Activity 一样而已,然后第二步就是修改一个 HelloThree 类的实现,在 onCreate 方法中绑定按钮的事件处理器: public void onCreate(Bundle icicle) { super.onCreate(icicle); setTheme(android.R.style.Theme_Dark); setContentView(R.layout.main); setViewOneCommand(); } public void setViewOneCommand() { Button btn = (Button)findViewById(R.id.go); btn.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { Intent intent = new Intent(); intent.setClass(HelloThree.this, HelloThreeB.class); startActivity(intent); finish(); } }); Button btnExit=(Button)findViewById(R.id.exit); btnExit.setOnClickListener(new View.OnClickListener(){ public void onClick(View v){ HelloThree.this.finish(); } }); } 这里的跳转功能用 Intent 来操作,它的最简单用法就是用函数 setClass()设置跳转前后两 个 Activity 类的实例,然后调用 Activity 自己的 startActivity(intent)即可。最后一句 finish()表示将当前 Activity 关掉(如果不关掉会如何?你可以自己试一下看效果,事实上 有时我们是不需要关掉当前 Activity 的)。 然后,我们同样弄一个 Activity 类 HelloThreeB,代码与前面的差不多,只是将 setClass 的两个参数反一下,这样就可以简单地实现在两个 Activity 界面中来回切换的功能了。 3.如果我想在两个 Activity 之间进行数据交换,怎么办? 前例中的 startActivity()只有一个参数,如果需要向新打开的 Activity 传递参数,我们得 换一个函数了, Android 提供了 startSubActivity(Intent,int)这个函数来实现这个功能。 函数原型为: public void startSubActivity(Intent intent, int requestCode) 这里的 requestCode 用来标识某一个调用,一般由我们定义一个常量。 如何把参数传过去呢?Intent 类在提供 setClass()函数的同时也提供了一个 setData()函 数。 函数原型为:public Intent setData(ContentURI data) 参数类型是 ContentURI,它的详细内容下回再分析,现在就把它当成一个 String 类型来 用吧。 参数带到新的 Activity 后,同样用 Activity.getIntent()函数可以得到当前过来的 Intent 对象,然后用 getData()就取到参数了。 把参数带回来的方法是 Activity.setResult(),它有几个形式,现在先看最简单的一个吧。 函数原型是:public final void setResult(int resultCode, String data) resultCode 是返回代码,同样用来标识一个返回类型,而 data 则是它要返回的参数。 在原来的 Activity 中的事件处理回调函数 onActivityResult,会被系统调用,从它的参数 里可以得到返回值。 函数原型为:protected void onActivityResult(int requestCode, int resultCode,String data, Bundle extras) 这里的 requestCode 就是前面启动新 Activity 时的带过去的 requestCode,而 resultCode 则关联上了 setResult 中的 resultCode,data 是参数,extras 也是一个很 重要的东西,后面再研究一下它的作用。 下面,我们来看一下代码吧,先看看 HelloThree 中的代码: public void setViewOneCommand() { Button btn = (Button)findViewById(R.id.go); btn.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { try { Intent intent = new Intent(); intent.setClass(HelloThree.this, HelloThreeB.class); intent.setData(new ContentURI("One")); startSubActivity(intent,REQUEST_TYPE_A); } catch(Exception ex){} } }); Button btnExit=(Button)findViewById(R.id.exit); btnExit.setOnClickListener(new View.OnClickListener(){ public void onClick(View v){ HelloThree.this.finish(); } }); } protected void onActivityResult(int requestCode, int resultCode, String data, Bundle extras) { if (requestCode == REQUEST_TYPE_A) { if (resultCode == RESULT_OK) { Log.v(TAG,data); TextView txt = (TextView)findViewById(R.id.txt); txt.setText(data); } } } 这里的 REQUEST_TYPE_A 是我们定义的一个常量。在 onActivityResult 中用它与 RESULT_OK 一起作为条件判断如何处理返回值,这里只是简单将 TextView 显示值换成 传来的字串。 再来看看另一个 HelloThreeB 类的实现代码: private Intent i; protected void onCreate(Bundle icicle) { super.onCreate(icicle); setContentView(R.layout.second); i = getIntent(); android.util.Log.v(TAG,"onCreate"); Button btn = (Button)findViewById(R.id.go); btn.setOnClickListener(new View.OnClickListener(){ public void onClick(View v){ String result=HelloThreeB.this.i.getData().toString()+" And Two"; HelloThreeB.this.setResult(RESULT_OK,result); finish(); } }); TextView v = (TextView)findViewById(R.id.txt); v.setText("Param is "+i.getData().toString()); } 在按钮处理事件中,从 Intent 取出参数,处理一下再用 setResult 返回给前一个 Activity 即可。 编译运行即可。 今天先到这里,下一回我再研究一下 Activity 的生命周期的问题。 注意到在 Activity 的 API 中有大量的 onXXXX 形式的函数定义,除了我们前面用到的 onCreate 以外,还有 onStart,onStop 以及 onPause 等等。从字面上看,它们是一些 事件回调,那么次序又是如何的呢?其实这种事情,自己做个实验最明白不过了。在做这个 实验之前,我们先得找到在 Android 中的 Log 是如何输出的。 显然,我们要用的是 android.util.log 类,这个类相当的简单易用,因为它提供的全是一 些静态方法: Log.v(String tag, String msg); //VERBOSE Log.d(String tag, String msg); //DEBUG Log.i(String tag, String msg); //INFO Log.w(String tag, String msg); //WARN Log.e(String tag, String msg); //ERROR 前面的 tag 是由我们定义的一个标识,一般可以用“类名_方法名“来定义。 输出的LOG 信息,如果用Eclipse+ADT 开发,在LogCat中就可以看到,否则用adb logcat 也行,不过我是从来都依赖于 IDE 环境的。 好了,现在我们修改前面的 HelloThree 代码: public void onStart() { super.onStart(); Log.v(TAG,"onStart"); } public void onStop() { super.onStop(); Log.v(TAG,"onStop"); } public void onResume() { super.onResume(); Log.v(TAG,"onResume"); } public void onRestart() { super.onRestart(); Log.v(TAG,"onReStart"); } public void onPause() { super.onPause(); Log.v(TAG,"onPause"); } public void onDestroy() { super.onDestroy(); Log.v(TAG,"onDestroy"); } public void onFreeze(Bundle outState) { super.onFreeze(outState); Log.v(TAG,"onFreeze"); } 在 HelloThreeB 中也同样增加这样的代码,编译,运行一下,从 logcat 中分析输出的日 志。 在启动第一个界面 Activity One 时,它的次序是: onCreate (ONE) - onStart (ONE) - onResume(ONE) 虽然是第一次启动,也要走一遍这个 resume 事件。然后,我们点 goto 跳到第二个 Activity Two 中(前一个没有关闭),这时走的次序是: onFreeze(ONE) - onPause(ONE) - onCreate(TWO) - onStart(TWO) - onResume(TWO) - onStop(ONE) 说明,第二个 Activity Two 在启动前,One 会经历一个:冻结、暂停的过程,在启动 Two 后,One 才会被停止? 然后,我们再点 back 回到第一个界面,这时走的次序是: onPause(TWO) - onActivityResult(ONE) - onStart(ONE) - onRestart(ONE) - onResume(ONE) - onStop(TWO) - onDestroy(TWO) 说明,返回时,Two 没有经历冻结就直接暂停了,在 One 接收参数,重启后,Two 就停止 并被销毁了。 最后,我们点一下 Exit 退出应用,它的次序是: onPause(ONE) - onStop(ONE) - onDestroy(ONE) 说明如果我们用了 finish 的话,不会有 freeze,但是仍会经历 pause - stop 才被销毁。 这里有点疑问的是:为什么回来时先是 Start 才是 Restart?可是文档中的图上画的却是先 restart 再 start 的啊?不过,后面的表格中的描述好象是正确的,start 后面总是跟着 resume(如果是第一次)或者 restart(如果原来被 stop 掉了,这种情况会在 start 与 resume 中插一个 restart)。 下面不跑例子了,看看文档吧。 1.Android 用 Activity Stack 来管理多个 Activity,所以呢,同一时刻只会有最顶上的那 个 Activity 是处于 active 或者 running 状态。其它的 Activity 都被压在下面了。 2.如果非活动的 Activity 仍是可见的(即如果上面压着的是一个非全屏的 Activity 或透明 的 Activity),它是处于 paused 状态的。在系统内存不足的情况下,paused 状态的 Activity 是有可被系统杀掉的。只是不明白,如果它被干掉了,界面上的显示又会变成什么模样?看 来下回有必要研究一下这种情况了。 3.几个事件的配对可以比较清楚地理解它们的关系。Create 与 Destroy 配成一对,叫 entrie lifetime,在创建时分配资源,则在销毁时释放资源;往上一点还有 Start 与 Stop 一对,叫 visible lifetime,表达的是可见与非可见这么一个过程;最顶上的就是 Resume 和 Pause 这一对了,叫 foreground lifetime,表达的了是否处于激活状态的过程。 4.因此,我们实现的 Activity 派生类,要重载两个重要的方法:onCreate()进行初始化操 作,onPause()保存当前操作的结果。 除了 Activity Lifecycle 以外,Android 还有一个 Process Lifecycle 的说明: 在内存不足的时候,Android 是会主动清理门户的,那它又是如何判断哪个 process 是可 以清掉的呢?文档中也提到了它的重要性排序: 1.最容易被清掉的是 empty process,空进程是指那些没有 Activity 与之绑定,也没有任 何应用程序组件(如 Services 或者 IntentReceiver)与之绑定的进程,也就是说在这个 process 中没有任何 activity 或者 service 之类的东西,它们仅仅是作为一个 cache,在 启动新的 Activity 时可以提高速度。它们是会被优先清掉的。因此建议,我们的后台操作, 最好是作成 Service 的形式,也就是说应该在 Activity 中启动一个 Service 去执行这些操 作。 2.接下来就是 background activity 了,也就是被 stop 掉了那些 activity 所处的 process, 那些不可见的 Activity 被清掉的确是安全的,系统维持着一个 LRU 列表,多个处于 background 的 activity 都在这里面,系统可以根据 LRU 列表判断哪些 activity 是可以被 清掉的,以及其中哪一个应该是最先被清掉。不过,文档中提到在这个已被清掉的 Activity 又被重新创建的时候,它的 onCreate 会被调用,参数就是 onFreeze 时的那个 Bundle。 不过这里有一点不明白的是,难道这个 Activity 被 killed 时,Android 会帮它保留着这个 Bundle 吗? 3.然后就轮到 service process 了,这是一个与 Service 绑定的进程,由 startService 方 法启动。虽然它们不为用户所见,但一般是在处理一些长时间的操作(例如 MP3 的播放), 系统会保护它,除非真的没有内存可用了。 4.接着又轮到那些 visible activity 了,或者说 visible process。前面也谈到这个情况, 被 Paused 的 Activity 也是有可能会被系统清掉,不过相对来说,它已经是处于一个比较 安全的位置了。 5.最安全应该就是那个 foreground activity 了,不到迫不得已它是不会被清掉的。这种 process 不仅包括 resume 之后的 activity,也包括那些 onReceiveIntent 之后的 IntentReceiver 实例。 在 Android Application 的生命周期的讨论中,文档也提到了一些需要注意的事项:因为 Android 应用程序的生存期并不是由应用本身直接控制的,而是由 Android 系统平台进行 管理的,所以,对于我们开发者而言,需要了解不同的组件 Activity、Service 和 IntentReceiver 的生命,切记的是:如果组件的选择不当,很有可能系统会杀掉一个正在 进行重要工作的进程。 下一步要了解的应该是 Intent 和它的 IntentReceiver 了,改天继续。 刚看到 Intent 的时候,我的确有点困惑:从字面上来说,它表示一种意图和目的;从使用 上看,它似乎总是用于 Activity 之间的切换;而从它所在包 android.content 来看,它似 乎与内容有关。所以,我想或许可以这样理解它: Intent 类绑定一次操作,它负责携带这 次操作所需要的数据以及操作的类型等。 如果是这样的话,是否可以将它与事件处理联想起来?即一个 Intent 类似于一个 Event。 从 Intent 的两个最重要的成员操作类型(Action)和数据(Data)来看,似乎是有道理的。 文档中说,Intent 的 Action 的取值主要是一些定义好了的常量,例如 PICK_ACTION, VIEW_ACTION,EDIT_ACTION 之类的,而 Data 则是一个 ContentURI 类型的变量, 这一点,我们前面提到过。 而且文档中说 Intent 分为两大类,显性的(Explicit )的和隐性的(Implicit)。在前面的 例子中,我们在两个 Activity 之间跳转时初步使用了 Intent 类,当时是用 setClass 来设 置 Intent 的发起方与接收方,它被称为显性的 Intent,而隐性的 Intent 则不需要用 setClass 或 setComponent 来指定事件处理 器,利用 AndroidMenifest.xml 中的配置 就可以由平台定位事件的消费者。 一般来说,intent 要定位事件的目的地,无外乎需要以下几个信息: 1.种类(category),比如我们常见的 LAUNCHER_CATEGORY 就是表示这是一类应用 程序。 2.类型(type),在前面的例子中没用过,表示数据的类型,这是隐性 Intent 定位目标的 重要依据。 3.组件(component),前面的例子中用的是 setClass,不过也可以用 setComponent 来设置 intent 跳转的前后两个类实例。 4.附加数据(extras),在 ContentURI 之外还可以附加一些信息,它是 Bundle 类型的对 象。 Implicit Intent 的使用相对有点麻烦,我们来做一个例子。首先,我们需要增加一个类: HelloThreeProvider,它必须实现于 ConentProvider 接口,所以代码如下: public class HelloThreeProvider extends ContentProvider { public boolean onCreate() { return true; } public int delete(ContentURI url, String where, String[] whereArgs) { return 0; } public ContentURI insert(ContentURI url, ContentValues initialValues){ return url; } public Cursor query(ContentURI url, String[] projection, String selection, String[] selectionArgs, String groupBy, String having, String sort) { return null; } public int update(ContentURI url, ContentValues values, String where, String[] whereA rgs) { return 0; } public String getType(ContentURI url) { return "vnd.sharetop.hello.three/vnd.hello.three"; } } 这里面有一堆方法要实现,因为它们都是 ContentProvider 中的 abstract 方法,但是今 天的例子中它们都半没有什么用处,只是一个 getType 方法我们让它不管什么 url 都返回 一个表示 Intent 所携带的数据类型是我们定义的一个长字串: vnd.sharetop.hello.three/vnd.hello.three。 然后,在 AndroidMenifest.xml 中我们将上面这个 HelloThreeProvider 类加入应用程序: 相对于前面的例子,主要修改了 HelloThreeB 的配置,包括增加了一个标签 表示这是一个一般性的 activity 而已。增加了 标签,定义它负责处理 VIEW_ACTION 类型的操作。增加了 标签给出一个数据类型的定义串 vnd.sharetop.hello.three/vnd.hello.three。最主要的是在下增加的那 个标签,有个 authorities 属性,我们给的值是 cn.sharetop.android.hello, 待一会我们再说它的用处。 最后就是修改以前的跳转代码如下: Intent intent = new Intent(); intent.setData(new ContentURI("content://cn.sharetop.android.hello/one")); intent.setAction(intent.VIEW_ACTION); startActivity(intent); 现在我们的 setData 里的东西可与以前不一样的,是吧?注意到它的格式了吗? content://是个协议头,固定这样写就行了。然后就是那个 authorities 中定义的串了,再 后面就是我们自定义的东西了,我这里很简单的写个 one,其它还可以更长一点,如 one/101 之类的。它负责去关联上那个 provider 类。另外,增加了 setAction 的调用设 置操作为 VIEW_ACTION,与 Menifest 中的又挂上了。Android 平台负责根 据 Intent 的 Data 信息中的 authorities,找到 ContentProvider,然后 getType,用 type 和 intent 中的 Action 两个信息,再找到可以处理这个 intent 的消费者。 OK,编译运行。 其实,如果是在一个应用内部,这种隐性的 intent 实在有点别扭,个人觉得,这种松藕合 的实现方法,只适用于那些较大的系统或者多个不同的应用之间的调用,可手机上又有什么 “较大”的系统呢?无非是可以与不同来源的多个应用之间方便地互操作而已,那么会是什 么样的场景呢?比如,给 QQ 好友发送 gmail 邮件,用 GoogleMap 查找 QQ 好友所在的 位置?看上去挺不错的。 关于这个 ContentProvider,其实还有话说,它主要是的那些看似数据库操作的方法我们 都没真正去实现呢。不过今天就到这里了,等下回再去研究吧。 今天学习点轻松的内容吧,看看 android.app 包里的几个类。首先是这个在平台自的例子 中被广泛使用的 ListActivity。这个类其实就是一个含有一个 ListView 组件的 Activity 类。 也就是说,如果我们直接在一个普通的 Activity 中自己加一个 ListView 也是完全可以取代 这个 ListActivity 的,只是它更方便而已,方便到什么程度呢?来做个例子瞧瞧。 public class HelloTwoB extends ListActivity { public void onCreate(Bundle icicle) { super.onCreate(icicle); setTheme(android.R.style.Theme_Dark); setContentView(R.layout.mainb); List items = fillArray(); ArrayAdapter adapter = new ArrayAdapter(this,R.layout.list_row, items); this.setListAdapter(adapter); } private List fillArray() { List items = new ArrayList(); items.add("日曜日"); items.add("月曜日"); items.add("火曜日"); items.add("水曜日"); items.add("木曜日"); items.add("金曜日"); items.add("土曜日"); return items; } @Override protected void onListItemClick(ListView l, View v, int position, long id) { TextView txt = (TextView)this.findViewById(R.id.text); txt.setText("あすは "+l.getSelectedItem().toString()+"です。"); } } 的确可以简单到只需准备一个 List 对象并借助 Adapter 就可以构造出一个列表。重载 onListItemClick 方法可以响应选择事件,利用第一个参数可以访问到这个 ListView 实例 以得到选中的条目信息。这里有一点要说明的,就是如果更简单的话,其实连那个 setContentView 都可以不要了,Android 也会自动帮我们构造出一个全屏的列表。但是 本例中我们需要一个 TextView 来显示选中的条目,所以我们需要一个 layout.mainb 描述 一下这个列表窗口。 这里需要注意的是那个 ListView 的 ID,是系统自定义的 android:list,不是我们随便取的, 否则系统会说找不到它想要的 listview 了。然后,在这个 listview 之外,我们又增加了一 个 TextView,用来显示选中的条目。 再来说说这里用到的 ArrayAdapter,它的构造函数中第二个参数是一个资源 ID, ArrayAdapter 的 API 文档中说是要求用一个包含 TextView 的 layout 文件,平台用它来 显示每个选择条目的样式,这里的取值是 R.layout.list_row,所以,我们还有一个 list_row.xml 文件来描述这个布局,相当简单。 从 ArrayAdapter 上溯到 BaseAdapter,发现还有几个同源的 Adapter 也应该可以使用, 象 SimpleAdapter 和 CursorAdapter,还是做个例子来实验一下吧。 先看看 SimpleAdapter,说是 simple 却不 simple。 首先看看这个 fillMaps 方法,基本上就明白这个 simpleAdapter 是怎么回事了,在有些场 合它还是挺有用的,可以为每个条目绑定一个值: private List> fillMaps() { List> items = new ArrayList> (); HashMap i = new HashMap(); i.put("name","日曜日"); i.put("key", "SUN"); items.add(i); HashMap i1 = new HashMap(); i1.put("name","月曜日"); i1.put("key", "MON"); items.add(i1); HashMap i2 = new HashMap(); i2.put("name","火曜日"); i2.put("key", "TUE"); items.add(i2); HashMap i3 = new HashMap(); i3.put("name","水曜日"); i3.put("key", "WED"); items.add(i3); HashMap i4= new HashMap(); i4.put("name","木曜日"); i4.put("key", "THU"); items.add(i4); HashMap i5 = new HashMap(); i5.put("name","金曜日"); i5.put("key", "FRI"); items.add(i5); HashMap i6 = new HashMap(); i6.put("name","土曜日"); i.put("key", "SAT"); items.add(i6); return items; } 然后,在 HelloTwoB 中的 onCreate 函数中,修改代码,有几个不同:items 的元素是 HashMap 实例,这是一点变化,然后构造函数除了要求 items 以外,还要求提供一个 string[]来说明用 hash 表中的哪个字段显示在列表中,而后是一个资源 ID 的数组。我的 代码是这样的: //SimpleAdapter demo List> items = fillMaps(); SimpleAdapter adapter=new SimpleAdapter(this,items,R.layout.list_row,new String[]{"na me"},new int[]{R.id.item}); 编译跑一下可以看到结果了,是吧?只是显示的文字不太对,再改一下: protected void onListItemClick(ListView l, View v, int position, long id) { TextView txt = (TextView)this.findViewById(R.id.text); txt.setText(" あすは "+((HashMap)l.obtainItem(position)).get("key").toString()+" で す。"); } 这样就好多了,其实一般情况下我们都是用 ListView 中的 obtainItem 取得当前选中的条 目,然后转成 List 中的对应类型来使用的。 上面的例子中只显示 name 对应的值,其实你也可以试一下这样: SimpleAdapter adapter=new SimpleAdapter(this,items,R.layout.list_row,new String[]{"na me","key"},new int[]{R.id.item,R.id.item2}); 看看是什么效果。 再看看那个 CursorAdapter 吧,它的列表中元素要求是 Cursor,这东西与 DB 有关,不 过最简单的 DB 就是通讯簿。先从 Contacts.People 入手吧,同样修改代码: //CursorAdapter demo Cursor mCursor = this.getContentResolver().query(Contacts.People.CONTENT_URI, null, n ull, null, null); SimpleCursorAdapter adapter=new SimpleCursorAdapter(this,R.layout.list_row,mCursor,ne w String[]{Contacts.People.NAME},new int[]{R.id.item}); 因为单纯的 CursorAdapter 是抽象类,所以我用的是它的子类 SimpleCursorAdapter, 很好理解,先用 ContentResolver 查询通讯簿得到一个游标,然后告诉 SimpleCursorAdapter 要用其中的 People.NAME 作为显示项来构造出一个 adapter 即 可。 现在的 onListItemClick 也不一样了,如下: protected void onListItemClick(ListView l, View v, int position, long id) { TextView txt = (TextView)this.findViewById(R.id.text); Cursor c = (Cursor)l.obtainItem(position); txt.setText("SEL = "+c.getString(c.getColumnIndex(Contacts.People.NUMBER))); } 这里同样是先用 obtainItem 取到游标,然后用从记录中取出想要的字段显示即可。在做这 个例子时,因为权限的问题我们还得修改一下 AndroidManifest.xml 文件,让我们的应用 可以访问到通讯簿: ... ...
还剩28页未读

继续阅读

下载pdf到电脑,查找使用更方便

pdf的实际排版效果,会与网站的显示效果略有不同!!

需要 20 金币 [ 分享pdf获得金币 ] 4 人已下载

下载pdf

pdf贡献者

liuwt

贡献于2010-09-19

下载需要 20 金币 [金币充值 ]
亲,您也可以通过 分享原创pdf 来获得金币奖励!
下载pdf