前一篇文章把SFA的官方介绍翻译了一哈哈,自然要拿个简单的例子来研究一下Android程序开发和SFA的架构。
下面这个例子非常简单,完成的功能是:
- 从文本框中读入网址
- 向该网址发送访问请求并显示响应的信息
- 信息返回后向调用系统通知
图1 效果图
不扭捏的先贴出最后完工的代码(不包含其他配置部分)。
- /**
- * <strong>测试Spring for Android,简单的RestTemplate用法</strong>
- *
- * @author yoara
- */
- 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());
- }
- /**点击时访问指定URL**/
- private void sfaGetMethod() {
- String url = etUrl.getText().toString();
- if(!url.startsWith("http://")){ //懒汉判断
- url = "http://"+url;
- }
- new InternetLinkTask().execute(url);
- }
- /** 线程类,用于异步执行web访问任务 **/
- class InternetLinkTask extends AsyncTask<String,Void,String>{
- 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);
- //请求返回后,将消息传递给broadcast receiver,调用toast和通知两种方式显示
- 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 {
- /**
- 当Activity第一次加载或被destroy掉后加载时执行,
- 此方法中您可以创建view视图、绑定数据到list等。
- 该方法还会提供一个用于保存状态的Bundle对象。
- 下一个方法是onStart()
- **/
- protected void onCreate(Bundle savedInstanceState);
- /**
- onCreate()执行后执行。另外在Activity被交换到后台,
- onStop()方法已经被执行,但是Activity还没有被killed掉时,
- 用户在此查看该Activity时将会跳过onCreate()而执行onStart()
- 下一方法是onResume()。
- !!!API上指出下个方法有可能是onStop(),不能理解。!!!
- **/
- protected void onStart();
- /**
- 当Activity从stopped状态恢复过来时被调用
- 下一个状态是onStart()
- **/
- protected void onRestart();
- /**
- 当Activity被聚焦时调用,该方法被调用时Activity位于栈顶。
- 下一个方法是onPause()
- **/
- protected void onResume();
- /**
- 当系统准备跳往另一个Activity时被调用,
- 这个方法通常被用作持久化数据、暂停动画等CPU消耗型的操作。
- 这个方法的过程应该非常快,因为下一个Activity在这个方法返回前不会被恢复。
- 当前Activity没有stopped,且还会返回当前Activity时,调用下个方法是onResume()
- 如果不可见,则下一个调用方法是onStop()
- **/
- protected void onPause();
- /**
- 当Activity不可见时,onStop()方法将被调用。
- 如果Activity在被销毁前又获得焦点,则下一个方法是onRestart(),
- 否则,onDestory()方法将被调用
- 会被killed
- **/
- protected void onStop();
- /**
- 当finish()方法被调用,或者系统为了回收资源销毁Activity时,这个方法会被调用。
- 您可以通过isFinishing()方法来区分这两种情况。
- 会被killed
- **/
- protected void onDestroy();
- }
但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()做了什么留待后续源码分析的时候讲。
- ----src/
- -----------java/package/code
- ---gen/ 根据资源自动生成的常量Int组类,方便代码中加载资源
- <pre name="code" class="html">-----------<span style="font-family:Arial, Helvetica, sans-serif;">R.java</span>
Android插件提供了一个非常强大的图形化工具,常用的视图可以非常方便的拖拽到界面编辑器上并自动生成布局代码。
图1.2 界面编辑器
- <?xml version="1.0" encoding="utf-8"?>
- <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- android:orientation="vertical" >
- <Button
- android:id="@+id/bt_function_sfa"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/bt_function_sfa" >
- </Button>
- <EditText
- android:id="@+id/et_function_sfa"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:ems="10" >
- </EditText>
- <ScrollView
- android:id="@+id/scrollView1"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent" >
- <LinearLayout
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- android:orientation="vertical" >
- <TextView
- android:id="@+id/tv_function_sfa"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- android:scrollbars="vertical" />
- </LinearLayout>
- </ScrollView>
- </LinearLayout>
一些酷炫的显示效果,则需要自己设计并实现视图。这在我看来是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增加字符转换器
- restTemplate.getMessageConverters().add(new StringHttpMessageConverter());
- }
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>的三个泛型类型分别表示Params任务参数类型、Progress过程数据类型、Result结果类型。
它提供了3个主要方法。您需要特别注意,前两个方法是在用户线程执行的。而只有doInBackground()方法是线程帮忙的。
- /** 用户线程,在执行具体操作之前操作 **/
- protected void onPreExecute()
- /** 用户线程,在执行具体操作之后操作 **/
- protected void onPostExecute(Result result)
- /** 线程池工作线程,执行具体操作 **/
- protected abstract Result doInBackground(Params... params)
- /** 线程池工作线程,使用handler机制向用户线程发送消息 **/
- protected final void publishProgress(Progress... values)
- /** 用户线程,在publishProgress调用后执行 **/
- 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发送广播
- //请求返回后,将消息传递给broadcast receiver,调用toast和通知两种方式显示
- Intent intent = new Intent();
- intent.setAction(LinkOkReceiver.BROADCAST_ACTION);
- intent.putExtra(LinkOkReceiver.EXTRA_MSG, "请求已得到应答响应,请注意!");
- sendBroadcast(intent);
6 BroadcastReceiver
BroadcastReceiver必须实现onReceive()方法用以接受广播和处理。
广播的重要用途在于它不仅可以在内部传播,也可以是跨应用的,为不同应用间的通讯带来便利。所以使用它需要在AndroidManifest.xml中注册。
- <!-- 注册自定义静态广播接收器 -->
- <receiver android:name="yunhe.doit.receiver.LinkOkReceiver">
- <intent-filter>
- <action android:name="yunhe.doit.urlLink" />
- </intent-filter>
- </receiver>
接收到请求后,广播做了信息显示和通知的处理。
- //信息框显示
- 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应用的响应速度就非常影响用户的体验,所以程序的调优必不可少。