前一篇文章把SFA的官方介绍翻译了一哈哈,自然要拿个简单的例子来研究一下Android程序开发和SFA的架构。
下面这个例子非常简单,完成的功能是:
从文本框中读入网址
向该网址发送访问请求并显示响应的信息
信息返回后向调用系统通知
图1 效果图
不扭捏的先贴出最后完工的代码(不包含其他配置部分)。
public class D_1_SFATestActivity extends Activity {
private Button btSfa;
private EditText etUrl;
private TextView tvInfo;
RestTemplate restTemplate = new RestTemplate();
@Override
protected void onCreate(Bundle savedInstanceState) {
super .onCreate(savedInstanceState);
setContentView(R.layout.d_function_1_sfa);
initLayoutView();
}
private void initLayoutView() {
etUrl = (EditText)findViewById(R.id.et_function_sfa);
btSfa = (Button)findViewById(R.id.bt_function_sfa);
btSfa.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
sfaGetMethod();
}
});
tvInfo = (TextView)findViewById(R.id.tv_function_sfa);
restTemplate.getMessageConverters().add(new StringHttpMessageConverter());
}
private void sfaGetMethod() {
String url = etUrl.getText().toString();
if (!url.startsWith( "http://" )){ //懒汉判断
url = "http://" +url;
}
new InternetLinkTask().execute(url);
}
class InternetLinkTask extends AsyncTask{
protected String doInBackground(String... params) {
String result = restTemplate.getForObject(params[0 ], String. class , "Android" );
return result;
}
@Override
protected void onPostExecute(String result) {
super .onPostExecute(result);
tvInfo.setText(result);
Intent intent = new Intent();
intent.setAction(LinkOkReceiver.BROADCAST_ACTION);
intent.putExtra(LinkOkReceiver.EXTRA_MSG, "请求已得到应答响应,请注意!" );
sendBroadcast(intent);
}
}
}
public class LinkOkReceiver extends BroadcastReceiver {
public static final String EXTRA_MSG = "msg" ;
public static final String BROADCAST_ACTION = "yunhe.doit.urlLink" ;
@Override
public void onReceive(Context context, Intent intent) {
String msg = intent.getStringExtra(EXTRA_MSG);
Toast.makeText(context, msg, Toast.LENGTH_SHORT).show();
makeNotify(context);
}
private void makeNotify(Context context) {
NotificationManager nManager = (NotificationManager) context
.getSystemService(Context.NOTIFICATION_SERVICE);
Notification notify = new Notification(R.drawable.ic_launcher, "" ,
System.currentTimeMillis());
notify.flags = Notification.FLAG_AUTO_CANCEL;
notify.defaults |= Notification.DEFAULT_SOUND;
String title = "通知栏标题" ;
String msgContent = "内容返回啦,可以查看啦" ;
Intent notifyIntent = new Intent(context, D_1_SFATestActivity. class );
PendingIntent contentIntent = PendingIntent.getActivity(context, 0 ,
notifyIntent, PendingIntent.FLAG_UPDATE_CURRENT);
notify.setLatestEventInfo(context, title, msgContent, contentIntent);
nManager.notify(1 , notify);
}
}
假定正在读本文的您跟我一样完全没有接触过Android软件开发的,那我们就一步一步的走下来,看看怎样才能比较快的入门吧。
1 需要一个Activity
按照我翻阅百度/谷歌的结果,一个普通的App应用程序,都是从Activity类开始的。Activity意思是“活动”,指的就是移动设备上的一屏,从应用开始使用到结束使用,与用户面对面交互的都是这一个一个的Activity,从MVC模型的来看,他就是这个C了。Activity这么重要,难怪用向导生成新的Android项目时,首选默认生成一个继承Activity的MainActivity类啦。
1.1 由onCreate(),看Activity的生命周期
我们的D_1_SFATestActivity 也是从继承Activity开始的,而且,第一个需要覆盖的方法就是onCreate(),顾名思义,oncreate方法就是Activity生命周期之始,即在其被第一次加载或者被destroy掉后背加载时最先调用。你在屏幕上希望展示应用的显示效果,就需要通过一个重写的方法来加载视图、初始化数据等。
我们回过头来看看Activity,Activity在系统内部是以Activity栈的形式被被管理的,一个新的Activity开始运行后,它就会被push到栈顶并被激活为运行状态,以前活动过的Activity则保存到栈中后一个位置,除非新的Activity退出,否则就不会被移至屏幕上。
一个Activity本质上具有
四种不同的状态 :
active(running) :Activity被置于屏幕上,即在栈顶位置
paused :如果一个Activity失去了用户焦点但在屏幕上仍然可见(比如被一个不是全屏的或者透明的Activity覆盖),那他的状态就是paused。一个paused状态的Activity是存活的(它仍然持有着自己的状态和信息,同时附着在窗口管理器window manager上),但是在可用内存极低的情况下有可能被系统杀掉killed并回收它的资源。
stoped :如果一个Activity完全被另一个Activity所覆盖,那它就stoped了。该状态下Activity仍持有着状态和信息,但因为它不可见了,所以系统可能会在任何需要内存资源的时候把它killed掉。
killed :顾名思义,已经死透了。资源被回收。
下面这种图是官方的一张生命周期描述图,可以参考一下
图1.1 Activity生命周期
从上面这张图,我们可以看出有必要在实际使用中需要注意的三个循环:
首先Activity的整个生命直环是从onCreate开始到onDestory为止的。Activity将在onCreate()方法中初始化所有必要的或全局性的数据及状态,而在OnDestroy()放将持有的所有资源释放。比如,如果您开启了一个线程用于在后台从网络中下载数据,那可以在onCreate()方法中执行,并在onDestory()中释放。
其次是从onStart()到onStop()之间的用户可见环。这个周期内Activity是用户可见的,尽管他有可能失去了用户的焦点,同时您也一直持有着可以在Activity上显示的数据或状态等资源。比如您可以在onStart()中注册一个BroadcastReceiver用于接收某些可能影响您Activity显示的事件变化,然后在不再需要显示当前的Activity时在onStop()方法中将BroadcastReceiver注销掉。onStop()和onStart()都可以被调用很多次,因为Activity可以显示或被覆盖很多次。
最后是焦点周期,这段时间用户是聚焦在这个Activity上的。Activity有可能频繁的在runing和paused状态之间切换。当设备屏幕提示休眠时、当某个计算结果完成被提醒时、当产生一次新的跳转动作时,因此在这这个环内的生命周期方法应该是轻量级的。
由上可见,一个完整的Activity生命周期所需要的方法就是这几个。它们将在状态变更时被调用,所以您可以在这些方法中实现您的逻辑业务以适应状态的变更。大部分的应用都改
复写onCreate() 方法用于初始化。建议大部分应用
复写onPause() 方法用于在提交状态变更。不过无论你复写哪种方法,一定要谨记调用父类的方法super.onXXXX()。
下面给出Activity关于生命周期的API清单:
public class Activity extends ApplicationContext {
protected void onCreate(Bundle savedInstanceState);
protected void onStart();
protected void onRestart();
protected void onResume();
protected void onPause();
protected void onStop();
protected void onDestroy();
}
注意,因为因为onStop()和onDestroy()方法被标记为killable,所以他们的状态是不稳定的。建议用onPause()来执行持久化的工作。
但Activity将被转到后台时,onSaveInstanceState(Bundle)将被调用。此方法允许您将一些动态的实例状态存入Bundle中,用于后面可以通过onCreate(Bundle)获得(如果该Activity能被re-created)。但注意持久化状态还是建议您在onPause()中保存,因为onSaveInstanceState(Bundle)并不是生命周期的一部分,他有在某些文档描述应该被调用情况下却没有被调用。
D_1_SFATestActivity.onCreate(Bundle savedInstanceState) {
super .onCreate(savedInstanceState);
setContentView(R.layout.d_function_1_sfa);
initLayoutView();
}
基于上述的原因和建议,我们的Activity就需要重写onCreate()。方法代码段的前两句可以理解成固定格式的。super.onCreate()做了什么留待后续源码分析的时候讲。
2 界面布局
Android开发的有一个分工非常明确的代码目录结构,如下
----src/
-----------java/package/code
---gen/ 根据资源自动生成的常量Int组类,方便代码中加载资源
< pre name = "code" class = "html" > ----------- < span style = "font-family:Arial, Helvetica, sans-serif;" > R.java span >
---res/-----------layout/ 包含应用布局文件-----------drawable/ 包含图片绘制的资源或工具-----------raw/ 包含一些可以被以字节流的形式使用的文件-----------values/ 配合布局文件使用的全局变量,如字符串等。----AndroidManifest.xml 应用环境数据,包括应用名、Activity、service、broadcastReceiver、注册的intent、许可条件等等
而setContentView就是加载/res/layout中指定的界面布局layout.d_function_1_sfa.xml。R.java文件会自动生成资源对应的一个唯一的int键,为我们开发带来便利。
Android插件提供了一个非常强大的图形化工具,常用的视图可以非常方便的拖拽到界面编辑器上并自动生成布局代码。
图1.2 界面编辑器
"1.0" encoding= "utf-8" ?>
"http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
android:id="@+id/bt_function_sfa"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/bt_function_sfa" >
android:id="@+id/et_function_sfa"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ems="10" >
android:id="@+id/scrollView1"
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
android:id="@+id/tv_function_sfa"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:scrollbars="vertical" />
上面就是布局文件/res/layout/d_function_1_sfa.xml的源码需要注意的是,在给这些视图定义android:id时最好根据自己项目的要求规范,可以很方便的查看使用。
一些酷炫的显示效果,则需要自己设计并实现视图。这在我看来是Android开发的第一个要点。
3 View初始化
接着就是初始化各个View视图
private void initLayoutView() {
etUrl = (EditText)findViewById(R.id.et_function_sfa);
btSfa = (Button)findViewById(R.id.bt_function_sfa);
btSfa.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
sfaGetMethod();
}
});
tvInfo = (TextView)findViewById(R.id.tv_function_sfa);
restTemplate.getMessageConverters().add(new StringHttpMessageConverter());
}
首先,我们注意到Activity已经提供了findViewById用于查找我们所需要的View视图,所有的UI视图(像按钮就、文本域等等)都是继承于基类View。VIew类代表了用户界面组件的基本构建快。一个View占用了屏幕上的一块矩形块区域,并提供绘制样式和事件处理的功能。他的子类android.view.ViewGroup是所有布局类视图的基类,所谓布局类视图就是指不可见的、可用于容纳多个其他View视图的视图容器。
setOnClickListener()方法是就是注册一个点击监听器,当用户点击该View区域时,Android就会调用回调方法。常用的几个监听器还包括:
public void setOnCreateContextMenuListener(OnCreateContextMenuListener l)
public void setOnFocusChangeListener(OnFocusChangeListener l)
public void setOnKeyListener(OnKeyListener l)
public void setOnLongClickListener(OnLongClickListener l)
public void setOnTouchListener(OnTouchListener l)
对于事件的监听策略,通常的做法是在同一类的监听实例化一个处理对象,而不是为每个事件监听方法都创建一个实例:
private OnClickListener listener = new OnClickListener() {
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.main_go_add:
break ;
case R.id.main_go_info:
break ;
case R.id.main_go_list:
break ;
case R.id.main_go_function:
break ;
default :
break ;
}
}
};
4 Android的异步处理 因为手持设备的特殊性,系统要求UI必须能第一时间响应用户的请求,而不允许在主线程上过多的占用处理时间。因此,对一些资源需求型的操作,Android在2.3以后强制要求开启线程去执行。如果在主线程中运行类似网络访问等操作,就会抛出android.os.NetworkOnMainThreadException异常。
线程处理有两种机制:handler机制和AsyncTask机制。
4.1 Handler线程处理机制
这是基本的处理方式,原理就是向消息队列发送消息。简单的应用形式如下:
public void onCreate(Bundle savedInstanceState) {
super .onCreate(savedInstanceState);
this .setContentView(R.layout.main_view);
new Thread(runnable).start();
}
Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
super .handleMessage(msg);
Log.i(TAG,"请求结果:" + msg.getData());
}
}
Runnable runnable = new Runnable(){
@Override
public void run() {
Message msg = new Message();
Bundle data = new Bundle();
data.putString("value" , "请求结果" );
msg.setData(data);
handler.sendMessage(msg);
}
}
4.2 AsyncTask机制
AsyncTask在底层维护了Executor线程池,能有效的管理线程的生命周期,将用户从线程管理和业务管理中解耦。AsyncTask
的三个泛型类型分别表示Params任务参数类型、Progress过程数据类型、Result结果类型。
它提供了3个主要方法。您需要特别注意,前两个方法是在用户线程执行的。而只有doInBackground()方法是线程帮忙的。
protected void onPreExecute()
protected void onPostExecute(Result result)
protected abstract Result doInBackground(Params... params)
还提供了两个用于发布进度的方法
protected final void publishProgress(Progress... values)
protected void onProgressUpdate(Progress... values)
4.3 两者比较
其实两者不是同一个维度的,Handler机制提供的是消息队列方式,而线程的生命周期还是用户维护。而AsyncTask就是把线程生命周期解耦出来,所以两者没啥好比较的。
5 Intent——组件之间的通讯
Intent是Activity、BroadcastReceiver、Service之间通讯的工具。通过android.content.Context.startActivity ()可以从一个Activity跳转到另一个Activity;通过android.content.Context.sendBroadcast()可以把广播发送有需要的BroadcastReceiver ;通过android.content.Context.startService()或android.content.Context.bindService()可以开启一个新的后台Service服务等。
最重要的是,它还提供携带各种数据的能力,是各个组件连接的纽带。
那我们这里就是通过Intent,在完成了网络连接任务后向BroadcastReceiver发送广播
Intent intent = new Intent();
intent.setAction(LinkOkReceiver.BROADCAST_ACTION);
intent.putExtra(LinkOkReceiver.EXTRA_MSG, "请求已得到应答响应,请注意!" );
sendBroadcast(intent);
6 BroadcastReceiver
BroadcastReceiver必须实现onReceive()方法用以接受广播和处理。
广播的重要用途在于它不仅可以在内部传播,也可以是跨应用的,为不同应用间的通讯带来便利。所以使用它需要在AndroidManifest.xml中注册。
"yunhe.doit.receiver.LinkOkReceiver" >
"yunhe.doit.urlLink" />
接收到请求后,广播做了信息显示和通知的处理。
Toast.makeText(context, msg, Toast.LENGTH_SHORT).show();
定义新的通知,并通过NotificationManager通知用户。
private void makeNotify(Context context) {
NotificationManager nManager = (NotificationManager) context
.getSystemService(Context.NOTIFICATION_SERVICE);
Notification notify = new Notification(R.drawable.ic_launcher, "" ,
System.currentTimeMillis());
notify.flags = Notification.FLAG_AUTO_CANCEL;
notify.defaults |= Notification.DEFAULT_SOUND;
String title = "通知栏标题" ;
String msgContent = "内容返回啦,可以查看啦" ;
Intent notifyIntent = new Intent(context, D_1_SFATestActivity. class );
PendingIntent contentIntent = PendingIntent.getActivity(context, 0 ,
notifyIntent, PendingIntent.FLAG_UPDATE_CURRENT);
notify.setLatestEventInfo(context, title, msgContent, contentIntent);
nManager.notify(1 , notify);
}
7 结语
这样,一个完整的例子就展示出来了。通过这个例子的实践, 我认为Android应用的设计要点主要有3个:
1.对View组件的设计和使用,UI很重要。
2.移动设备的特殊性以及使用习惯,决定了他必将成为用户日常生活中与其他设备、网络等通讯的中心,所以Android相关的通讯开发很重要,包括蓝牙、wifi、socket、httpclient等等。
3.调优,正是由于移动设备使用的敏感性,Android应用的响应速度就非常影响用户的体验,所以程序的调优必不可少。