配色: 字号:
Android(经典实例)
2012-02-10 | 阅:  转:  |  分享 
  
Android学习笔记-让我们快速上手吧Google的AndroidSDK发布也有一段时间了,一直想研究一下却苦于找不到时间。

利用这个周未,开始强迫自己再次进入学习状态,原因很简单:我看好开放的gPhone。SDK的下载与安装并不复杂,网上也有不少同学已经进入状态了,我就不再重复了吧。

今天主要讨论的,还是永远不变的话题:HelloWorld.1.最简单的HelloWorld

安装了SDK后,直接生成一个AndroidProject,一句代码不用写,就能跑出一个最简单的HelloWorld例程。我们看一下它的代码:

publicvoidonCreate(Bundleicicle){super.onCreate(icicle);

setTheme(android.R.style.Theme_Dark);setContentView(R.layout.main);

}看上去实在很简单,只有两句话而已。关键在这个R.layout.main上,凭直觉,这

应该是定义的资源。的确,在R.java中只是定义了一个staticint而已,真正的资源描述在res/layout/main.xml文件里(注意:这里的R.java不要手工编辑,每次build

project时它都会根据res下的资源描述被自动修改)。


android:layout_width="fill_parent"android:layout_height="fill_parent"

>
android:layout_width="fill_parent"android:layout_height="wrap_content"

android:text="HelloWorld"/>

这个文件很好读,一个描述了这是一个线性排列的布局,

android:orientation=vertical表示所有组件将纵向排布。而经典的HelloWorld是用一个TextView来展示的。

由此,我们知道,Android的程序从一个Activity派生出来,并且从它的onCreate开始启动;Android里要显示的组件用XML文件描述而不用在代码中硬编码(这是一个好

的习惯,我们应该从一开始就坚持下去);2.让Button来说HelloWorld

上面的例子是ADT自动生成的代码,似乎与我们一点关系也没有。那我们来改一下代码,因为在windows平台上的Helloworld经常是由一个按钮触发的,所以,我们想第二个

Helloworld应该是这样的:加一个按钮和文本输入框,单击按钮后在原来的TextView后面加上输入框中输入的文字。

第一步是,增加一个Button和一个EditText,与TextView一样,它们也在main.xml里描述一下:


android:layout_height="wrap_content"android:text=""

/>
android:layout_width="wrap_content"android:layout_height="wrap_content"

android:text="@string/go">

这里有两个地方要注意:id=@+id/go,这表示需要一个唯一的UID来作为Button的ID,

它的引用名是go。还有一个是android:text=@string/go表示这个按钮的文本不是直接写有main.xml里了,而是来源于另一个资源描述文件strings.xml里,本例中的

strings.xml如下:

helloTwo

提示你好,中国

确定浏览

然后,在代码里(onCreate函数中)我们加上以下代码(简单起见,用了嵌套类):

Buttonbtn=(Button)findViewById(R.id.go);btn.setOnClickListener(newView.OnClickListener()

{publicvoidonClick(Viewv)

{EditTextedt=(EditText)helloTwo.this.findViewById(R.id.edt);

TextViewtxt=(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.再让菜单SayHello

从API文档中我们看到Activity中有两个函数:onCreateOptionsMenu和onOptionsItemSelected,显示,这个OptionsMenu就是所谓的上下文菜单(在GPhone

的模拟器上,有个键专用于弹出这个菜单)。下面我们就为这个HelloWorld例子加上一个菜单,并且让它可以Sayhello。

这次,我们不涉及到资源的描述文件了,而是直接使用这两个函数来实现,其实代码也很简单,所以,我们再增加一个退出应用的功能(否则每次都是按取消键退出应用

显示太不专业了)。代码如下:

publicbooleanonCreateOptionsMenu(Menumenu){

super.onCreateOptionsMenu(menu);menu.add(0,1,"sayhello");

menu.add(0,2,"exit");returntrue;

}publicbooleanonOptionsItemSelected(Itemitem)

{super.onOptionsItemSelected(item);

intid=item.getId();switch(id){

case1:AlertDialog.show(this,getString(R.string.app_name),

getString(R.string.msg_dialog),getString(R.string.ok_dialog),true);

break;case2:

finish();break;

}在CreateOptionsMenu时,我们简单地增加两个菜单项,menu.add(组ID,项ID,显

示文本),(注意:这里我直接将文字写在代码里,这并不提倡)。然后,在OptionsItemSelected事件中,我们根据选中的菜单项做相应处理,如果选中1,则弹出

一个对话框显示资源文件中的“你好,中国”,如果选中2则退出应用。AlertDialog.show是一个静态方法,类似于我们在WIN平台上经常使用的

MessageBox一样,很方便的。

来源:http://www.sf.org.cn/Android/lumen/20976.html

Android学习笔记(2)-初识Activity根据文档的解释,Activity是Android开发中非常重要的一个基础类。我把它想像

成J2ME中的Display类,或者是Win32平台上的Form类,也许不准确,但是它的重要性我觉得应该是一样的(当然,如果我们写的是一个没有界面的应用,例如后台运行的服

务之类的,可以不用Display的)。1.在一个Activity中使用多个View

如果把Activity看作MVC中的Control?它负责管理UI和接受事件(包括用户的输入),虽然说一个Activity通常对应一个屏幕,但事实上,我们是可以只用一个Activity

管理多个不同的View来实现简单的逻辑。首先,我们增加一个新的资源描述layout/second.xml。


android:orientation="vertical"android:layout_width="fill_parent"

android:layout_height="fill_parent">


android:layout_height="wrap_content"android:text="Hello中国"

/>
android:layout_width="wrap_content"android:layout_height="wrap_content"

android:text="back">



除了一个“Hello中国”以外,增加一个按钮可以返回前一个界面。然后,在代码中我们要为helloTwo增加两个方法,setViewOneCommand和setViewTwoCommand,分别处

理一下在不同界面时,从资源里加载组件并为组件绑定一个事件处理器。publicvoidsetViewOneCommand()

{Buttonbtn=(Button)findViewById(R.id.go);

btn.setOnClickListener(newView.OnClickListener(){

publicvoidonClick(Viewv){

helloTwo.this.setContentView(R.layout.second);helloTwo.this.setViewTwoCommand();

}});

ButtonbtnExit=(Button)findViewById(R.id.exit);btnExit.setOnClickListener(newView.OnClickListener(){

publicvoidonClick(Viewv){helloTwo.this.finish();

}});

}publicvoidsetViewTwoCommand()

{ButtonbtnBack=(Button)findViewById(R.id.go2);

btnBack.setOnClickListener(newView.OnClickListener(){publicvoidonClick(Viewv){

helloTwo.this.setContentView(R.layout.main);helloTwo.this.setViewOneCommand();

}});

}最后,我们需要在onCreate的时候,也就是启动后的main界面上设置一下按钮事

件处理器。新的onCreate方法如下:publicvoidonCreate(Bundleicicle){

super.onCreate(icicle);setTheme(android.R.style.Theme_Dark);

setContentView(R.layout.main);setViewOneCommand();

}编译,运行,OK。

2.还是回到正道上,多个Activity之间的跳转Android中提供一个叫Intent的类来实现屏幕之间的跳转,按文档的说法,似乎他

们也建议采用这种方法,Intent的用法比较复杂,现在我先看看它最简单的用法。先在应用中增加两个Activity,这需要修改AndroidManifest.xml文件了,如下:


package="cn.sharetop.android.hello.three">




/>






很简单,就是加一个标签而已,新标签的class是.HelloThreeB,显示的应用标题

与前一个Activity一样而已,然后第二步就是修改一个HelloThree类的实现,在onCreate方法中绑定按钮的事件处理器:

publicvoidonCreate(Bundleicicle){super.onCreate(icicle);

setTheme(android.R.style.Theme_Dark);setContentView(R.layout.main);

setViewOneCommand();}

publicvoidsetViewOneCommand(){

Buttonbtn=(Button)findViewById(R.id.go);btn.setOnClickListener(newView.OnClickListener()

{publicvoidonClick(Viewv)

{Intentintent=newIntent();

intent.setClass(HelloThree.this,HelloThreeB.class);startActivity(intent);

finish();}

});ButtonbtnExit=(Button)findViewById(R.id.exit);

btnExit.setOnClickListener(newView.OnClickListener(){publicvoidonClick(Viewv){

HelloThree.this.finish();}

});}

这里的跳转功能用Intent来操作,它的最简单用法就是用函数setClass()设置跳转前后两个Activity类的实例,然后调用Activity自己的startActivity(intent)即可。

最后一句finish()表示将当前Activity关掉(如果不关掉会如何?你可以自己试一下看效果,事实上有时我们是不需要关掉当前Activity的)。

然后,我们同样弄一个Activity类HelloThreeB,代码与前面的差不多,只是将setClass的两个参数反一下,这样就可以简单地实现在两个Activity界面中来回切换的

功能了。3.如果我想在两个Activity之间进行数据交换,怎么办?

前例中的startActivity()只有一个参数,如果需要向新打开的Activity传递参数,我们得换一个函数了,Android提供了startSubActivity(Intent,int)这个函数来实现

这个功能。函数原型为:publicvoidstartSubActivity(Intentintent,int

requestCode)这里的requestCode用来标识某一个调用,一般由我们定义一个常量。

如何把参数传过去呢?Intent类在提供setClass()函数的同时也提供了一个setData()函数。

函数原型为:publicIntentsetData(ContentURIdata)参数类型是ContentURI,它的详细内容下回再分析,现在就把它当成一个String类型来

用吧。参数带到新的Activity后,同样用Activity.getIntent()函数可以得到当前过来的

Intent对象,然后用getData()就取到参数了。把参数带回来的方法是Activity.setResult(),它有几个形式,现在先看最简单的

一个吧。函数原型是:publicfinalvoidsetResult(intresultCode,Stringdata)

resultCode是返回代码,同样用来标识一个返回类型,而data则是它要返回的参数。在原来的Activity中的事件处理回调函数onActivityResult,会被系统调用,从它

的参数里可以得到返回值。函数原型为:protectedvoidonActivityResult(intrequestCode,int

resultCode,Stringdata,Bundleextras)这里的requestCode就是前面启动新Activity时的带过去的requestCode,而resultCode

则关联上了setResult中的resultCode,data是参数,extras也是一个很重要的东西,后面再研究一下它的作用。

下面,我们来看一下代码吧,先看看HelloThree中的代码:publicvoidsetViewOneCommand()

{Buttonbtn=(Button)findViewById(R.id.go);

btn.setOnClickListener(newView.OnClickListener(){

publicvoidonClick(Viewv){

try{

Intentintent=newIntent();intent.setClass(HelloThree.this,HelloThreeB.class);

intent.setData(newContentURI("One"));

startSubActivity(intent,REQUEST_TYPE_A);}

catch(Exceptionex){}}

});ButtonbtnExit=(Button)findViewById(R.id.exit);

btnExit.setOnClickListener(newView.OnClickListener(){publicvoidonClick(Viewv){

HelloThree.this.finish();}

});}

protectedvoidonActivityResult(intrequestCode,intresultCode,Stringdata,Bundleextras)

{if(requestCode==REQUEST_TYPE_A){

if(resultCode==RESULT_OK){Log.v(TAG,data);

TextViewtxt=(TextView)findViewById(R.id.txt);txt.setText(data);

}}

}这里的REQUEST_TYPE_A是我们定义的一个常量。在onActivityResult中用它与

RESULT_OK一起作为条件判断如何处理返回值,这里只是简单将TextView显示值换成传来的字串。

再来看看另一个HelloThreeB类的实现代码:privateIntenti;

protectedvoidonCreate(Bundleicicle){super.onCreate(icicle);

setContentView(R.layout.second);

i=getIntent();

android.util.Log.v(TAG,"onCreate");Buttonbtn=(Button)findViewById(R.id.go);

btn.setOnClickListener(newView.OnClickListener(){publicvoidonClick(Viewv){

Stringresult=HelloThreeB.this.i.getData().toString()+"AndTwo";

HelloThreeB.this.setResult(RESULT_OK,result);finish();

}});

TextViewv=(TextView)findViewById(R.id.txt);v.setText("Paramis"+i.getData().toString());

}在按钮处理事件中,从Intent取出参数,处理一下再用setResult返回给前一个

Activity即可。编译运行即可。

来源:http://www.sf.org.cn/Android/lumen/20977.htmlAndroid学习笔记(3)-Activity的生命周期

注意到在Activity的API中有大量的onXXXX形式的函数定义,除了我们前面用到的onCreate以外,还有onStart,onStop以及onPause等等。从字面上看,它们是一些

事件回调,那么次序又是如何的呢?其实这种事情,自己做个实验最明白不过了。在做这个实验之前,我们先得找到在Android中的Log是如何输出的。

显然,我们要用的是android.util.log类,这个类相当的简单易用,因为它提供的全是一些静态方法:

Log.v(Stringtag,Stringmsg);//VERBOSELog.d(Stringtag,Stringmsg);//DEBUG

Log.i(Stringtag,Stringmsg);//INFOLog.w(Stringtag,Stringmsg);//WARN

Log.e(Stringtag,Stringmsg);//ERROR前面的tag是由我们定义的一个标识,一般可以用“类名_方法名“来定义。

输出的LOG信息,如果用Eclipse+ADT开发,在LogCat中就可以看到,否则用adblogcat也行,不过我是从来都依赖于IDE环境的。

好了,现在我们修改前面的HelloThree代码:publicvoidonStart()

{super.onStart();

Log.v(TAG,"onStart");}

publicvoidonStop(){

super.onStop();Log.v(TAG,"onStop");

}publicvoidonResume()

{super.onResume();

Log.v(TAG,"onResume");}

publicvoidonRestart(){

super.onRestart();Log.v(TAG,"onReStart");

}publicvoidonPause()

{super.onPause();

Log.v(TAG,"onPause");}

publicvoidonDestroy(){

super.onDestroy();Log.v(TAG,"onDestroy");

}publicvoidonFreeze(BundleoutState)

{super.onFreeze(outState);

Log.v(TAG,"onFreeze");}

在HelloThreeB中也同样增加这样的代码,编译,运行一下,从logcat中分析输出的日志。

在启动第一个界面ActivityOne时,它的次序是:onCreate(ONE)-onStart(ONE)-onResume(ONE)

虽然是第一次启动,也要走一遍这个resume事件。然后,我们点goto跳到第二个ActivityTwo中(前一个没有关闭),这时走的次序是:

onFreeze(ONE)-onPause(ONE)-onCreate(TWO)-onStart(TWO)-onResume(TWO)-onStop(ONE)

说明,第二个ActivityTwo在启动前,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用ActivityStack来管理多个Activity,所以呢,同一时刻只会有最顶上的那个Activity是处于active或者running状态。其它的Activity都被压在下面了。

2.如果非活动的Activity仍是可见的(即如果上面压着的是一个非全屏的Activity或透明的Activity),它是处于paused状态的。在系统内存不足的情况下,paused状

态的Activity是有可被系统杀掉的。只是不明白,如果它被干掉了,界面上的显示又会变成什么模样?看来下回有必要研究一下这种情况了。

3.几个事件的配对可以比较清楚地理解它们的关系。Create与Destroy配成一对,叫entrielifetime,在创建时分配资源,则在销毁时释放资源;往上一点还有Start与Stop

一对,叫visiblelifetime,表达的是可见与非可见这么一个过程;最顶上的就是Resume和Pause这一对了,叫foregroundlifetime,表达的了是否处于激活状态的过程。

4.因此,我们实现的Activity派生类,要重载两个重要的方法:onCreate()进行初始化操作,onPause()保存当前操作的结果。

除了ActivityLifecycle以外,Android还有一个ProcessLifecycle的说明:在内存不足的时候,Android是会主动清理门户的,那它又是如何判断哪个process

是可以清掉的呢?文档中也提到了它的重要性排序:1.最容易被清掉的是emptyprocess,空进程是指那些没有Activity与之绑定,也

没有任何应用程序组件(如Services或者IntentReceiver)与之绑定的进程,也就是说在这个process中没有任何activity或者service之类的东西,它们仅仅是作为一个

cache,在启动新的Activity时可以提高速度。它们是会被优先清掉的。因此建议,我们的后台操作,最好是作成Service的形式,也就是说应该在Activity中启动一个

Service去执行这些操作。2.接下来就是backgroundactivity了,也就是被stop掉了那些activity所处的

process,那些不可见的Activity被清掉的确是安全的,系统维持着一个LRU列表,多个处于background的activity都在这里面,系统可以根据LRU列表判断哪些activity

是可以被清掉的,以及其中哪一个应该是最先被清掉。不过,文档中提到在这个已被清掉的Activity又被重新创建的时候,它的onCreate会被调用,参数就是onFreeze时的

那个Bundle。不过这里有一点不明白的是,难道这个Activity被killed时,Android会帮它保留着这个Bundle吗?

3.然后就轮到serviceprocess了,这是一个与Service绑定的进程,由startService方法启动。虽然它们不为用户所见,但一般是在处理一些长时间的操作(例如MP3的播

放),系统会保护它,除非真的没有内存可用了。4.接着又轮到那些visibleactivity了,或者说visibleprocess。前面也谈到这

个情况,被Paused的Activity也是有可能会被系统清掉,不过相对来说,它已经是处于一个比较安全的位置了。

5.最安全应该就是那个foregroundactivity了,不到迫不得已它是不会被清掉的。这种process不仅包括resume之后的activity,也包括那些onReceiveIntent之后的

IntentReceiver实例。在AndroidApplication的生命周期的讨论中,文档也提到了一些需要注意的事项:

因为Android应用程序的生存期并不是由应用本身直接控制的,而是由Android系统平台进行管理的,所以,对于我们开发者而言,需要了解不同的组件Activity、Service

和IntentReceiver的生命,切记的是:如果组件的选择不当,很有可能系统会杀掉一个正在进行重要工作的进程。

来源:http://www.sf.org.cn/Android/lumen/20978.htmlAndroid学习笔记(4)-学习Intent的使用

刚看到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类

型的对象。ImplicitIntent的使用相对有点麻烦,我们来做一个例子。首先,我们需要增加一个类:

HelloThreeProvider,它必须实现于ConentProvider接口,所以代码如下:publicclassHelloThreeProviderextendsContentProvider{

publicbooleanonCreate(){returntrue;

}

publicintdelete(ContentURIurl,Stringwhere,String[]whereArgs){return0;

}publicContentURIinsert(ContentURIurl,ContentValuesinitialValues){

returnurl;}

publicCursorquery(ContentURIurl,String[]projection,Stringselection,

String[]selectionArgs,StringgroupBy,Stringhaving,Stringsort){

returnnull;}

publicintupdate(ContentURIurl,ContentValuesvalues,Stringwhere,String[]whereArgs){

return0;}

publicStringgetType(ContentURIurl){return"vnd.sharetop.hello.three/vnd.hello.three";

}}

这里面有一堆方法要实现,因为它们都是ContentProvider中的abstract方法,但

是今天的例子中它们多半没有什么用处,只是一个getType方法我们让它不管什么url都返回一个表示Intent所携带的数据类型是我们定义的一个长字串:

vnd.sharetop.hello.three/vnd.hello.three。然后,在AndroidMenifest.xml中我们将上面这个HelloThreeProvider类加入应用程序:


op.android.hello"/>















相对于前面的例子,主要修改了HelloThreeB的配置,包括增加了一个

标签表示这是一个一般性的activity而已。增加了标签,定义它负责处理VIEW_ACTION类型的操作。增加了标签给出一个数据类型的定义串

vnd.sharetop.hello.three/vnd.hello.three。最主要的是在下增加的那个标签,有个authorities属性,我们给的值是cn.sharetop.android.hello,

待一会我们再说它的用处。最后就是修改以前的跳转代码如下:

Intentintent=newIntent();intent.setData(newContentURI("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,其实还有话说,它主要是的那些看似数据库操作的方法

我们都没真正去实现呢。不过今天就到这里了,等下回再去研究吧。来源:http://www.sf.org.cn/Android/lumen/20979.html

Android学习笔记(5)-关于ListActivity的简单体验今天学习点轻松的内容吧,看看android.app包里的几个类。首先是这个在平台自

的例子中被广泛使用的ListActivity。这个类其实就是一个含有一个ListView组件的Activity类。也就是说,如果我们直接在一个普通的Activity中自己加一个ListView

也是完全可以取代这个ListActivity的,只是它更方便而已,方便到什么程度呢?来做个例子瞧瞧。

publicclassHelloTwoBextendsListActivity...{publicvoidonCreate(Bundleicicle)...{

super.onCreate(icicle);setTheme(android.R.style.Theme_Dark);

setContentView(R.layout.mainb);Listitems=fillArray();

ArrayAdapteradapter=newArrayAdapter(this,R.layout.list_row,items);

this.setListAdapter(adapter);}

privateListfillArray()...{Listitems=newArrayList();

items.add("日曜日");items.add("月曜日");

items.add("火曜日");items.add("水曜日");

items.add("木曜日");items.add("金曜日");

items.add("土曜日");returnitems;

}@Override

protectedvoidonListItemClick(ListViewl,Viewv,intposition,longid)

...{TextViewtxt=(TextView)this.findViewById(R.id.text);

txt.setText("あすは"+l.getSelectedItem().toString()+"です。");}

}的确可以简单到只需准备一个List对象并借助Adapter就可以构造出一个列表。重

载onListItemClick方法可以响应选择事件,利用第一个参数可以访问到这个ListView实例以得到选中的条目信息。这里有一点要说明的,就是如果更简单的话,其实连那个

setContentView都可以不要了,Android也会自动帮我们构造出一个全屏的列表。但是本例中我们需要一个TextView来显示选中的条目,所以我们需要一个layout.mainb描

述一下这个列表窗口。


android:layout_width="fill_parent"android:layout_height="fill_parent"

>
android:layout_width="fill_parent"android:layout_height="wrap_content"

android:text=""/>


android:layout_height="0dip"android:layout_weight="1"

android:drawSelectorOnTop="false"/>

这里需要注意的是那个ListView的ID,是系统自定义的android:list,不是我们

随便取的,否则系统会说找不到它想要的listview了。然后,在这个listview之外,我们又增加了一个TextView,用来显示选中的条目。

再来说这里用到的ArrayAdapter,它的构造函数中第二个参数是一个资源ID,ArrayAdapter的API文档中说是要求用一个包含TextView的layout文件,平台用它来

显示每个选择条目的样式,这里的取值是R.layout.list_row,所以,我们还有一个list_row.xml文件来描述这个布局,相当简单。


android:orientation="vertical"android:layout_width="fill_parent"

android:layout_height="fill_parent">


android:layout_width="wrap_content"android:layout_height="wrap_content"/>


android:layout_width="wrap_content"android:layout_height="wrap_content"/>

从ArrayAdapter上溯到BaseAdapter,发现还有几个同源的Adapter也应该可以使

用,象SimpleAdapter和CursorAdapter,还是做个例子来实验一下吧。先看看SimpleAdapter,说是simple却不simple。

首先看看这个fillMaps方法,基本上就明白这个simpleAdapter是怎么回事了,在有些场合它还是挺有用的,可以为每个条目绑定一个值:

privateList>fillMaps()...{

List>items=newArrayList>();

HashMapi=newHashMap();

i.put("name","日曜日");i.put("key","SUN");

items.add(i);HashMapi1=newHashMap();

i1.put("name","月曜日");i1.put("key","MON");

items.add(i1);HashMapi2=newHashMap();

i2.put("name","火曜日");i2.put("key","TUE");

items.add(i2);HashMapi3=newHashMap();

i3.put("name","水曜日");i3.put("key","WED");

items.add(i3);HashMapi4=newHashMap();

i4.put("name","木曜日");i4.put("key","THU");

items.add(i4);HashMapi5=newHashMap();

i5.put("name","金曜日");i5.put("key","FRI");

items.add(i5);HashMapi6=newHashMap();

i6.put("name","土曜日");i.put("key","SAT");

items.add(i6);returnitems;

}然后,在HelloTwoB中的onCreate函数中,修改代码,有几个不同:items的元素

是HashMap实例,这是一点变化,然后构造函数除了要求items以外,还要求提供一个string[]来说明用hash表中的哪个字段显示在列表中,而后是一个资源ID的数组。我

的代码是这样的://SimpleAdapterdemo

List>items=fillMaps();SimpleAdapteradapter=newSimpleAdapter(this,items,R.layout.list_row,newStr

ing[]{"name"},newint[]{R.id.item});编译跑一下可以看到结果了,是吧?只是显示的文字不太对,再改一下:

protectedvoidonListItemClick(ListViewl,Viewv,intposition,longid){

TextViewtxt=(TextView)this.findViewById(R.id.text);txt.setText("あす

は"+((HashMap)l.obtainItem(position)).get("key").toString()+"です。");}

这样就好多了,其实一般情况下我们都是用ListView中的obtainItem取得当前选中的条目,然后转成List中的对应类型来使用的。

上面的例子中只显示name对应的值,其实你也可以试一下这样:

SimpleAdapteradapter=newSimpleAdapter(this,items,R.layout.list_row,newString[]{"name","key"},newint[]{R.id.item,R.id.item2});

看看是什么效果。再看看那个CursorAdapter吧,它的列表中元素要求是Cursor,这东西与DB有关,

不过最简单的DB就是通讯簿。先从Contacts.People入手吧,同样修改代码://CursorAdapterdemo

CursormCursor=this.getContentResolver().query(Contacts.People.CONTENT_URI,null,null,null,null);

SimpleCursorAdapteradapter=newSimpleCursorAdapter(this,R.layout.list_row,mCursor,newString[]{Contacts.People.NAME},newint[]{R.id.item});

因为单纯的CursorAdapter是抽象类,所以我用的是它的子类SimpleCursorAdapter,很好理解,先用ContentResolver查询通讯簿得到一个游标,然后告诉

SimpleCursorAdapter要用其中的People.NAME作为显示项来构造出一个adapter即可。现在的onListItemClick也不一样了,如下:

protectedvoidonListItemClick(ListViewl,Viewv,intposition,longid)

{TextViewtxt=(TextView)this.findViewById(R.id.text);

Cursorc=(Cursor)l.obtainItem(position);txt.setText("SEL="+c.getString(c.getColumnIndex(Contacts.People.N

UMBER)));}

这里同样是先用obtainItem取到游标,然后用从记录中取出想要的字段显示即可。

在做这个例子时,因为权限的问题我们还得修改一下AndroidManifest.xml文件,让我们的应用可以访问到通讯簿:





......来源:http://www.sf.org.cn/Android/lumen/20980.html

Android学习笔记(6)—关于Dialog的简单体验继续android.app中的几个类的学习,今天的内容是那几个Dialog的体验。

注意到android.app包下除了Dialog(可用于制作复杂的对话框)以外,还包括了几个系统定义好的对话框类,如DatePickerDialog、TimePickerDialog及AlertDialog。

其中AlertDialog我上回用过一次,基本上就那样子了,今天看看另外两个对话框的使用吧。

首先是DatePickerDialog类,修改代码如下:publicclassHelloTwoCextendsActivityimplementsOnClickListener,OnDat

eSetListener{publicHelloTwoC(){

super();}

publicvoidonCreate(Bundleicicle){super.onCreate(icicle);

setTheme(android.R.style.Theme_Dark);setContentView(R.layout.mainc);

Buttonbtn=(Button)findViewById(R.id.date);btn.setOnClickListener(this);

}@Override

publicvoidonClick(Viewv){Calendard=Calendar.getInstance(Locale.CHINA);

d.setTime(newDate());DatePickerDialogdlg=newDatePickerDialog(this,this,d.get(Calendar.YEAR),

d.get(Calendar.MONTH),d.get(Calendar.DAY_OF_MONTH),d.get(Calendar.DAY_OF_WEEK));

dlg.show();}

@OverridepublicvoiddateSet(DatePickerdp,inty,intm,intd){

TextViewtxt=(TextView)findViewById(R.id.text);txt.setText(Integer.toString(y)+"-"+Integer.toString(m)+"-"+Integer.toStr

ing(d));}

}很简单的,无非是需要一个OnDateSetListener接口的实现而已,在它里面的dateSet

方法中就可以得到选择的日期了。而TimePickerDialog与DatePickerDialog使用如出一辙,就不多说了。

看另一个ProgressDialog的用法吧,这个类与AlertDialog一样包含了多个

static的方法,所以使用起来是非常方便的。比如说,如果我们需要用它来表示一个长时间的操作,很简单的用一句话就可以了:

ProgressDialog.show(this,null,"operationrunning...",true,true);URL:http://www.sf.org.cn/Android/lumen/20981.html

Android学习笔记(7)—关于Service和Notification的体验大略地看了一下android.app下的Service类,觉得它与Activity非常相似,只是

要注意几个地方:1.生命周期,Service的从onCreate()->onStart(int,Bundle)->onDestroy()显得

更为简单。但是它的onStart是带参数的,第一个ID可用来标识这个service,第二个参数显示是用来传递数据的了。比较Activity,传递数据的Bundle是在onCreate就带

进入的。2.Service的启动由Context.startService开始,其实Activity或者Service都是

Context的派生类。结束于Context.stopService()或者它自己的stopSelf()。3.Service还有一个与Activity不一样的是它可以由另一个Context去绑定一个已

存在的Service。就是这个方法Context.bindService(),被绑定的Service要求是已经onCreate了但可以没有onStart。在Service类中有个抽象方法getBinder()可以得到这

个IBinder对象。关于这方面的细节,以后再看,这里只做个记录罢。4.与Service有关的还有一个安全的问题,可以在AndroidManifest.xml中用

标签来声明一个Service的访问权限,关于Android的安全问题也留待以后再解决吧。

我一直相信一种水到渠成的学习方法,先从最简单的东西入手,就不会觉得学习很枯燥了。

下面来做个例子。修改AndroidManifest.xml文件,增加一个Activity和一个Service:



HelloTwoD.java的代码比较简单,如下:

publicclassHelloTwoDextendsActivityimplementsOnClickListener{

publicHelloTwoD(){

super();}

publicvoidonCreate(Bundleicicle){super.onCreate(icicle);

setTheme(android.R.style.Theme_Dark);setContentView(R.layout.maind);

Buttonbtn=(Button)findViewById(R.id.btnTest);btn.setOnClickListener(this);

}

@OverridepublicvoidonClick(Viewarg0){

//用一个显式的Intent来启动服务Intenti=newIntent();

i.setClass(this,HelloTwoDService.class);

//带上我的名字Bundleb=newBundle();

b.putString("name","sharetop");this.startService(i,b);

}

}当然要启动这个HelloTwoD,也需要在我最初的那个HelloTwo中加一点代码(我就

不罗嗦了)。再看看那个HelloTwoDService是如何实现的:publicclassHelloTwoDServiceextendsService{

publicTimertimer;publicfinalStringTAG="HelloTwoDService_TAG";

publicvoidonCreate(){super.onCreate();

Log.d(TAG,"onCreate");

timer=newTimer(true);}

@OverridepublicIBindergetBinder(){

//TODOAuto-generatedmethodstubreturnnull;

}

publicvoidonStart(intstartId,Bundlearg){

//看看startId是什么内容if(arg!=null)

Log.d(TAG,"onStart"+Integer.valueOf(startId).toString()+"from"+arg.getString("name"));

elseLog.d(TAG,"onStartwithnullBundle");

timer.schedule(newTimerTask(){publicvoidrun(){

//表示一下我的存在Log.d(TAG,"sayfromatimer.");

//停掉自己这个服务HelloTwoDService.this.stopSelf();

}},5000);

}publicvoidonDestroy()

{Log.d(TAG,"onDestroy");

}}

这里我用一个定时器timer来延时5秒钟显示消息,否则立即就显示出来觉得不象

一个后台服务了。用日志输出那个onStart中的startId看看,原来只是一个标识而已。下面来个简单的NotificationManager吧,看了看API文档,觉得最简单地恐怕就

是那个NotificationManager.notifyWithText()了,修改上面的run方法如下:timer.schedule(newTimerTask(){

publicvoidrun(){NotificationManagermanager=(NotificationManager)getSystemService

(NOTIFICATION_SERVICE);manager.notifyWithText(1001,"わたしはSHARETOPです。",Notificati

onManager.LENGTH_LONG,null);HelloTwoDService.this.stopSelf();

}},5000);

再试试看效果。太简单了,Notification主要是用于后台服务用来通知前台,所以,Android提供了三类不同的通知方式,notifyWithText可以简单地显示一个字串,而

notifyWithView稍复杂点,可以有一个view来构造这个显示信息框,而最灵活的就是那个notify(intid,Notificationnotification)了,参数notification是类

Notification的实例。修改一下刚才的那个run方法,如下:

timer.schedule(newTimerTask(){publicvoidrun(){

NotificationManagermanager=(NotificationManager)getSystemService(NOTIFICATION_SERVICE);

Notificationnf=newNotification(R.drawable.icon,"这是信息的详

细描述",null,"信息的标题",null);manager.notify(0,nf);

HelloTwoDService.this.stopSelf();}

},5000);这里创建一个Notification的实例nf,构造函数的第一个参数是那个显示在状态栏

(也就是Android手机上面的那一条显示信号强度、电池电量等信息的位置)的图标。后面可以有

一个标题和点击以后的详细信息,这是字串形式,还可以有一个Intent用来表示点击后可以发生一个跳转行为。

URL:http://www.sf.org.cn/Android/lumen/20982.htmlAndroid学习笔记(8)—GridView与ImageView

简单一点吧,就瞧那个Grid的效果,Android提供了一个GridView,不过从Epidemic中看来,它似乎与PC上的GRID差别还是挺大的,更像那个IconView的感觉。

不知道Android中如何实现表格界面?虽然在移动终端上,表格一般不会有谁使用,大家似乎更倾向于使用ListView,而Android对于ListView则有更简单的实现List

Activity。废话不说,还是自己写几句代码来实验一下。


android:layout_height="fill_parent"android:padding="10dip"

android:verticalSpacing="10"

android:horizontalSpacing="10"android:numColumns="auto_fit"

android:columnWidth="60"android:stretchMode="columnWidth"

android:gravity="center"/>

从描述文件中的这些属性来看,与表格非常类似,除了padding和spacing以外,它还多了那个gravity,这里是center表示单元格中的内容居中放,在类GridView中

也提供了方法setGravity(int)来实现这个效果。接着,我们沿用以前那个fillMaps方法来构造SimpleAdapter,以前将这个adapter

赋给ListActivity,现在同样的Adapter,却是赋给了GridView,效果又会是怎样呢?List>items=fillMaps();

GridViewgrd=(GridView)this.findViewById(R.id.grid);SimpleAdapteradapter=newSimpleAdapter(this,items,R.layout.list_row,newS

tring[]{"name"},newint[]{R.id.item});grd.setAdapter(adapter);

我觉得GridView并不象表格,倒更象IconView,下面试试用图像作为GridView的内容。现在,不能用简单Adapter了,得自己弄一个ImageAdapter,就让它衍生于

BaseAdapter类吧。publicclassImageAdapterextendsBaseAdapter{

//这是资源ID的数组privateInteger[]mThumbIds={

R.drawable.a,R.drawable.b,R.drawable.c,R.drawable.d,R.drawable.e,R.drawable.f,

R.drawable.g,R.drawable.h,R.drawable.i};

publicImageAdapter(Contextc){mContext=c;

}publicintgetCount(){

returnmThumbIds.length;}

publicObjectgetItem(intposition){returnposition;

}publiclonggetItemId(intposition){

returnposition;}

publicViewgetView(intposition,ViewconvertView,ViewGroupparent){

ImageViewi=newImageView(mContext);//设置图像源于资源ID。

i.setImageResource(mThumbIds[position]);i.setAdjustViewBounds(true);

i.setBackground(android.R.drawable.picture_frame);returni;

}privateContextmContext;

}很简单,只要重载几个方法就可以了,关键是那个getView方法,它负责构建出每

个单元格中的对象实例。这里我们构造的是一个ImageView实例。然后就是同样的将这个Adapter赋给GridView即可,大家可以看看效果,注意在

做这个例子前,先放几个小图片到res/drawable目录下,buildproject一下就可以得到那个R.drawable.a了(这里的a是图像文件名,如a.png)。

在getView方法中我们使用了ImageView类,这又是一个widget。除了上面用到的几个方法以外,还有以下几个方法值得注意:

与图像来源有关的方法,我们只用了资源文件的方式。//不同的图像来源

publicvoidsetImageBitmap(Bitmapbm)publicvoidsetImageDrawable(Drawabledrawable)

publicvoidsetImageResource(intresid)publicvoidsetImageURI(ContentURIuri)

图像效果的操作。//颜色过滤

publicvoidsetColorFilter(intcolor,Modemode)//矩阵变换

publicvoidsetImageMatrix(Matrixmatrix)//透明度

publicvoidsetAlpha(intalpha)具体的使用可以参考API,动手试一下就差不多了。

URL:http://www.sf.org.cn/Android/lumen/20983.htmlAndroid学习笔记(9)-开始做一个数独游戏[上]

不想再写Helo123了,今天开始做一个数独小游戏,因为这个游戏比较简单应该容易上手,就作为我学习Android之后的第一个程序比较合适。

初步的设计是只有一个界面(如下图),然后用绿色字体表示题目中有的固定的数字,黄色字体显示玩家输入的数字,而红色字体则是程序判断输入错误后的显示。另外模式分为三种:普通写入、标记(玩家用蓝色小方块标记当前单元格可以输入的数字)、排除模式(玩家指定数字,游戏自动判断一下这个数字肯定不能输入的单元格,将它反相显示出来)。

准备工作就是做一张背景图(棋盘)和三套数字小图片(红、绿、黄)即可。首先建立工程sudo,程序主体类?MainActivity以后,再修改一下那个main.xml?文件,去掉TextView标签即可。因为我们

会自己定义一个View,所以不再需要它了。程序不大,所以不打算做过多的类,下面把几个类的分工描述一下:?1、MainActivity,主体类,负责处理键盘事件和维护一个题库。?

2、MainView,显示类,负责维护并显示当前棋局,它的核心在于它的onDraw函数。?3、GridCel?和Question两个实体类,分别描述了棋盘单元格的信息和题目信息。?4、Helper类,助手类,封装一些函数,如读写记录、自动解题函数等。

在MainActivity中的onCreate中,加几句话就可以让游戏全屏显示了。如下:?

setTheme(android.R.style.Theme_Dark)??requestWindowFeature(Window.FEATURE_NO_TITLE)??

getWindow().setFlags(WindowManager.LayoutParams.NO_STATUS_BAR_FLAG,WindowManager.LayoutParams.NO_S?TATUS_BAR_FLAG)??

主要来看看MainView类的代码吧,它的onDraw负责显示当前棋局,涉及到的API主要是android.graphics中的Canvas?和Paint。

一是显示图片的方法,因为图片来源于资源,所以显示它的代码如:?

Bitmap?bmp?=?BitmapFactory.decodeResource(this.getResources(),R.drawable.grid)??canvas.drawBitmap(bmp,?0,?0,?nul)??

这是显示背景,如果是数字呢,如何将数字1与R.drawable.a1资源关联呢??

private?int[]?thumbNormal=new?int[]{0,?R.drawable.a1,R.drawable.a2,R.drawable.a3,R.drawable.a4,R.drawable.a5,?R.drawable.a6,R.drawable.a7,R.drawable.a8,R.drawable.a9?

}??然后就简单地加载即可了。?

Bitmap?b?=?BitmapFactory.decodeResource(this.getResources(),this.thumbNormal[this.grid[i].value])??canvas.drawBitmap(b,?xx,?yy,?nul)??

二是显示文本的方法,刚才显示图像的drawBitmap中最后一个参数直接给了nul,因为我们实在没有什么效果需要给图像的,但是文本则不同,我们用Paint来控制文本的样式。?

Paint?paintText=new?Paint()??paintText.setFlags(Paint.ANTI_ALIAS_FLAG)??paintText.setColor(Color.WHITE)??

..?..?canvas.drawText(Long.toString(this.ti.code),?xx,?yy,?paintText)??

三是画一下框的方法,同样是用Paint来做的。?

Paint?paintRect=?new?Paint()??paintRect.setColor(Color.RED)??paintRect.setStrokeWidth(2)??

paintRect.setStyle(Style.STROKE)??Rect?r=new?Rect()??

r.left=this.curColCELL_WIDTH+GRID_X??r.top=this.curRowCELL_WIDTH+GRID_Y??r.botom=r.top+CELL_WIDTH??

r.right=r.left+CELL_WIDTH??canvas.drawRect(r,?paintRect)??

如果不setStyle为Style.STROKE,则缺省为填充模式。

四是反相显示的方法,更简单了,就是一句话了:?

Paint?paintHint=new?Paint()??paintHint.setXfermode(new?PixelXorXfermode(Color.WHITE))??URL:http://www.sf.org.cn/Android/lumen/20984.html

Android学习笔记(10)-开始做一个数独游戏[中]继续,今天讨论的是记录文件的读写。因为原来在Brew平台上实现的数独将题库是

一个二进制文件,所以在Android就直接拿那个文件来用了。计划实现两个函数,先是LoadTiList(),加载题库,先装题库文件放在资源里,然

后从资源里加载它作为一个DataInputStream即可。代码也没几行,如下:publicstaticbooleanLoadTiList(MainActivityme){

DataInputStreamin=null;try

{in=newDataInputStream(me.getResources().openRawResource(R.ra

w.ti));

byte[]bufC4=newbyte[4];byte[]bufC81=newbyte[81];

//总个数in.read(bufC4,0,4);

intlen=((int)bufC4[3]<<24)+((int)bufC4[2]<<16)+((int)bufC4[1]<<8)+(int)bufC4[0];

for(inti=0;i
{Questionti=newQuestion();

//代码in.read(bufC4,0,4);

ti.code=(long)(((long)bufC4[3]<<24)+((long)bufC4[2]<<16)+((long)bufC4[1]<<8)+(long)bufC4[0]);

//时间in.read(bufC4,0,4);

SharedPreferencessp=me.getPreferences(Context.MODE_WORLD_READABLE);

ti.time=sp.getLong(Long.toString(ti.code),0);//数据

in.read(bufC81,0,81);for(intj=0;j<81;j++)ti.data[j]=bufC81[j];

me.tiList.add(ti);}

in.close();}

catch(Exceptionex){returnfalse;

}finally{

try{in.close();}catch(Exceptione){}

}returntrue;

}这里最麻烦的是因为java里没有unsigned类型,所以会溢出,比较郁闷,这个没

有解决,只能是生成题库文件里注意一下了,不能与brew平台共用那个题库文件了。二是保存记录,在brew平台我直接用一个文件搞定,读写它,但是android不能这

样了,因为ti.dat是从资源中加载的,所以只能是静态的,不可修改,那记录只能放入preferences中了,代码如下:

publicstaticbooleanSaveTiList(MainActivityme){

try{

SharedPreferencessp=me.getPreferences(Context.MODE_WORLD_WRITEABLE);

Questionti=me.gridView.ti;sp.edit().putLong(Long.toString(ti.code),ti.time);

sp.edit().commit();}

catch(Exceptionex){returnfalse;

}returntrue;

}SharePreferences可以按key-value来保存记录,所以key用题目的code,则value

就是解它所用的时间了。Android不能直接访问app目录下的文件,所以不能够象brew那样将数据文件放在程

序目录下供它读写,而在Activity中提供的两个函数openFileOutput和openFileInput,虽可用来读写文件,但是总是不太方便。

另外,用SQLite其实也不方便,因为手机中弄这些东西,事实上用处不大。URL:http://www.sf.org.cn/Android/lumen/20985.html

Android学习笔记(11)-开始做一个数独游戏[下]继续,最后再讨论一下定时器的实现。

本来很简单的一件事,直接用java.util.timer应该就够用了,但是发现在它的task中无法去invalidate我们的MainView,很郁闷。这一点的处理说明Android还是相对线

程安全的。折腾良久,明白了非得再做一个Handler,才能在线程中操作界面元素。所以,代码

比brew复杂了一点。先还是用Timer和TimerTask来做,如下:

publicTimerHandlertimerHandler;publicTimertimer;

publicMyTimerTasktask;......

timer=newTimer(true);task=newMyTimerTask(this);

......那个MyTimerTask是MainActivity的一个内嵌类,实现如下:

privateclassMyTimerTaskextendsTimerTask{

privateMainActivityme;privateinta=0;

publicMyTimerTask(MainActivityp){me=p;

}publicvoidrun(){

me.gridView.time++;Log.d("MyTask",Integer.toString(me.gridView.time));

timerHandler.sendEmptyMessage(0);}

}这里做两件事,一是将gridView中的time加一,二是发送一个消息通知

timerHandler。原来我在这里直接让MainView去刷新屏幕,发现不行,所以就改成这样处理了。

然后就是如何实现TimerHandler类的,也不复杂,就是让它去刷新一下屏幕即可。publicclassTimerHandlerextendsHandler{

privateMainViewme;publicTimerHandler(MainViewm){

me=m;}

@OverridepublicvoidhandleMessage(Messagemsg){

Log.d("Ti",msg.toString());me.invalidate();

}}

如此一来,就顺了。在MainView中的onDraw,根据当前的time值显示成00:00:00的格式即可。

另外,发现Android的模拟器运算速度不如BREW的模拟器,相当的慢。URL:http://www.sf.org.cn/Android/lumen/20986.html

Android学习笔记(12)-开始做一个数独游戏[补充]再补充一点吧,如果需要给游戏加上背景音乐,其实也是非常容易的事情。因为

Android提供了一个MediaPlayer类可以方便的播放音乐文件。android.media.MediaPlayer类没有构造函数,一般是用它的静态方法create生成

实例,简单地告诉它音乐文件的资源ID即可(支持mp3/wav/midi等)。首先,我们得建立一个Service,就叫MediaService吧,它的代码如下:

Android/UploadFiles_8448/200804/20080419152842539.gif"align=top>Android/UploadFiles_8448/200804/20080419152843671.gif"

align=top>publicclassMediaServiceextendsServiceimplementsMediaPlayer.OnErrorListener,MediaPlayer.OnCompletionListener,MediaPlayer.OnPreparedListen

er{

......

privateMediaPlayerplayer;

@OverrideprotectedvoidonDestroy(){

//TODOAuto-generatedmethodstubsuper.onDestroy();

if(player!=null){player.stop();

player.release();}

}@Override

protectedvoidonStart(intstartId,Bundlearguments){//TODOAuto-generatedmethodstub

Log.d("Media","onStart");

player=MediaPlayer.create(this.getApplication(),R.raw.tonhua);if(player!=null){

player.setAudioStreamType(AudioSystem.STREAM_MUSIC);

player.setOnCompletionListener(this);player.setOnPreparedListener(this);

player.setOnErrorListener(this);

player.prepareAsync();}

}@Override

publicvoidonCompletion(MediaPlayerarg0){//TODOAuto-generatedmethodstub

Log.d("Media","finished.");}

@OverridepublicvoidonPrepared(MediaPlayerarg0){

//TODOAuto-generatedmethodstubLog.d("Media","prepared.");

player.start();}

@OverridepublicvoidonError(MediaPlayerarg0,intwhat,intextra){

Log.d("Media","onError");player.stop();

}}

这个服务主要就是用一个MediaPlayer去播放资源中的tonghua(一首MP3音乐)。次序一般是先create出这个实例,然后prepare一下(如果是文件直接prepare,如果

是流则最好异步parepareAsync),接着就可以start了,同步可以直接start,异步则必须放到onPrepared中再start。

在MainActivity中启动这个服务即可。mediaServiceIntent=newIntent();

mediaServiceIntent.setClass(this,MediaService.class);

this.startService(mediaServiceIntent,newBundle());当前,在Activity停止时也别忘了将这个Service停掉,而在Service停止时关掉

MediaPlayer。在模拟器上试了,效果不是太好,声音有点断续,不知道是不是我的解码器的

问题(Vista系统)。URL:http://www.sf.org.cn/Android/lumen/20987.html

消息机制,异步和多线程有了framework后,我们不用面对赤裸裸的OSAPI,做一些重复而繁杂的事情。但

天下没有免费的午餐,我们还是需要学会高效正确的使用不同的framework,很多处理某一特定问题的手法在不同的framework中,用起来都会有所不同的。

在Android中,下层是Linux的核,但上层的java做的framework把这一切封装的密不透风。以消息处理为例,在MFC中,我们可以用PreTranslateMessage等东东自由

处理消息,在C#中,AndersHejlsberg老大说了,他为我们通向底层开了一扇“救生窗”,但很遗憾,在Android中,这扇窗户也被关闭了(至少我现在没发现...)。

在Android中,你想处理一些消息(比如:Keydown之类的...),你必须寻找Activity

为你提供的一些重载函数(比如onKeyDown之类的...)或者是各式各样的listener(比如OnKeyDownListner之类的...)。这样做的好处是显而易见的,越多的自由就会有越

多的危险和越多的晦涩,条框画好了,用起来省心看起来省脑,这是一个设计良好的framework应该提供的享受。对于我目前的工程而言,我没有什么BT的需求在当前API

下做不到的,google的设计ms还是很nice的。但世界是残酷的,有的时候我们还是必须有机制提供消息的分发和处理的,因为有

的工作是不能通过直接调用来同步处理的,同时也不能通过Activity中内嵌的消息分发和接口设定来做到,比如说事件的定时触法,异步的循环事件的处理,高耗时的工作等

等。在Android中,它提供了一些蛮有意思的方式来做这件事情(不好意思,我见不多识不广,我没见过类似玩法,有见过的提个醒&&嘴下超生^_^),它有一个

android.os.Handler的类,这个类接受一个Looper参数,顾名思义,这是一个封装过的,表征消息循环的类。默认情况下,Handler接受的是当前线程下的消息循环实例,也就是

说一个消息循环可以被当前线程中的多个对象来分发,来处理(在UI线程中,系统已经有一个Activity来处理了,你可以再起若干个Handler来处理...)。在实例化一个

handlerInstance之后,你可以通过sendMessage等消息发送机制来发送消息,通过重载handleMessage等函数来分发消息。但是!该handlerInstance能够接受到的消息,只有

通过handlerInstance.obtainMessage构造出来的消息(这种说法是不确切的,你也可以手动new一个Message,然后配置成该handlerInstance可以处理的,我没有跟进去分

析其识别机制,有兴趣的自己玩吧^_^)。也就是说A,B,C,D都可以来处理同一线程内的消息分发,但各自都只能处理属于自己的那一份消息,这抹杀了B想偷偷进入A领

地,越俎代庖做一些非份之事的可能(从理论上看,B还是有可能把消息伪装的和A他们家的一样,我没有尝试挑战一下google的智商,有BT需求的自行研究^_^)。这样做,

不但兼顾了灵活性,也确保了安全性,用起来也会简单,我的地盘我做主,不用当心伤及无辜,左拥右抱是一件很开心的事情。。。

很显然,消息发送者不局限于自己线程,否者只能做一些定时,延时之类的事情,岂不十分无趣。在实例化Handler的时候,Looper可以是任意线程的,只要有Handler

的指针,任何线程也都可以sendMessage(这种构造方式也很有意思,你可以在A线程里面传B线程的Looper来构造Handler,也可以在B线程里构造,这给内存管理的方法带

来很大的变数...)。但有条规则肯定是不能破坏的,就是非UI线程,是不能触碰UI类的。在不同平台上有很多解决方式(如果你有多的不能再多的兴趣,可以看一下很久很

久以前我写的一个,不SB不要钱)。我特意好好跟了一下android中的AsyncQueryHandler类,来了解google官方的解决方案。

AsyncQueryHandler是Handler的子类,文档上说,如果处理ContentProvider相关的内容,不用需要自行定义一套东西,而可以简单的使用async方式。我想指代的就应

该是AsyncQueryHandler类。该类是一个典型的模板类,为ContentProvider的增删改查提供了很好的接口,提供了一个解决架构,final了一些方法,置空了一些方法。通过

派生,实例化一些方法(不是每个对ContentProvider的处理多需要全部做增删改查,我想这也是该类默认置空一些方法而不是抽象一些方法的原因),来达到这个目的。在

内部,该类隐藏了多线程处理的细节,当你使用时,你会感觉异常便利。以query为例,你可以这么来用:

//定义一个handler,采用的是匿名类的方式,只处理query,因此只重写了onQueryComplete函数:

queryHandler=newAsyncQueryHandler(this.getContentResolver()){//传入的是一个ContentResolver实例,所以必须在OnCreate后实例化该Handler

类@Override

protectedvoidonQueryComplete(inttoken,Objectcookie,Cursorcursor){

//在这里你可以获得一个cursor和你传入的附加的token和cookie。//该方法在当前线程下(如果传入的是默认的Looper话),可以自由设定

UI信息}

};//调用时只需要调用startQuery(inttoken,Objectcookie,ContentURIuri,

String[]projection,Stringselection,String[]selectionArgs,StringsortOrder)函数即可:

queryHandler.startQuery(token,cookie,uri,projection,selection,selectionArgs,sortBy);

可见,该类的使用是多么简单(其实现可不会很容易,因为我尝试做了一次造车轮的工作_),比直接用Handler简单无数倍。但让我倍感孤独的是,不知道是没人做异

步的ContentProvider访问,还是这个类使用太过于弱智(这个使用方法可是我摸索了半天的啊,难道我真的如此的弱@_@),抑或是大家都各有高招,从SDK到网上,没有任

何关于该类的有点用的说明。而我又恰巧悲伤的发现,这个类其实有很多的问题,比如他吃掉异常,有错误时只是简单的返回null指针(这个其实不能怪他,你可以看这

里...);当你传一个null的ContentResolver进去的时候,没有任何异常,只是莫名其妙的丢弃所有消息,使你陷入苦苦的等待而不知其因;更愤慨的是,他的token传递

竟然有Bug(难道还是我使用不对&_&),从startXX传入的token,到了onXXComplete里面一律变成1,而文档上明明写着两个是一个东西(我的解决方法是用cookie做token,

这个不会丢_)。不过我暂时还没有遗弃它的打算,虽然没人理睬,虽然有一堆问题,虽然我按图索骥造了个新轮子,但为了节省剩下的一些无聊的工作,我决定苟且偷生

了。。。还是习惯性跑题了,其实,我是想通过我对这个类的无数次Debugger跟进,说说它

的多线程异步处理的解决策略的。他的基本策略如下:1.当你实例化一个AsyncQueryHandler类时(包括其子类...),它会单件构造一

个线程(后面会详述...),这个线程里面会构建一个消息循环。2.获得该消息循环的指针,用它做参数实例化另一个Handler类,该类为内部类。

至此,就有了两个线程,各自有一个Handler来处理消息。3.当调用onXXX的时候,在XXX函数内部会将请求封装成一个内部的参数类,将其

作为消息的参数,将此消息发送至另一个线程。4.在该线程的Handler中,接受该消息,并分析传入的参数,用初始化时传入的

ContentResolver进行XXX操作,并返回Cursor或其他返回值。5.构造一个消息,将上述返回值以及其他相关内容绑定在该消息上,发送回主线程。

6.主线程默认的AsyncQueryHandler类的handleMessage方法(可自定义,但由于都是内部类,基本没有意义...)会分析该消息,并转发给对应的onXXXComplete方法。

7.用户重写的onXXXComplete方法开始工作。这就是它偷摸做过的事情,基本还是很好理解的。我唯一好奇的是它的线程管

理方式,我猜测他是用的单件模式。第一个AsyncQueryHandler的实例化会导致创建一个线程,从此该线程成为不死老处男,所有的ContentResolver相关的工作,都由该线

程统一完成。个人觉得这种解决方式很赞。本来这个线程的生命周期就很难估量,并且,当你有一个ContentProvider的请求的时候,判断你会做更多的类似操作并不过分。就

算错了,花费的也只是一个不死的线程(与进程同生死共存亡...),换来的却是简单的生命周期管理和无数次线程生死开销的节约。同时另外一个很重要的问题,他并会涉及

到单件中数据同步的问题,每个类都有各自的Handler类,彼此互不干扰,分发可以分别进行。当多个数据请求的时候,在同一个ContentResolver上进行的可能微乎其微,

这就避免了堵塞。总而言之,这套解决办法和Android的整体设计算是天作之合了。所以建议,如果你有什么非ContentProvider操作,却需要异步多线程执行的话,模拟

一套,是个不错的策略,当然,具体情况具体分析,生搬硬套是学不好马列主义的。。。URL:http://www.sf.org.cn/Android/lumen/21075.html

显示控件使用Android的界面显示同样也是基于控件的。通常是用View(包括ViewGroup)控件配

上XML的样式来做的。具体细节不想说了,可以参考Samples里的ApiDemos/View,和View的Doc,以及ImplementingaUI这篇Doc。其他还有很多,感觉算是SDK讲述的最

多的内容。从控件的使用上,和网页的设计类似,尽量用parent_width之类的抽象长度,用Theme

来做风格,抽取所有的字串等信息做本地化设计。相关内容参看ImplementingaUI就好。

一类比较重要的是数据绑定控件。如果做过ASP.Net会从中看到很多类似的地方。一个支持数据绑定的控件,比如ListView。可以通过一个ListAdapter绑定到一个数

据源上。ListAdapter是一个抽象类,主要的实现类包括SimpleAdapter和SimpleCursorAdapter。前者是绑定一个静态的Array,后者是绑定一个动态的Cursor。

Cursor前面说过,是一个指向数据源的随机迭代器,将View绑定到Cursor通常要设置这样几个参数。一个是每一行的样式,称作RowLayout,其实就是一个普通的Layout的

XML文件。还有就是一个列和现实控件的对应关系。那个控件显示哪个列的值,这是需要配置的。为了定制一个良好的数据显示控件,最简单你可以定制很PP的RowLayout,复

杂一点就是可以重载绑定控件View,或者是适配器ListAdapter。如果是一个数据显示密集的应用,且你对UI有些追求,这个工作估计是必不可少的。

一个主要用于显示数据内容的Activity,可以选择派生自ListActivity。它提供了一个具有ListView的Layout,还有simple_list_item_1,simple_list_item_2,

two_line_list_item等默认的RowLayout,还有一些比较不错的API,和可供响应选择Item的事件。可以满足你比较基础的需求。如果你觉得只有一个ListView的界面太突

兀,你可以为这个ListActivity指定一个Layout,需要注意的是,你需要提供一个id为@android:id/list的ListView控件,避免Activity在内部偷偷寻找该控件的时候失

败。除了这些要求,做好UI还有注意易用性和效率。快捷键是一个比较不错的选择,在

Activity中调用setDefaultkeyMode(SHORTCUT_DEFAULT_KEYS),可以开启快捷键模式,然后你可以将菜单绑定到指定快捷键上就OK了。个人觉得Tip也是一个比较重要的东西,

但目前观察看来,这个东西只能够自己提供了。界面的动态性有时候是不可避免的,比如说菜单就是一个需要经常根据光标位置提供不同的选项。这个东西Android很人道的考

虑到了,你可以参看NodeList这个Sample。它采取的应该是一个静态模拟动态的方式,这样有助于提高速度。你也可以利用ViewInflate,动态从一个XML创建一个控件。成本

据Doc说很大,不到万不得已不要使用。URL:http://www.sf.org.cn/Android/lumen/21073.html

Intent消息传递在前面写Android的ContentProvider时候,可以看到那是基于观察者模式的一个消

息传递方法。每一个Cursor、ContentResolver做为一个小的注册中心,相关观察者可以在这个中心注册,更新消息由注册中心分发给各个观察者。而在MFC或Winform中,

都会形成一个消息网,让消息在网中流动,被各节点使用、吃掉或者在出口死掉。相比之下,我个人觉得基于Intent的Android核心消息传递机制是有所不同的。它

应该会有一个全局性的注册中心,这个注册中心是隐性的,整个Android系统中就那么一个。所有的消息接收者,都被隐形的注册到这个中心。包括Activity,Service和

IntentReceiver。其实说隐形注册是不确切的,所有注册都还是我们手动告诉注册中心的,只是与传统的方式不一样,我们通常不是通过代码,而是通过配置文件来做。在应

用的Manifest中,我们会为一些Activity或Service添加上Intent-filter,或在配置文件中添加项。这其实就相当于向系统的注册中心,注册了相关

的Intent-filter和receiver(这个事情完全可以通过代码来做,只是这样就失去了修改的灵活性)。

当程序有一个消息希望发出去的时候,它需要将消息封装成一个Intent,并发送。这时候,应该是有一个统一的中心(恩,有可能Android底层实现的时候不是,但简单

这样看是没问题的...)接受到这个消息,并对它进行解析、判定消息类型(这个步骤降低了耦合...),然后检查注册了相匹配的filter或receiver,并创建或唤醒接收者,

将消息分发给它。这样做有很多好处。虽然这种传递有的时候不如点对点的传递快(这有些需要速度的地方,我们看到Android会通过直接通信来做),但有时候又因为它只

经过一跳(姑且这么叫吧...),比复杂的流动又要更快。更重要的是,它耦合性低,在手机平台这种程序组件多变的条件下使用十分适合。并且它可以很容易实现消息的精确

或模糊匹配,弹性很大。(我个人曾想在开发一个C++二次平台的时候引入这样的机制,但在C++中,建立一套完整的数据marshal机制不容易,相比之下,用java来做会简单

很多...)恩,废话说了很多,具体讲讲Android中Intent的使用。当你有一个消息需要传递,如

果你明确知道你需要哪个Activity或者其他Class来响应的话,你可以指定这个类来接受该消息,这被称为显性发送。你需要将Intent的class属性设置成目标。这种情况很

常见,比如startActivity的时候,会清楚当前Activity完了应该是哪个Activity,那就明确的发送这个消息。

但是,有的时候你并不确定你的消息是需要具体哪个类来执行,而只是知道接收者该符合哪些条件。比如你只需要有一个接收者能显示用户所选的数据,而不想制定某个

具体的方法,这时候你就需要用到隐形发送(传统上,我们可能会考虑用多态,但显然这种方式更为灵活...)。在Android中,你可以为Intent指定一个action,表示你这

个指令需要处理的事情。系统为我们定义了很多Action类型,这些类型使系统与我们通信的语言(比如在Activity里面加一个Main的filter,该activity就会做成该应用的

入口点),当然你也可以用于你自己的应用之间的通信(同样当然,也可以自定义...)。强烈建议,在自己程序接收或发出一个系统action的时候,要名副其实。比如你响应一

个view动作,做的确实edit的勾当,你发送一个pick消息,其实你想让别人做edit的事,这样都会造成混乱。当然只有Action有时候是不够的,在Android中我们还可以

指定catalog信息和type/data信息,比如所有的显示数据的Activity,可能都会响应Viewaction。但很多与我们需要显示的数据类型不一样,可以加一个type信息,明确

的指出我们需要显示的数据类型,甚至还可以加上一个catalog信息,指明只有你只有按的是“中键”并发出这样的消息才响应。

从上面可以看出,Android的Intent可以添加上class,action,data/type,catalog等消息,注册中心会根据这些信息帮你找到符合的接收者。其中class是点对点的指示,

一旦指明,其他信息都被忽略。Intent中还可以添加key/value的数据,发送方和接收方需要保持统一的key信息和value类型信息,这种数据的marshal在java里做,是不

费什么力气的。Android的Intent发送,可以分成单播和广播两种。广播的接收者是所有注册了的

符合条件的IntentReceiver。在单播的情况下,即使有很多符合条件的接收者,也只要有一个出来处理这个消息就好(恩,个人看法,没找到确切条款或抉择的算法,本来想

实验一下,没来得及...),这样的情况很容易理解,当你需要修改某个数据的时候,你肯定不会希望有十个编辑器轮流让你来处理。当广播不是这样,一个receiver没有办法

阻止其他receiver进行对广播事件的处理。这种情况也很容易理解,比如时钟改变了,闹钟、备忘录等很多程序都需要分别进行处理。在自己的程序的使用中,应该分清楚区

别,合理的使用。URL:http://www.sf.org.cn/Android/lumen/21072.html

ContentProvider数据模型概述Android的数据(包括files,database等...)都是属于应用程序自身,其他程序

无法直接进行操作。因此,为了使其他程序能够操作数据,在Android中,可以通过做成ContentProvider提供数据操作的接口。其实对本应用而言,也可以将底层数据封装成

ContentProvider,这样可以有效的屏蔽底层操作的细节,并且是程序保持良好的扩展性和开放性。

ContentProvider,顾名思义,就是数据内容的供应者。在Android中它是一个数据源,屏蔽了具体底层数据源的细节,在ContentProvider内部你可以用Android支持的

任何手段进行数据的存储和操作,可能比较常用的方式是基于Android的SQLite数据库(恩,文档中和示例代码都是以此为例)。无论如何,ContentProvider是一个重要的数

据源,可以预见无论是使用和定制ContentProvider都会很多。于是花了点时间仔细看了看。

数据库操作从我目前掌握的知识来看,SQLite比较轻量(没有存储过程之类的繁杂手段),用

起来也比较简单。实例化一个SQLiteDatabase类对象,通过它的APIs可以搞定大部分的操作。从sample中看,Android中对db的使用有一种比较简单的模式,即派生一个

ContentProviderDatabaseHelper类来进行SQLiteDatabase对象实例的获取工作。基本上,ContentProviderDatabaseHelper类扮演了一个singleton的角色,提供单一的实

例化入口点,并屏蔽了数据库创建、打开升级等细节。在ContentProvider中只需要调用ContentProviderDatabaseHelper的openDatabase方法获取SQLiteDatabase的实例

就好,而不需要进行数据库状态的判断。URI

像进行数据库操作需要用SQL一样,对ContentProivder进行增删改查等操作都是通过一种特定模式的URI来进行的(ig:content://provider/item/id),URI的能力

与URL类似,具体细节可以查看SDK。建立自己的ContentProvider,只需要派生ContentProivder类并实现insert,delete,update等抽象函数即可。在这些接口中比

较特殊的是getType(uri)。根据传入的uri,该方法按照MIME格式返回一个字符串(==!没听过的诡异格式...)唯一标识该uri的类型。所谓uri的类型,就是描述这个uri所

进行的操作的种类,比如content://xx/a与content://xx/a/1不是一个类型(前者是多值操作,后者是单值),但content://xx/a/1和content://xx/a/2就会是一个类型

(只是id号不同而已)。在ContentProvider通常都会实例化一个ContentURIPraser来辅助解析和操作传入

的URI。你需要事先(在static域内)为该ContentURIPraser建立一个uri的语法树,之后就可以简单调用ContentURIPraser类的相关方法进行uri类型判断(match方法),

获取加载在uri中的参数等操作。但我看来,这只是在使用上简化了相关操作(不然就需要自己做人肉解析了...),但并没有改变类型判定的模式。你依然需要用

switch...case...对uri的类型进行判断,并进行相关后续的操作。从模式来看,这样无疑是具有强烈的坏味道,类似的switch...case...代码要出现N此,每次一个

ContentProvider做uri类型的增减都会需要遍历修改每一个switch...case...,当然,如果你使用模式(策略模式...)进行改造对手机程序来说无疑是崩溃似的(类型膨胀,

效率降低...),所以,只能是忍一忍了(恩,还好不会扩散到别的类中,维护性上不会有杀人性的麻烦...)。

增删改查ContentProvider和所有数据源一样,向外提供增删改查操作接口,这些都是基于

uri的指令。进行insert操作的时候,你需要传入一个uri和ContentValues。uri的作用基本就限于指明增减条目的类型(从数据库层面来看就是table名),ContentValues

是一个key/value表的封装,提供方便的API进行插入数据类型和数据值的设置和获取。在数据库层面上来看,这应该是columnname与value的对应。但为了屏蔽

ContentProvider用户涉及到具体数据库的细节,在Android的示例中,用了一个小小的模式。它为每一个表建一个基于BaseColumn类的派生类(其实完全可以不派生自

BaseColumn,特别当你的表不基于默认的自动id做主键的时候),这个类通常包括一个描述该表的ContentURI对象和形如publicstaticfinalTITLE="title"这样的column

到类数据的对应。从改变上角度来看,你可以修改column的名字而不需要更改用户上层代码,增加了灵活性。insert方法如果成功会返回一个uri,该uri会在原有的uri基

础上增加有一个rowid。对于为什么使用rowid而不是keyid我想破了脑袋。到最后,我发现我傻了,因为ContentProvider不一定需要使用数据库,使用数据库对应的表也

可以没有主键,只有rowid,才能在任何底层介质下做索引标识。但,基于rowid在删除和修改操作是会造成一定的混乱。删除和修改操作类似。删

除操作需要传入一个uri,一个where字串,一组where的参数(做条件判定...),而修改操作会多一个ContentValues做更新值。着两个操作的uri都支持在末尾添加一个

rowid。于是混乱就出现了。当在where参数中指明了keyid,而在uri中提供了rowid,并且rowid和keyid所指函数不一致的时候,你听谁的?示例代码中的做法是完全无

视rowid(无语...),如此野蛮的方式我估计也只能在示例中出现,在实际中该如何用,恩,我也不知道。幸运的是,我看了下上层对ContentProvider的删除操作,其实都不

会直接进行,而是通过调用Cursor的delete方法进行,在这前提下,我想Cursor会处理好这些东西吧。

最后一个操作是查询操作,可以想见,查询的参数是最多的,包括uri和一组条件参数。条件参数类型和标准的sql类似,包括sort,projection之类的。从这些参数

到sql语句的生成,可以寻求QueryBuilder类的帮助,它提供了一组操作接口,简化了参数到sql的生成工作,哪怕你不懂sql都完全没有问题(这话说的我自己都觉得有点

悬...)。查询返回一个Cursor。Cursor是一个支持随机读写的指针,不仅如此,它还提供了方便的删除和修改的API,是上层对ContentProvider进行操作一个重要对象,需

要仔细掌握(Cursor还可以绑定到view上,直接送显,并与用户进行交互,真是程序越往上,封装越好,工作越机械没有复杂性了...)。

数据模型在与界面打交道的Cursor、ContentResolver等数据操作层中,大量采用观察者模

式建立数据层与显示层的联系。一个显示层的视图,可以做成某一种观察者注册到Cursor或ContentResolver等数据中间层中,在实现底层ContentProvider中,我们需要特别

注意在对数据进行修改操作(包括增删改...)后,调用相应类型的notify函数,帮助表层对象进行刷新(还有一种刷新方式是从一个view发起的)。可以看到Android的整

体数据显示框架有点像MVC的方式(贫瘠了...叫不出名)。Cursor、ContentResolver相当于控制层,数据层和显示层的交互通过控制层来掌管,而且控制层很稳定不需要特

别定制,通常工作只在定制数据层和显示层空间,还是比较方便和清晰的。一个设计问题

现在有个设计问题,比如我要扩充一个已有的ContentProvider(第三方提供),我是建立一个ContentProvider,只保留第三方ContentProvider的key信息,并为其添

加更多的信息,在表层维护这两个ContentProvider的联系好;还是建议一个ContentProvider,以第三方的ContentProvider做一部分底层数据源,像表层提供一个

ContentProvider好。前者无疑在实现上简单一些,如果第三方改变,灵活性也更好,只是需要仔细维护

表层的相关代码。后者实现上需要付出大量的苦力劳动,当表层使用会简单多了。我举棋不定,期待你的意见。。。

自定义ContentProvider的语义ContentProvider中,最重要的就是query操作。query根据输入返回一个符合条件

的Cursor。这就可能出现以下几种情况:1.查询成功,包含几个正确的结果;2.查询失败,没有符合的结果;3.输入错误,触发了某个异常;4.没能查询到结果,但无法

确定是输入错误还是查询失败。第一种情况是我们最需要的,当然是需要正确维系的,而最后一种情况在大部分应用中应该不会出现(但在我的应用中会的_#),而第二种第

三种是比较常见的。经过我的测试,系统的ContentProvider维持这样的语义:如果是情况2,返回正常

的Cursor,并且,其count为0,相当于emptycursor;如果是情况3,不抛出任何异常,返回null的Cursor。这样的话明明白白写出来是很好理解的,但由于没有官方的文

档说明,在自定义的时候经常会误用。比如在某些情况下,用null表征查询失败,用抛出异常来描述错误的输入。

返回emptycursor,如果是通过databasecursor自然会有db帮你维护,但是如果返回ArrayListCursor,MergeCursor或其他自定义的Cursor,就需要自己维系了。

ArrayListCursor可以通过newArrayListCursor(Columns,newArrayList(){})来提供。其中Columns一定不为null。MergeCursor不能以newMergeCursor(newCursor[]{})

来创建,而需要通过newMergeCursor(newCursor[]{aEmptyCursor,...}来维系(其实很好理解,我呆了...)。自定义的Cursor也一定要提供生成emptycursor的方式。

如果将ContentProvider作为一个单独的module来理解,不通过异常而是通过null来返回MS是有好处的。在module的出口吃掉所有异常,虽然不能提供足够的信息(异

常信息全部写入日志),但可能会使上层使用更简单。但在Android中,我并没有感觉到这一点。作为ContentProvider的上层函数,ListActivity.managedQuery、

ListView.setListAdapter等,根本不能处理一个null的Cursor,在ListView中这会触发一个异常。更无语的是,当你把一个nullCursor设置为manage的后。它不会立即

抛异常,而是在OnFreeze等生命周期函数的时候,因无法处理nullCursor而抛出一个异常。这使得你根本无法在当地catch该异常,换句话,ListActivity的manageCursor

根本是个无法使用的函数。你必须用getContext().query()获得Cursor,然后判定该Cursor是否null,在进行startManagingCursor进行绑定。这远不如直接用异常进行错

误路径的处理来的统一和方便。当然,有些东西我们是不能改变的,只能去适应。对于自定义的cursor,ContentProvider,

最重要的,是在无人造错误输入的情况下返回emptycursor,而不是null。至于使用null响应还是异常响应上,我个人觉得还是和系统同步为好,虽然别扭,但至少统一不容易

有歧义。此外,ContentProvider还有很多细致的语义。比如返回的Cursor需要绑定一个URI,

以便自动响应更新。自定义的更新需要支持deleteRow等操作语义等等。PS:而上层的ListView,更是陷阱重重。首先绑定到ListView的Cursor必须有_id

项,否则会有异常抛出。如果做过.net的开发,这一点是可以想到的,但是,这种问题应该在文档中写明。另外,在ListView中,如果你不绑定一个数据源,你一定不能在layout

中添加涉及内容的属性。比如android:height="wrap_content",这会在onMeasure的时候抛出异常。

献花(0)
+1
(本文系薇薇one图书...首藏)