《第一行代码》阅读记录—四大组件
来源:互联网 发布:想开个淘宝店怎么开 编辑:程序博客网 时间:2024/06/06 13:11
一、前提概要
《第一行代码—Android》是Android初学者的最佳入门书。全书由浅入深、系统全面地讲解了Android软件开发的方方面面。这本书是2014年8月上市,到现在我写下这篇博客为止,已经过了两年,目前,它还是很畅销。可见,在一批新的android开发者眼中,它还是一本启蒙书的存在。这本书的作者郭霖,Android软件开发工程师。从事Android开发工作,有着丰富的项目实战经验,负责及参与开发过多款移动应用与游戏,对Android系统架构及应用层开发有着深入的理解,活跃于CSDN,在CSDN上发表Android技术相关博文,受到大量好评。
接下来,我会记录关于阅读《第一行代码》的学习见解与体会,内容框架并不是按照书上目录而来,如有不妥错误之处,可以评论指正以及补充。
二、四大组件之活动-Activity
(一)Intent
Intent 是 Android 程序中各组件之间进行交互的一种重要方式,它不仅可以指明当前组件想要执行的动作,还可以在不同组件之间传递数据。Intent 一般可被用于启动活动、启动服务、以及发送广播等场景。Intent 的用法大致可以分为两种,显式 Intent 和隐式 Intent,我们先来看一下显式 Intent如何使用。
1.显式Intent
Intent 有多个构造函数的重载,其中一个是 Intent(Context packageContext, Class<?> cls)
。这个构造函数接收两个参数,第一个参数 Context 要求提供一个启动活动的上下文,第二个参数 Class 则是指定想要启动的目标活动, 通过这个构造函数就可以构建出 Intent 的 “意图” 。
然后我们应该怎么使用这个 Intent 呢?Activity 类中提供了一个 startActivity()方法, 这个方法是专门用于启动活动的, 它接收一个 Intent参数, 这里我们将构建好的 Intent传入 startActivity()方法就可以启动目标活动了。
Intent intent = new Intent(FirstActivity.this, SecondActivity.class);startActivity(intent);
2.隐式Intent
相比于显式 Intent,隐式 Intent 则含蓄了许多,它并不明确指出我们想要启动哪一个活动, 而是指定了一系列更为抽象的 action 和 category 等信息, 然后交由系统去分析这个 Intent,并帮我们找出合适的活动去启动。
通过在标签下配置的内容,可以指定当前活动能够响应的 action和 category,打开AndroidManifest.xml,添加如下代码:
<activity android:name=".SecondActivity" ><intent-filter><action android:name="com.example.activitytest.ACTION_START" /><category android:name="android.intent.category.DEFAULT" /></intent-filter></activity>
在action
标签中我们指明了当前活动可以响应com.example.bin.helloworld.ACTION_START
这个action,而category
标签则包含了一些附加信息,更精确地指明了当前的活动能够响应的Intent 中还可能带有的category。只有action
和category
中的内容同时能够匹配上Intent 中指定的action 和category 时,这个活动才能响应该Intent。
每个Intent 中只能指定一个action,但却能指定多个category。
Intent intent = new Intent("com.example.activitytest.ACTION_START");startActivity(intent);
android.intent.category.DEFAULT
是一种默认category
, 在调用startActivity()
方法的时候会自动将这个category
添加到Intent 中。可以调用Intent 中的addCategory()
方法来添加一个category
。
3.更多隐式Intent用法
使用隐式Intent,我们不仅可以启动自己程序内的活动,还可以启动其他程序的活动,这使得Android 多个应用程序之间的功能共享成为了可能。比如调用系统的浏览器打开网页。
Intent intent = new Intent(Intent.ACTION_VIEW);intent.setData(Uri.parse("http://www.baidu.com"));startActivity(intent);
首先指定了 Intent 的 action 是 Intent.ACTION_VIEW
, 这是一个 Android 系统内置的动作,其常量值为android.intent.action.VIEW
。然后通过 Uri.parse()
方法,将一个网址字符串解析成一个 Uri 对象,再调用 Intent 的 setData()
方法将这个 Uri 对象传递进去。我们还可以在<intent-filter>
标签中再配置一个<data>
标签,用于更精确地指定当前活动能够响应什么类型的数据。<data>
标签中主要可以配置以下内容。
- android:scheme
用于指定数据的协议部分,如上例中的 http 部分。 - android:host
用于指定数据的主机名部分,如上例中的 www.baidu.com 部分。 - android:port
用于指定数据的端口部分,一般紧随在主机名之后。 - android:path
用于指定主机名和端口之后的部分,如一段网址中跟在域名之后的内容。 - android:mimeType
除了http 协议外,我们还可以指定很多其他协议,比如geo 表示显示地理位置、tel 表示拨打电话。调用系统拨号界面。
Intent intent = new Intent(Intent.ACTION_DIAL); intent.setData(Uri.parse("tel:10086")); startActivity(intent);
4.向下一个活动传递数据
Intent 中提供了一系列putExtra()
方法的重载,可以把我们想要传递的数据暂存在Intent 中,启动了另一个活动后,只需要把这些数据再从Intent 中取出就可以了。putExtra()
方法接收两个参数,第一个参数是键,用于后面从Intent中取值,第二个参数才是真正要传递的数据。
String data = "I am from FirstActivity"; Intent intent = new Intent(FirstActivity.this,SecondActivity.class); intent.putExtra("extra_data",data);startActivity(intent);
//获得传递过来的意图 Intent intent = getIntent(); //获得意图中的信息,getStringExtra获得字符串类型,getBooleanExtra获得布尔类型。 String data = intent.getStringExtra("extra_data");
另外,通过intent来传递对象有两种方式Serializable方式和Parcelable方式,这里暂且不提,有兴趣得可以Googel用法。
5.返回数据给上一个活动
Activity 中还有一个 startActivityForResult()方法也是用于启动活动的,但这个方法期望在活动销毁的时候能够返回一个结果给上一个活动。毫无疑问,这就是我们所需要的。 startActivityForResult()
方法接收两个参数,第一个参数还是 Intent,第二个参数是请求码,用于在之后的回调中判断数据的来源。
Intent intent = new Intent(FirstActivity.this,SecondActivity.class);//第一个参数还是Intent//第二个参数是请求码,用于在之后的回调中判断数据的来源startActivityForResult(intent,1);
Intent intent = new Intent();intent.putExtra("data_return","hello FirstActivity");//setResult()方法接收两个参数//第一个参数用于向上一个活动返回处理结果, 一般只使用RESULT_OK 或 RESULT_CANCELED(按Back键后返回)//第二个参数则是把带有数据的Intent 传递回去,setResult(RESULT_OK,intent);finish();
由于我们是使用 startActivityForResult()方法来启SecondActivity 的,在 SecondActivity被销毁之后会回调上一个活动的onActivityResult()方法,因此我们需要在 FirstActivity 中重写这个方法来得到返回的数据:
@Overrideprotected void onActivityResult(int requestCode, int resultCode, Intent data) {//第一个参数requestCode,即我们在启动活动时传入的请求码。//第二个参数resultCode,即我们在返回数据时传入的处理结果。//第三个参数data,即携带着返回数据的Intent switch (requestCode) { case 1: if (resultCode == RESULT_OK) { String returnData = data.getStringExtra("data_return"); Toast.makeText(FirstActivity.this, "返回的值:" + returnData, Toast.LENGTH_SHORT).show(); } break; default: }}
如果用户点击Back键,也需要返回值,我们可以重写SecondActivity中的onBackPressed()
方法。
@Overridepublic void onBackPressed() { Intent intent = new Intent(); intent.putExtra("data_return", "Hello FirstActivity"); setResult(RESULT_OK, intent); finish();}
也可以重写onKeyDown()
方法
@Overridepublic boolean onKeyDown(int keyCode, KeyEvent event) { // 是否触发按键为back键 if (keyCode == KeyEvent.KEYCODE_BACK) { Intent intent = new Intent(); intent.putExtra("data_return", "Hello FirstActivity"); setResult(RESULT_OK, intent); this.finish(); return true; }else { return super.onKeyDown(keyCode, event); }}
(二)活动的生命周期
1.返回栈
Android 中的活动是可以层叠的。我们每启动一个新的活动,就会覆盖在原活动之上,然后点击 Back 键会销毁最上面的活动,下面的一个活动就会重新显示出来。其实 Android 是使用任务(Task)来管理活动的,一个任务就是一组存放在栈里的活动的集合,这个栈也被称作返回栈(Back Stack) 。栈是一种后进先出的数据结构,在默认情况下,每当我们启动了一个新的活动,它会在返回栈中入栈,并处于栈顶的位置。而每当我们按下 Back 键或调用 finish()方法去销毁一个活动时,处于栈顶的活动会出栈,这时前一个入栈的活动就会重新处于栈顶的位置。系统总是会显示处于栈顶的活动给用户。
2.活动状态
每个活动在其生命周期中最多可能会有四种状态。
1.运行状态
当一个活动位于返回栈的栈顶时,这时活动就处于运行状态。系统最不愿意回收的就是处于运行状态的活动,因为这会带来非常差的用户体验。
2.暂停状态
当一个活动不再处于栈顶位置,但仍然可见时,这时活动就进入了暂停状态。你可能会觉得既然活动已经不在栈顶了,还怎么会可见呢?这是因为并不是每一个活动都会占满整个屏幕的,比如对话框形式的活动只会占用屏幕中间的部分区域,你很快就会在后面看到这种活动。处于暂停状态的活动仍然是完全存活着的,系统也不愿意去回收这种活动(因为它还是可见的,回收可见的东西都会在用户体验方面有不好的影响),只有在内存极低的情况下,系统才会去考虑回收这种活动。
3.停止状态
当一个活动不再处于栈顶位置,并且完全不可见的时候,就进入了停止状态。系统仍然会为这种活动保存相应的状态和成员变量,但是这并不是完全可靠的,当其他地方需要内存时,处于停止状态的活动有可能会被系统回收。
4.销毁状态
当一个活动从返回栈中移除后就变成了销毁状态。系统会最倾向于回收处于这种状态的活动,从而保证手机的内存充足。
3.活动的生存期
Activity 类中定义了七个回调方法,覆盖了活动生命周期的每一个环节,下面我来一一介绍下这七个方法。
1.onCreate()
这个方法你已经看到过很多次了,每个活动中我们都重写了这个方法,它会在活动第一次被创建的时候调用。你应该在这个方法中完成活动的初始化操作,比如说加载布局、绑定事件等。
2.onStart()
这个方法在活动由不可见变为可见的时候调用。
3.onResume()
这个方法在活动准备好和用户进行交互的时候调用。此时的活动一定位于返回栈的栈顶,并且处于运行状态。
4.onPause()
这个方法在系统准备去启动或者恢复另一个活动的时候调用。我们通常会在这个方法中将一些消耗CPU 的资源释放掉,以及保存一些关键数据,但这个方法的执行速度一定要快,不然会影响到新的栈顶活动的使用。
5.onStop()
这个方法在活动完全不可见的时候调用。它和onPause()方法的主要区别在于,如果启动的新活动是一个对话框式的活动,那么onPause()方法会得到执行,而onStop()方法并不会执行。
6.onDestroy()
这个方法在活动被销毁之前调用,之后活动的状态将变为销毁状态。
7.onRestart()
这个方法在活动由停止状态变为运行状态之前调用,也就是活动被重新启动了。
4.活动被回收了怎么办
打个比方,MainActivity 中有一个文本输入框,现在你输入了一段文字,然后启动NormalActivity,这时MainActivity 由于系统内存不足被回收掉,过了一会你又点击了Back 键回到MainActivity,你会发现刚刚输入的文字全部都没了,因为MainActivity 被重新创建了。
Activity 中还提供了一个onSaveInstanceState()
回调方法,这个方法会保证一定在活动被回收之前调用。
@Overrideprotected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); String tempData = "Something you just typed"; outState.putString("data_key", tempData);}
onSaveInstanceState()
方法会携带一个Bundle 类型的参数,Bundle 提供了一系列的方法用于保存数据,比如可以使用putString()
方法保存字符串,使用putInt()
方法保存整型数据,以此类推。同样可以使用putParcelable()
,putSerializable()
保存对象。每个保存方法需要传入两个参数,第一个参数是键,用于后面从Bundle 中取值,第二个参数是真正要保存的内容。
恢复数据,使用的onCreate()
方法有一个Bundle 类型的参数。这个参数在一般情况下都是null,但是当活动被系统回收之前有通过onSaveInstanceState()
方法来保存数据的话,这个参数就会带有之前所保存的全部数据,我们只需要再通过相应的取值方法将数据取出即可。修改onCreate()
方法,如下所示:
@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);Log.d(TAG, "onCreate");requestWindowFeature(Window.FEATURE_NO_TITLE);setContentView(R.layout.activity_main);if (savedInstanceState != null) {String tempData = savedInstanceState.getString("data_key");Log.d(TAG, tempData);}……}
Intent 还可以结合Bundle 一起用于传递数据的,首先可以把需要传递的数据都保存在Bundle 对象中,然后再将Bundle 对象存放在Intent 里。到了目标活动之后先从Intent 中取出Bundle,再从Bundle中一一取出数据。
(三)活动的启动模式
启动模式一共有四种,分别是 standard、singleTop、singleTask 和 singleInstance , 可 以 在 AndroidManifest.xml 中 通 过 给 <activity>
标 签 指 定android:launchMode
属性来选择启动模式。
standard,默认启动模式,系统不管此 Activity 是否已经在返回栈中存在,每次启动 Activity 都会创建该 Activity 的一个新的实例。
singleTop,在启动 Activity 时,如果发现返回栈的栈顶是该 Activity,则直接使用她,不会再创建新的实例;如果栈顶不是该 Activity,则会创建新的实例。
singleTask,在启动 Activity 时,如果找到返回栈中已经存在该 Activity,则直接使用,并将这个 Activity 之上的所有 Activity 全部出栈;如果没找到,则会创建新的实例。
singleInstance,会启用一个新的返回栈来管理这个活动,并保证不再有其他 Activity 的实例进入。
三、四大组件之广播接收者-Broadcast Receiver
(一)系统广播
Android 内置了很多系统级别的广播,通过监听这些广播可以得到相关的系统状态信息。例如:
- 系统启动完成
- 打开、关闭飞行模式
- 电量低
- 内存不足
1.动态注册
新建一个类,让它继承自 BroadcastReceiver,并重写父类的onReceive()
方法就行了。 这样当有广播到来时, onReceive()
方法就会得到执行,具体的逻辑就可以在这个方法中处理。
class NetworkChangeReceiver extends BroadcastReceiver{ @Override public void onReceive(Context context, Intent intent){ Toast.makeText(context, "network changes", Toast.LENGTH_SHORT).show(); }}
在 onCreate() 方法中注册,代码如下:
private IntentFilter intentFilter;private NetworkChangeReceiver networkChangeReceiver;@Overrideprotected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); intentFilter = new IntentFilter(); intentFilter.addAction("android.net.conn.CONNECTIVITY_CHANGE"); networkChangeReceiver = new NetworkChangeReceiver(); registerReceiver(networkChangeReceiver, intentFilter);}
在 onDestroy() 方法中取消注册,代码如下:
@Overrideprotected void onDestroy(){ super.onDestroy(); unregisterReceiver(networkChangeReceiver);}
运行一下,会发现程序启动时会弹出一个提示;按 Home 回到主界面,并尝试开关网络,会发现有提示弹出。
2.静态注册
这里我们准备让程序接收一条开机广播, 当收到这条广播时就可以在 onReceive()
方法里执行相应的逻辑,从而实现开机启动的功能。新建一个 BootCompleteReceiver
继承自BroadcastReceiver
,代码如下所示:
public class BootCompleteReceiver extends BroadcastReceiver{ @Override public void onReceive(Context context, Intent intent){ Toast.makeText(context, "Boot complete", Toast.LENGTH_SHORT).show(); }}
修改 AndroidManifest.xml 文件,代码如下所示:
<receiver android:name=".MainActivity$BootCompleteReceiver"> <intent-filter> <action android:name="android.intent.action.BOOT_COMPLETED"/> </intent-filter></receiver>
监听系统开机广播需要权限,在 AndroidManifest.xml 中添加权限声明,代码如下:
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"></uses-permission>
(二)自定义广播
1.标准广播
在发送广播之前,我们还是需要先定义一个广播接收器来准备接收此广播,新建一个 MyBroadcastReceiver继承自BroadcastReceiver,代码如下所示:
public class MyBroadcastReceiver extends BroadcastReceiver {@Overridepublic void onReceive(Context context, Intent intent) {Toast.makeText(context, "received in MyBroadcastReceiver",Toast.LENGTH_SHORT).show();}}
然后在 AndroidManifest.xml 中对这个广播接收器进行注册:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"package="com.example.broadcasttest"android:versionCode="1"android:versionName="1.0" >……<applicationandroid:allowBackup="true"android:icon="@drawable/ic_launcher"android:label="@string/app_name"android:theme="@style/AppTheme" >……<receiver android:name=".MyBroadcastReceiver"><intent-filter><action android:name="com.example.broadcasttest. MY_BROADCAST"/></intent-filter></receiver></application></manifest>
MyBroadcastReceiver 接收一条com.example.broadcasttest.MY_BROADCAST
的广播。给 MainActivity 增加一个按钮,并在 onCreate() 方法中添加点击事件,代码如下:
public class MainActivity extends Activity {……@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);Button button = (Button) findViewById(R.id.button);button.setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View v) {Intent intent = new Intent("com.example.broadcasttest.MY_BROADCAST");sendBroadcast(intent);}});……}……}
此时发出去的广播就是一条标准广播。
2.有序广播
定义一个按钮,设置其点击事件,发送一个有序广播。
Intent intent = new Intent(); //设置intent的动作为com.example.broadcast,可以任意定义 intent.setAction("com.example.broadcast"); //发送无序广播 //第一个参数:intent //第二个参数:String类型的接收者权限 //第三个参数:BroadcastReceiver 指定的接收者 //第四个参数:Handler scheduler //第五个参数:int 此次广播的标记 //第六个参数:String 初始数据 //第七个参数:Bundle 往Intent中添加的额外数据 sendOrderedBroadcast(intent, null, null, null, "这是初始数据", );
定义多个广播接收者,来接收这个广播事件。通过Toast的打印判断是否收到广播。
public class MyReceiver1 extends BroadcastReceiver { public MyReceiver1() { } @Override public void onReceive(Context context, Intent intent) { //获取广播中的数据(即得到 "这是初始数据" 字符串) String message = getResultData(); Toast.makeText(context ,message ,Toast.LENGTH_SHORT).show(); //修改数据 setResultData("这是修改后的数据"); }}
public class MyReceiver2 extends BroadcastReceiver { public MyReceiver2() { } @Override public void onReceive(Context context, Intent intent) { String message = getResultData(); Toast.makeText(context ,message ,Toast.LENGTH_SHORT).show(); //终止广播 abortBroadcast(); }}
public class MyReceiver3 extends BroadcastReceiver { public MyReceiver3() { } @Override public void onReceive(Context context, Intent intent) { String message = getResultData(); Toast.makeText(context ,message ,Toast.LENGTH_SHORT).show(); }}
在Manifest.xml中配置该接收者。
并设置优先级:MyReceiver1>MyReceiver2>MyReceiver3。
<!-- 优先级相等的话,写在前面的receiver的优先级大于后面的 --><receiver android:name=".MyReceiver1" > <!-- 定义广播的优先级 --> <intent-filter android:priority="1000"> <!-- 动作设置为发送的广播动作 --> <action android:name="com.example.broadcast"/> </intent-filter></receiver><receiver android:name=".MyReceiver2" > <!-- 定义广播的优先级 --> <intent-filter android:priority="0"> <!-- 动作设置为发送的广播动作 --> <action android:name="com.example.broadcast"/> </intent-filter></receiver><receiver android:name=".MyReceiver3" > <!-- 定义广播的优先级 --> <intent-filter android:priority="-1000"> <!-- 动作设置为发送的广播动作 --> <action android:name="com.example.broadcast"/> </intent-filter></receiver>
运行结果:MyReceiver1得到广播数据后打印“这是初始数据”,MyReceiver2接收到广播数据打印“这是修改后的数据”,MyReceiver3没有打印。
(三)本地广播
定义一个 LocalReceiver ,代码如下:
public static class LocalReceiver extends BroadcastReceiver{ @Override public void onReceive(Context context, Intent intent){ Toast.makeText(context, "received local broadcast", Toast.LENGTH_SHORT).show(); }}
使用 LocalBroadcastManager 来注册接收器、发送广播,代码如下:
private IntentFilter intentFilter;private LocalReceiver localReceiver;private LocalBroadcastManager localBroadcastManager;@Overrideprotected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); localBroadcastManager = localBroadcastManager.getInstance(this); Button button = (Button)findViewById(R.id.button); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent("com.example.broadcasttest.LOCAL_BROADCAST"); localBroadcastManager.sendBroadcast(intent); } }); intentFilter = new IntentFilter(); intentFilter.addAction("com.example.broadcasttest.LOCAL_BROADCAST"); localReceiver = new LocalReceiver(); localBroadcastManager.registerReceiver(localReceiver, intentFilter);}
记住取消注册,代码如下:
@Overrideprotected void onDestroy(){ super.onDestroy(); localBroadcastManager.unregisterReceiver(localReceiver);}
本地广播无法通过静态注册的方式接收。
四、四大组件之服务-Service
(一)服务的基本用法
Service 是一个可以在后台执行长时间运行操作而不使用用户界面的应用组件。服务可由其他应用组件启动,而且即使用户切换到其他应用,服务仍将在后台继续运行。 此外,组件可以绑定到服务,以与之进行交互,甚至是执行进程间通信 (IPC)。 例如,服务可以处理网络事务、播放音乐,执行文件 I/O 或与内容提供程序交互,而所有这一切均可在后台进行。
新建一个Android项目,项目名就叫ServiceTest,然后新建一个MyService继承自Service,并重写父类的onCreate()、onStartCommand()和onDestroy()
方法,如下所示:
public class MyService extends Service { public static final String TAG = "MyService"; @Override public void onCreate() { super.onCreate(); Log.d(TAG, "onCreate() executed"); } @Override public int onStartCommand(Intent intent, int flags, int startId) { Log.d(TAG, "onStartCommand() executed"); return super.onStartCommand(intent, flags, startId); } @Override public void onDestroy() { super.onDestroy(); Log.d(TAG, "onDestroy() executed"); } @Override public IBinder onBind(Intent intent) { return null; } }
可以看到,这里我们又重写了 onCreate()、onStartCommand()和 onDestroy()这三个方法,它们是每个服务中最常用到的三个方法了。其中 onCreate()方法会在服务创建的时候调用,onStartCommand()方法会在每次服务启动的时候调用,onDestroy()方法会在服务销毁的时候调用。
通常情况下,如果我们希望服务一旦启动就立刻去执行某个动作,就可以将逻辑写在onStartCommand()方法里。而当服务销毁时,我们又应该在 onDestroy()方法中去回收那些不再使用的资源。
另外需要注意,每一个服务都需要在 AndroidManifest.xml文件中进行注册才能生效,于是我们还应该修改 AndroidManifest.xml文件,代码如下所示:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"package="com.example.servicetest"android:versionCode="1"android:versionName="1.0" >……<applicationandroid:allowBackup="true"android:icon="@drawable/ic_launcher"android:label="@string/app_name"android:theme="@style/AppTheme" >……<service android:name=".MyService" ></service></application></manifest>
我们在布局文件中加入了两个按钮,一个用于启动Service,一个用于停止Service。
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <Button android:id="@+id/start_service" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Start Service" /> <Button android:id="@+id/stop_service" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Stop Service" /> </LinearLayout>
然后打开或新建MainActivity作为程序的主Activity,在里面加入启动Service和停止Service的逻辑,代码如下所示:
public class MainActivity extends Activity implements OnClickListener { private Button startService; private Button stopService; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); startService = (Button) findViewById(R.id.start_service); stopService = (Button) findViewById(R.id.stop_service); startService.setOnClickListener(this); stopService.setOnClickListener(this); } @Override public void onClick(View v) { switch (v.getId()) { case R.id.start_service: Intent startIntent = new Intent(this, MyService.class); startService(startIntent); break; case R.id.stop_service: Intent stopIntent = new Intent(this, MyService.class); stopService(stopIntent); break; default: break; } } }
活动和服务进行通信
观察MyService中的代码,你会发现一直有一个onBind()方法我们都没有使用到,这个方法其实就是用于和Activity建立关联的,修改MyService中的代码,如下所示:
public class MyService extends Service { public static final String TAG = "MyService"; private MyBinder mBinder = new MyBinder(); @Override public void onCreate() { super.onCreate(); Log.d(TAG, "onCreate() executed"); } @Override public int onStartCommand(Intent intent, int flags, int startId) { Log.d(TAG, "onStartCommand() executed"); return super.onStartCommand(intent, flags, startId); } @Override public void onDestroy() { super.onDestroy(); Log.d(TAG, "onDestroy() executed"); } @Override public IBinder onBind(Intent intent) { return mBinder; } class MyBinder extends Binder { public void startDownload() { Log.d("TAG", "startDownload() executed"); // 执行具体的下载任务 } } }
可以看到,这里我们新建了一个 DownloadBinder 类,并让它继承自 Binder,然后在它的内部提供了开始下载以及查看下载进度的方法。当然这只是两个模拟方法,并没有实现真正的功能,我们在这两个方法中分别打印了一行日志。
然后修改activity_main.xml中的代码,在布局文件中添加用于绑定Service和取消绑定Service的按钮:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical" >……<Buttonandroid:id="@+id/bind_service"android:layout_width="match_parent"android:layout_height="wrap_content"android:text="Bind Service" /><Buttonandroid:id="@+id/unbind_service"android:layout_width="match_parent"android:layout_height="wrap_content"android:text="Unbind Service" /></LinearLayout>
这两个按钮分别是用于绑定服务和取消绑定服务的,那到底谁需要去和服务绑定呢?当然就是活动了。 当一个活动和服务绑定了之后, 就可以调用该服务里的 Binder 提供的方法了。
修改 MainActivity 中的代码,如下所示:
public class MainActivity extends Activity implements OnClickListener {private Button startService;private Button stopService;private Button bindService;private Button unbindService;private MyService.DownloadBinder downloadBinder;private ServiceConnection connection = new ServiceConnection() {@Overridepublic void onServiceDisconnected(ComponentName name) {}@Overridepublic void onServiceConnected(ComponentName name, IBinder service) {downloadBinder = (MyService.DownloadBinder) service;downloadBinder.startDownload();downloadBinder.getProgress();}};@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);……bindService = (Button) findViewById(R.id.bind_service);unbindService = (Button) findViewById(R.id.unbind_service);bindService.setOnClickListener(this);unbindService.setOnClickListener(this);}@Overridepublic void onClick(View v) {switch (v.getId()) {……case R.id.bind_service:Intent bindIntent = new Intent(this, MyService.class);bindService(bindIntent, connection, BIND_AUTO_CREATE); // 绑定服务break;case R.id.unbind_service:unbindService(connection); // 解绑服务break;default:break;}}}
现在活动和服务其实还没进行绑定,这个功能是在 Bind Service 按钮的点击事件里完成的。可以看到,这里我们仍然是构建出了一个 Intent 对象,然后调用 bindService()
方法将 MainActivity 和 MyService 进行绑定。bindService()
方法接收三个参数,第一个参数就是刚刚构建出的 Intent 对象,第二个参数是前面创建出的 ServiceConnection
的实例,第三个参数则是一个标志位,这里传入 BIND_AUTO_CREATE
表示在活动和服务进行绑定后自动创建服务。这会使得 MyService 中的 onCreate()
方法得到执行,但 onStartCommand()
方法不会执行。
如何销毁Service
在Service的基本用法这一部分,我们介绍了销毁Service最简单的一种情况,点击Start Service按钮启动Service,再点击Stop Service按钮停止Service,这样MyService就被销毁了。
那么如果我们是点击的Bind Service按钮呢?由于在绑定Service的时候指定的标志位是BIND_AUTO_CREATE
,说明点击Bind Service按钮的时候Service也会被创建,这时应该怎么销毁Service呢?其实也很简单,点击一下Unbind Service按钮,将Activity和Service的关联解除就可以了。
以上这两种销毁的方式都很好理解。那么如果我们既点击了Start Service按钮,又点击了Bind Service按钮会怎么样呢?这个时候你会发现,不管你是单独点击Stop Service按钮还是Unbind Service按钮,Service都不会被销毁,必要将两个按钮都点击一下,Service才会被销毁。也就是说,点击Stop Service按钮只会让Service停止,点击Unbind Service按钮只会让Service和Activity解除关联,一个Service必须要在既没有和任何Activity关联又处理停止状态的时候才会被销毁。
(二)服务的生命周期
与 Activity 类似,服务也拥有生命周期回调方法,您可以实现这些方法来监控服务状态的变化并适时执行工作。
public class ExampleService extends Service { int mStartMode; // indicates how to behave if the service is killed IBinder mBinder; // interface for clients that bind boolean mAllowRebind; // indicates whether onRebind should be used @Override public void onCreate() { // The service is being created } @Override public int onStartCommand(Intent intent, int flags, int startId) { // The service is starting, due to a call to startService() return mStartMode; } @Override public IBinder onBind(Intent intent) { // A client is binding to the service with bindService() return mBinder; } @Override public boolean onUnbind(Intent intent) { // All clients have unbound with unbindService() return mAllowRebind; } @Override public void onRebind(Intent intent) { // A client is binding to the service with bindService(), // after onUnbind() has already been called } @Override public void onDestroy() { // The service is no longer used and is being destroyed }}
服务的整个生命周期从调用 onCreate()
开始起,到 onDestroy() 返回时结束。与 Activity 类似,服务也在 onCreate()
中完成初始设置,并在 onDestroy()
中释放所有剩余资源。例如,音乐播放服务可以在 onCreate()
中创建用于播放音乐的线程,然后在 onDestroy()
中停止该线程。无论服务是通过 startService()
还是 bindService()
创建,都会为所有服务调用 onCreate()
和 onDestroy()
方法。
服务的有效生命周期从调用 onStartCommand()
或 onBind()
方法开始。每种方法均有 Intent 对象,该对象分别传递到 startService()
或 bindService()
。
对于启动服务,有效生命周期与整个生命周期同时结束(即便是在 onStartCommand()
返回之后,服务仍然处于活动状态)。对于绑定服务,有效生命周期在 onUnbind()
返回时结束。
五、四大组件之内容提供者-Content Provider
(一)ContentResolver 的基本用法
内容提供者是android应用程序的基本构建块之一,它们封装数据并将封装的数据通过单一的ContentResolver接口提供给应用程序。当你需要在多个应用之间共享数据的时候就需要用到内容提供者。例如,手机中的联系人数据会被多个应用所用到所以必须要用内容提供者存储起来。如果你不需要在多个应用之间共享数据,你可以使用一个数据库,直接通过SQLite数据库。 当通过content resolver
发送一个请求时,系统会检查给定的URI并把请求传给有注册授权的Contentprovider
。 UriMatcher
类有助于解析uri。
下面activity文件中包含每个基础的生命周期方法。我们添加了两个新的方法,onClickAddName() 和 onClickRetrieveStudents() 来让应用程序处理用户交互。
public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.menu_main, menu); return true; } public void onClickAddName(View view) { // Add a new student record ContentValues values = new ContentValues(); values.put(StudentsProvider.NAME, ((EditText)findViewById(R.id.editText2)).getText().toString()); values.put(StudentsProvider.GRADE, ((EditText)findViewById(R.id.editText3)).getText().toString()); Uri uri = getContentResolver().insert( StudentsProvider.CONTENT_URI, values); Toast.makeText(getBaseContext(), uri.toString(), Toast.LENGTH_LONG).show(); } public void onClickRetrieveStudents(View view) { // Retrieve student records String URL = "content://com.example.provider.College/students"; Uri students = Uri.parse(URL); Cursor c = managedQuery(students, null, null, null, "name"); if (c.moveToFirst()) { do{ Toast.makeText(this, c.getString(c.getColumnIndex(StudentsProvider._ID)) + ", " + c.getString(c.getColumnIndex( StudentsProvider.NAME)) + ", " + c.getString(c.getColumnIndex( StudentsProvider.GRADE)), Toast.LENGTH_SHORT).show(); } while (c.moveToNext()); } }}
在新建包contentprovider下创建新的文件StudentsProvider.java。以下是StudentsProvider.java的内容。
public class StudentsProvider extends ContentProvider { static final String PROVIDER_NAME = "com.example.provider.College"; static final String URL = "content://" + PROVIDER_NAME + "/students"; static final Uri CONTENT_URI = Uri.parse(URL); static final String _ID = "_id"; static final String NAME = "name"; static final String GRADE = "grade"; private static HashMap<String, String> STUDENTS_PROJECTION_MAP; static final int STUDENTS = 1; static final int STUDENT_ID = 2; static final UriMatcher uriMatcher; static{ uriMatcher = new UriMatcher(UriMatcher.NO_MATCH); uriMatcher.addURI(PROVIDER_NAME, "students", STUDENTS); uriMatcher.addURI(PROVIDER_NAME, "students/#", STUDENT_ID); } /** * 数据库特定常量声明 */ private SQLiteDatabase db; static final String DATABASE_NAME = "College"; static final String STUDENTS_TABLE_NAME = "students"; static final int DATABASE_VERSION = 1; static final String CREATE_DB_TABLE = " CREATE TABLE " + STUDENTS_TABLE_NAME + " (_id INTEGER PRIMARY KEY AUTOINCREMENT, " + " name TEXT NOT NULL, " + " grade TEXT NOT NULL);"; /** * 创建和管理提供者内部数据源的帮助类. */ private static class DatabaseHelper extends SQLiteOpenHelper { DatabaseHelper(Context context){ super(context, DATABASE_NAME, null, DATABASE_VERSION); } @Override public void onCreate(SQLiteDatabase db) { db.execSQL(CREATE_DB_TABLE); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { db.execSQL("DROP TABLE IF EXISTS " + STUDENTS_TABLE_NAME); onCreate(db); } } @Override public boolean onCreate() { Context context = getContext(); DatabaseHelper dbHelper = new DatabaseHelper(context); /** * 如果不存在,则创建一个可写的数据库。 */ db = dbHelper.getWritableDatabase(); return (db == null)? false:true; } @Override public Uri insert(Uri uri, ContentValues values) { /** * 添加新学生记录 */ long rowID = db.insert( STUDENTS_TABLE_NAME, "", values); /** * 如果记录添加成功 */ if (rowID > 0) { Uri _uri = ContentUris.withAppendedId(CONTENT_URI, rowID); getContext().getContentResolver().notifyChange(_uri, null); return _uri; } throw new SQLException("Failed to add a record into " + uri); } @Override public Cursor query(Uri uri, String[] projection, String selection,String[] selectionArgs, String sortOrder) { SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); qb.setTables(STUDENTS_TABLE_NAME); switch (uriMatcher.match(uri)) { case STUDENTS: qb.setProjectionMap(STUDENTS_PROJECTION_MAP); break; case STUDENT_ID: qb.appendWhere( _ID + "=" + uri.getPathSegments().get(1)); break; default: throw new IllegalArgumentException("Unknown URI " + uri); } if (sortOrder == null || sortOrder == ""){ /** * 默认按照学生姓名排序 */ sortOrder = NAME; } Cursor c = qb.query(db, projection, selection, selectionArgs,null, null, sortOrder); /** * 注册内容URI变化的监听器 */ c.setNotificationUri(getContext().getContentResolver(), uri); return c; } @Override public int delete(Uri uri, String selection, String[] selectionArgs) { int count = 0; switch (uriMatcher.match(uri)){ case STUDENTS: count = db.delete(STUDENTS_TABLE_NAME, selection, selectionArgs); break; case STUDENT_ID: String id = uri.getPathSegments().get(1); count = db.delete( STUDENTS_TABLE_NAME, _ID + " = " + id + (!TextUtils.isEmpty(selection) ? " AND (" + selection + ')' : ""), selectionArgs); break; default: throw new IllegalArgumentException("Unknown URI " + uri); } getContext().getContentResolver().notifyChange(uri, null); return count; } @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { int count = 0; switch (uriMatcher.match(uri)){ case STUDENTS: count = db.update(STUDENTS_TABLE_NAME, values, selection, selectionArgs); break; case STUDENT_ID: count = db.update(STUDENTS_TABLE_NAME, values, _ID + " = " + uri.getPathSegments().get(1) + (!TextUtils.isEmpty(selection) ? " AND (" +selection + ')' : ""), selectionArgs); break; default: throw new IllegalArgumentException("Unknown URI " + uri ); } getContext().getContentResolver().notifyChange(uri, null); return count; } @Override public String getType(Uri uri) { switch (uriMatcher.match(uri)){ /** * 获取所有学生记录 */ case STUDENTS: return "vnd.android.cursor.dir/vnd.example.students"; /** * 获取一个特定的学生 */ case STUDENT_ID: return "vnd.android.cursor.item/vnd.example.students"; default: throw new IllegalArgumentException("Unsupported URI: " + uri); } }}
以下是修改后的AndroidManifest.xml文件。这里添加了<provider.../>
标签来包含我们的内容提供者:
<?xml version="1.0" encoding="utf-8"?><manifest xmlns:android="http://schemas.android.com/apk/res/android" package="cn.uprogrammer.contentprovider" android:versionCode="1" android:versionName="1.0" > <uses-sdk android:minSdkVersion="8" android:targetSdkVersion="22" /> <application android:allowBackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" > <activity android:name="cn.uprogrammer.contentprovider.MainActivity" android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <provider android:name="StudentsProvider" android:authorities="com.example.provider.College" > </provider> </application></manifest>
下面是res/layout/activity_main.xml文件的内容:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".MainActivity"> <TextView android:id="@+id/textView1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="内容提供者实例" android:layout_alignParentTop="true" android:layout_centerHorizontal="true" android:textSize="30dp" /> <TextView android:id="@+id/textView2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="www.uprogrammer.cn" android:textColor="#ff87ff09" android:textSize="30dp" android:layout_below="@+id/textView1" android:layout_centerHorizontal="true" /> <ImageButton android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/imageButton" android:src="@drawable/ic_launcher" android:layout_below="@+id/textView2" android:layout_centerHorizontal="true" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/button2" android:text="添加" android:layout_below="@+id/editText3" android:layout_alignRight="@+id/textView2" android:layout_alignEnd="@+id/textView2" android:layout_alignLeft="@+id/textView2" android:layout_alignStart="@+id/textView2" android:onClick="onClickAddName"/> <EditText android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/editText" android:layout_below="@+id/imageButton" android:layout_alignRight="@+id/imageButton" android:layout_alignEnd="@+id/imageButton" /> <EditText android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/editText2" android:layout_alignTop="@+id/editText" android:layout_alignLeft="@+id/textView1" android:layout_alignStart="@+id/textView1" android:layout_alignRight="@+id/textView1" android:layout_alignEnd="@+id/textView1" android:hint="姓名" android:textColorHint="@android:color/holo_blue_light" /> <EditText android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/editText3" android:layout_below="@+id/editText" android:layout_alignLeft="@+id/editText2" android:layout_alignStart="@+id/editText2" android:layout_alignRight="@+id/editText2" android:layout_alignEnd="@+id/editText2" android:hint="年级" android:textColorHint="@android:color/holo_blue_bright" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="查询" android:id="@+id/button" android:layout_below="@+id/button2" android:layout_alignRight="@+id/editText3" android:layout_alignEnd="@+id/editText3" android:layout_alignLeft="@+id/button2" android:layout_alignStart="@+id/button2" android:onClick="onClickRetrieveStudents"/></RelativeLayout>
一旦你完成数据库记录的添加,是时候向内容提供者要求给回这些记录。点击”查询”按钮,这将通过实现的 query() 方法来获取并显示所有的数据记录。
你可以在 MainActivity.java 中提供回调方法,来编写更新和删除的操作,并修改用户界面来添加更新和删除操作。
你可以通过这种方式使用已有的内容提供者,如通讯录。你也可以通过这种方式来开发一个优秀的面向数据库的应用,你可以像上面介绍的实例那样来执行素有的数据库操作,如读、写、更新和删除。
以上内容或多或少的介绍了android的四大组件,后续会继续探索《第一行代码》的精彩内容,敬请期待…
- 《第一行代码》阅读记录—四大组件
- 《第一行代码》阅读记录—UI设计
- 《第一行代码》阅读记录—Git和Notification
- Android学习记录-《第一行代码》阅读笔记(2)
- Android学习记录—《第一行代码》阅读笔记(4)
- 第一行代码阅读推荐
- 第一行代码--知识点记录
- 四大组件及阅读优秀代码-Android
- Intent的用法——第一行代码阅读笔记
- activity的生命周期——第一行代码阅读笔记
- Android第一行代码(2版)——阅读笔记
- 《第一行代码Android》阅读笔记
- Android-《第一行代码》阅读收获
- 第一行代码阅读笔记---基本知识
- 《第一行代码》学习记录(一)
- 《第一行代码》学习记录(二)
- 《第一行代码》学习记录(三)
- 《第一行代码》学习记录(四)
- visiual studio添加现有文件夹的方法
- mybatis之关联查询
- 古典密码(凯撒密码/维吉尼亚密码/Play fair密码/Hill密码)的实现(MFC界面)
- 一道折半算法变种的面试题
- AR技术
- 《第一行代码》阅读记录—四大组件
- Hibernate框架基础——多对多关联关系映射
- PHP学习总结(12)——PHP入门篇之变量
- iOS 推送全解析,你不可不知的所有 Tips!
- Proof of Optimality for Huffman Coding
- 安卓自定义View基础-坐标系
- Java技术——String类为什么是不可变的
- 找到项目在Xcode中打包后的文件
- STM32移植lwip之官方工程修改