第5章Android组件与事件5.1Android组件 Android应用程序在Android应用框架之上,由一些系统自带的应用程序和用户创建的应用程序组成。组件是可以调用的基本功能模块,Android应用程序就是由组件组成的。一个Android应用程序通常包含4个核心组件和一个Intent,4个核心组件分别是Activity、Service、BroadcastReceiver和ContentProvider。Intent是组件之间进行通信的载体,不仅可以在同一个应用中起传递信息的作用,还可以在不同的应用间传递信息,如图51所示。 图51Android应用程序组件5.1.1Android组件Activity〖*3〗1. Activity的生命周期与创建Activity是与用户交互的接口,提供了一个用户完成相关操作的窗口。当在开发中创建Activity后,通过调用setContentView(View)方法给该Activity指定一个布局界面,而这个界面就是提供给用户交互的接口。Android系统中是通过Activity栈的方式管理Activity的,而Activity自身则是通过生命周期的方法管理自己的创建与销毁。Activity生命周期流程图如图52所示。 图52Activity生命周期流程图 Activity的形态如下。 (1) Active/Running: Activity处于活动状态,此时Activity处于栈顶,是可见状态,可与用户进行交互。基于Android技术的物联网应用开发第5章Android组件与事件〖2〗(2) Paused: 当Activity失去焦点时,或被一个新的非全屏的Activity,或被一个透明的Activity放置在栈顶时,Activity就转化为Paused状态。但此时Activity只是失去了与用户交互的能力,其所有的状态信息及其成员变量都还存在,只在系统内存紧张的情况下,才有可能被系统回收。 (3) Stopped: 当一个Activity被另一个Activity完全覆盖时,被覆盖的Activity就会进入Stopped状态,此时它不再可见,但是与Paused状态一样保持着其所有的状态信息及其成员变量。 (4) Killed: 当Activity被系统回收时,Activity就处于Killed状态。 所谓典型的生命周期,就是在有用户参与的情况下,Activity经历创建、运行、停止、销毁等正常的生命周期过程。这里先介绍几个主要方法的调用时机,然后再通过代码层验证其调用流程。 (1) onCreate(): 该方法在Activity被创建时调用,它是生命周期第一个调用的方法,创建Activity时一般都需要重写该方法,然后在该方法中做一些初始化的操作,如通过setContentView()设置界面布局的资源,初始化所需要的组件信息等。 (2) onStart(): 此方法被调用时表示Activity正在启动,此时Activity已处于可见状态,只是还没有在前台显示,因此无法与用户进行交互。可以简单理解为Activity已显示,但我们无法看见。 (3) onResume(): 当此方法被调用时,说明Activity已在前台可见,可与用户交互了(处于Active/Running形态)。onResume()方法与onStart()的相同点是,两者都表示Activity可见,只不过onStart()调用时Activity还是后台,无法与用户交互,而调用onResume()时Activity已显示在前台,可与用户交互。当然,从流程图也可以看出,当Activity停止后(onPause()方法和onStop()方法被调用),重新回到前台时也会调用onResume()方法,因此也可以在onResume()方法中初始化一些资源,如重新初始化在onPause()或者onStop()方法中释放的资源。 (4) onPause(): 此方法被调用时,表示Activity正在停止(处于Paused形态)。一般情况下,onStop()方法会紧接着被调用。通过流程图还可以看到的一种情况是: onPause()方法执行后直接执行了onResume()方法,这属于比较极端的现象,这可能是用户操作使当前Activity退居后台后又迅速再回到当前的Activity,此时onResume()方法就会被调用。当然,在onPause()方法中,我们可以做一些数据存储或者动画停止或者资源回收的操作,但是不能太耗时,因为这可能会影响到新的Activity的显示——onPause()方法执行完成后,新Activity的onResume()方法才会被执行。 (5) onStop(): 一般在onPause()方法执行完成后直接执行,表示Activity即将停止或者完全被覆盖(处于Stopped形态),此时Activity不可见,仅在后台运行。同样,在onStop()方法可以做一些资源释放的操作(不能太耗时)。 (6) onRestart(): 表示Activity正在重新启动,当Activity由不可见状态变为可见状态时,该方法被调用。这种情况一般是用户打开一个新的Activity时,当前的Activity就会被暂停(onPause()和onStop()被执行了),接着又回到当前Activity页面,onRestart()方法就会被调用。 (7) onDestroy(): 此时Activity正在被销毁,也是生命周期最后一个执行的方法。一般地可以在此方法中做一些回收工作和最终的资源释放。 下面通过程序验证上面流程中的几种比较重要的情况,见example5.1。 package com.cmcm.activitylifecycle; import android.content.Intent; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.view.View; import android.widget.Button; public class MainActivity extends AppCompatActivity { Button bt; //Activity创建时被调用,@param savedInstanceState @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); LogUtils.e("onCreate is invoke!!!"); bt= (Button) findViewById(R.id.bt); bt.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent i = new Intent(MainActivity.this,SecondActivity.class); startActivity(i); } }); } //Activity从后台重新回到前台时被调用 @Override protected void onRestart() { super.onRestart(); LogUtils.e("onRestart is invoke!!!"); } //Activity创建或者从后台重新回到前台时被调用 @Override protected void onStart() { super.onStart(); LogUtils.e("onStart is invoke!!!"); } //Activity创建或者从被覆盖、后台重新回到前台时被调用 @Override protected void onResume() { super.onResume(); LogUtils.e("onResume is invoke!!!"); } //Activity被覆盖到下面或者锁屏时被调用 @Override protected void onPause() { super.onPause(); LogUtils.e("onPause is invoke!!!"); } //退出当前Activity或者跳转到新Activity时被调用 @Override protected void onStop() { super.onStop(); LogUtils.e("onStop is invoke!!!"); } //退出当前Activity时被调用,调用之后Activity就结束了 @Override protected void onDestroy() { super.onDestroy(); LogUtils.e("onDestroy is invoke!!!"); } } 2. Activity间的数据传递与交互 1) 数据传递 假设有两个Activity,即MainActivity与SecondActivity,其中MainActivity是主活动。MainActivity中有一个字符串,现在想把这个字符串传递到SecondActivity,可以用putExtra与getStringExtra在Activity之间传递数据,见示例example5.2。public String getStringExtra(String name){ return mExtras == null?null:mExtras.getString(name); } MainActivity需要传递data给SecondActivity,具体使用如下: //MainActivity(sender) button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { String data = " hello world!"; Intent intent = new Intent(MainActivity.this, SecondActivity.class); intent.putExtra("extra_data",data); //{"extra_data":data} startActivity(intent); } }); // SecondActivity(receiver) public class SecondActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState){ super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_NO_TITLE); setContentView(R.layout.second_layout); //通过intent获取数据 Intent intent = getIntent(); String data = intent.getStringExtra("extra_data"); Toast.makeText(SecondActivity.this,data,Toast.LENGTH_SHORT).show(); } } 2) 数据返回 因为数据返回一般是Activity的销毁,而不是调用,因此与1)中的方式不一样。假设现在需要从SecondActivity返回数据给MainActivity: // SecondActivity public class SecondActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState){ super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_NO_TITLE); setContentView(R.layout.second_layout); Button button2 = (Button) findViewById(R.id.button_2); button2.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(); intent.putExtra("data_return", "Hello MainActivity"); //绑定result_code与intent的内容 setResult(RESULT_OK,intent); finish(); } }); } } MainActivity需要接收的参数有request Code、result Code和一个intent。public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_NO_TITLE); setContentView(R.layout.activity_main); final Button button = (Button) findViewById(R.id.button_1); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(MainActivity.this, SecondActivity.class); startActivityForResult(intent, 200); } }); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data){ switch (requestCode){ case 200: if (requestCode == RESULT_OK){ String returnedData = data.getStringExtra("data_return"); Log.d("MainActivity", returnedData); } break; default: } } ... } 3) 在Bundle中传递及保存数据 在Bundle中传递及保存数据是为了防止Activity调用时,当前Activity如果需要在onDestroy()时保存一些临时数据,则可以用构造函数onSaveInstanceState()。 保存数据示例example5.3如下。 @Override protected void onSaveInstanceState(Bundle outState){ super.onSaveInstanceState(outState); String tempData = "Something you just typed"; outState.putString("data_key",tempData); } 取出数据: public class ActivityLifeCycleTest extends AppCompatActivity { public static final String TAG = "MainActivity"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Log.d(TAG, "onCreate"); Toast.makeText(ActivityLifeCycleTest.this,TAG+"===onCreate==",Toast.LENGTH_SHORT).show(); requestWindowFeature(Window.FEATURE_NO_TITLE); setContentView(R.layout.activity_activity_life_cycle_test); if (savedInstanceState != null){ String data = savedInstanceState.getString("data_key"); Log.d(TAG, data); } ... } 5.1.2Android组件Service Service常用于没有用户界面,但需要长时间在后台运行的应用,与应用程序的其他模块(如Activity)一同运行于主线程中。一般通过startService()或bindService()方法创建Service,通过stopService()或stopSelf()方法终止Service。通常,都在Activity中启动和终止Service。 在Android应用中,Service的典型应用是: 音乐播放器,在一个媒体播放器程序中,大概要有一个或多个活动(Activity)供用户选择歌曲并播放。然而,音乐的回放就不能使用活动(Activity)了,因为用户希望能够切换到其他界面时音乐继续播放。这种情况下,媒体播放器活动(Activity)要用Context.startService()启动一个服务在后台运行,保持音乐的播放。系统将保持这个音乐回放服务的运行,直到它结束。需要注意,要用Context.bindService()方法连接服务(如果它没有运行,要先启动它)。当连接到服务后,可以通过服务暴露的一个接口和它通信。对于音乐服务,它支持暂停、倒带、重放等功能。 1. Service与Thread Service(服务): Android四大组件之一,非常适合执行那些不需要与用户交互且要求长期执行的任务。需要注意: 服务依赖于创建服务时所在的应用程序进程。 Thread(线程): 程序执行的最小单元,可以用Thread执行一些异步操作。 子进程和服务的使用及其区别如下。 (1) 子进程的使用: 通常使用子线程完成耗时任务。但是,有时需要根据任务的执行结果更新显示相应的UI控件,要知道Android的UI与许多其他GUI库一样是线程不安全的,必须在主线程中操作,于是需要采用异步消息机制。为了方便起见,我们还是使用基于异步消息机制的AsyncTask抽象类: 使用AsyncTask的诀窍在于,在doInBackground(运行在子线程中)方法中执行耗时操作,在onProgressUpdate(运行在主线程中)方法中进行UI操作,在onPostExcute(运行在主线程中)方法中执行任务的收尾工作。 (2) 服务的使用: 服务我们最初的理解是在后台处理一些耗时操作,但是不要被其所谓的后台概念迷惑,实际上服务不会自动开启线程,所有的代码都是默认运行在主线程中的,如果直接在服务中进行耗时操作,必定会阻塞主线程,出现ANR的情况。因此,需要在服务中手动创建子线程,在子线程中进行耗时操作。一个比较标准的服务如example5.4所示。public class MyService extends Service { ... @Override public int onStartCommand(Intent intent,int flags,int startId) { new Thread(new Runnable() { public void run() { //处理具体逻辑 } }).start(); return super.onStartCommand(intent,flags,startId); } } 但是,这种服务一旦启动,必须调用stopSelf()方法或stopService()方法,才能停止该服务。为了简单地创建一个异步的、会自动停止的服务,Android专门提供了一个IntentService类,只需要新建一个MyIntentService,继承IntentService类,在onHandlerIntent()方法中执行耗时操作即可,因为这个方法是在子线程中运行的,且这个服务在运行结束后会自动停止。 2. IntentService的使用 Android中的IntentService继承自Service类,我们在讨论IntentService之前,先想一下Service的特点: Service的回调方法(onCreate、onStartCommand、onBind、onDestroy)都是运行在主线程中的。当通过startService启动Service之后,需要在Service的onStartCommand()方法中写代码完成工作,但是onStartCommand()是运行在主线程中的,如果需要在此处完成一些网络请求或IO等耗时操作,就会阻塞主线程UI无响应,从而出现ANR现象。为了解决这种问题,最好的办法是在onStartCommand()中创建一个新的线程,并把耗时代码放到这个新线程中执行。在onStartCommand()中开启新的线程作为工作线程执行网络请求,这样不会阻塞主线程。可见,创建一个带有工作线程的Service是一种很常见的需求(因为工作线程不会阻塞主线程),所以Android为了简化开发带有工作线程的Service,Android额外开发了一个类——IntentService。 IntentService的特点: (1) IntentService自带一个工作线程,当Service需要做一些可能会阻塞主线程的工作时,可以考虑使用IntentService。 (2) 需要将要做的实际工作放入IntentService的onHandleIntent()回调方法中,当通过startService(intent)启动IntentService之后,最终Android Framework会回调其onHandleIntent()方法,并将intent传入该方法,这样就可以根据intent做实际工作,并且onHandleIntent运行在IntentService所持有的工作线程中,而非主线程。 (3) 当通过startService多次启动IntentService,会产生多个job,由于IntentService只有一个工作线程,所以每次onHandleIntent只能处理一个job。面对多个job,IntentService会如何处理?处理方式是onebyone,也就是一个一个按照先后顺序处理,先将intent1传入onHandleIntent,让其完成job1,然后将intent2传入onHandleIntent,让其完成job2……,直至所有job完成,所以说IntentService不能并行执行多个job,只能一个一个地按先后顺序完成,当所有job完成时,IntentService就销毁了,会执行onDestroy()回调方法。 IntentService继承自Service类,并且IntentService重写了onCreate()、onStartCommand()、onStart()、onDestroy()回调方法,IntentService还添加了一个onHandleIntent()回调方法。下面依次解释这几个方法在IntentService中的作用。 (1) onCreate:在onCreate()回调方法中,利用mName作为线程名称,创建HandlerThread,HandlerThread是IntentService的工作线程。HandlerThread执行了start()方法后,其本身就关联了消息队列和Looper,并且消息队列开始循环起来。 (2) onStartCommand: IntentService重写了onStartCommand()回调方法,即在内部调用onStart()回调方法。 (3) onStart:在onStart()方法中创建Message对象,并将intent作为Message的obj参数,这样Message与Intent就关联起来了,然后通过Handler的sendMessage()方法将关联了Intent信息的Message发送给Handler。 (4) onHandleIntent: 在onStart()方法中,通过sendMessage()方法将Message放入Handler所关联的消息队列中后,Handler所关联的Looper对象会从消息队列中取出一个Message,然后将其传入Handler的handleMessage()方法中,在handleMessage()方法中首先通过Message的obj获取到原始的Intent对象,然后将其作为参数传给onHandleIntent()方法让其执行。handleMessage()方法是运行在HandlerThread中的,所以onHandleIntent()也是运行在工作线程中的。执行完onHandleIntent()之后,需要调用stopSelf(startId)声明某个job完成了。当所有job完成时,Android会回调onDestroy()方法,销毁IntentService。 (5) onDestroy:当所有job完成时,Service会销毁并执行其onDestroy()回调方法。在该方法中调用了Handler的quit()方法,该方法会终止消息循环。 总结: IntentService可以在工作线程中完成工作,而不阻塞主线程,但是IntentService不能并行处理多个job,只能依次处理,一个接一个,当所有job完成后,会自动执行onDestroy()方法而无须自己调用stopSelf()或stopSelf(startId)方法。IntentService并不神秘,只是Android对一种常见开发方式的封装,便于开发人员减少开发工作量。IntentService是一个助手类,如果Android没有提供该类,也可以写一个类似的类。IntentService之于Service,类似于HandlerThread之于Handler。 5.1.3BroadcastReceiver组件〖*3〗1. BroadcastReceiver在Android中,Broadcast是一种广泛运用在应用程序之间传输信息的组件,而BroadcastReceiver是接收并响应广播消息的组件: 对发送出来的Broadcast进行过滤接收并响应,它不包含任何用户界面,可以通过启动Activity或者Notification通知用户接收到重要消息,在Notification中有多种方法提示用户,如闪动背景灯、振动设备、发出声音或在状态栏上放置一个持久的图标。 BroadcastReceiver过滤接收的过程如图53所示。 图53BroadcastReceiver过滤接收的过程 需要发送消息时,把要发送的消息和用于过滤的信息(如Action、Category)装入一个Intent对象,然后通过调用Context.sendBroadcast()、sendOrderBroadcast()或sendStickyBroadcast()方法,把Intent对象以广播方式发送出去。 当Intent发送后,所有已经注册的BroadcastReceiver会检查注册时的Intent Filter是否与发送的Intent相匹配,若匹配,则调用BroadcastReceiver的onReceive()方法。因此,在定义一个BroadcastReceiver时,通常需要实现onReceive()方法。 BroadcastReceiver注册有以下两种方式。 (1) 静态地在AndroidManifest.xml中用<receiver>标签声明注册,并在标签内用<intentfilter>标签设置过滤器。 (2) 动态地在代码中先定义并设置好一个Intent Filter对象,然后在需要注册的地方调用Context.registerReceiver()方法,如果取消,就调用Context.unregisterReceiver()方法。 不管是用XML注册的,还是用代码注册的,程序退出时,一般需要注销,否则下次启动程序可能会有多个BroadcastReceiver。另外,若在使用sendBroadcast()方法时指定了接收权限,则只有在AndroidManifest.xml中用<userpermission>标签声明了拥有此权限的BroadcastReceiver时,才有可能接收到发送来的Broadcast。 同样,若在注册BroadcastReceiver时指定了可接收的Broadcast的权限,则只有在包内的AndroidManifest.xml中用<userpermission>标签声明了拥有此权限的Context对