(内容来自《Android第一行代码(第二版)》)
附:Android基础之四大组件
本文目录
1. Android多线程编程
2. 服务的基本用法
2.1 定义一个服务
2.2 启动和停止服务
2.3 活动和服务进行通信
3. 服务的生命周期
4. 更多关于服务
4.1 使用前台服务
4.2 使用IntentService
5. 服务实践演示Dmeo
分割线
1. Android多线程编程
Android多线程编程
2. 服务的基本用法
2.1 定义一个服务
我们首先新建一个ServiceTest项目
然后右击com.example.servicetest新建一个服务
服务命名为MyService
Exported
属性表示是否允许除了当前之外的其他程序访问这个服务Enabled
属性表示是否启用这个服务这里将两个属性都选上
下面是自动生成的代码
public class MyService extends Service {
public MyService() {
}
@Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
throw new UnsupportedOperationException("Not yet implemented");
}
}
可以看到,MyService是继承自Service类的,说明这是一个服务
其中有个onBind()方法,这是Service中唯一的一个抽象方法,所以必须在子类中实现
下面我们重写Service中的另外一些方法
public class MyService extends Service {
public MyService() {
}
@Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
throw new UnsupportedOperationException("Not yet implemented");
}
@Override
public void onCreate(){
super.onCreate();
}
@Override
public int onStartCommand(Intent intent,int flags,int startId){
return super.onStartCommand(intent,flags,startId);
}
@Override
public void onDestroy(){
super.onDestroy();
}
}
可以看到,这里我们又重写了onCreate()
、onStartCommand()
和onDestroy()
这3个方法,它们是每个服务中最常用到的3个方法了。其中:
-
onCreate()
方法会在服务创建的时候调用 -
onStartCommand()
方法会在每次服务启动的时候调用 -
onDestroy()
方法会在服务销毁的时候调用。
通常情况下,如果我们希望服务一旦启动就立刻去执行某个动作,就可以将逻辑写在onStartCommand()
方法里。而当服务销毁时,我们又应该在onDestroy()
方法中去回收那些不再使用的资源。
另外需要注意,每一个服务都需要在AndroidManifest.xml文件中进行注册才能生效,不知道你有没有发现,这是Android四大组件共有的特点。不过相信你已经猜到了,智能的Android Studio早已自动帮我们将这一步完成了。打开AndroidManifest.rml文件瞧一瞧,代码如下所示:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.servicetest">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service
android:name=".MyService"
android:enabled="true"
android:exported="true">
</service>
</application>
</manifest>
这样的话就已经将一个服务定义好了。
2.2 启动和停止服务
接下来我们考虑如何去启动以及停止这个服务
-
修改activity_main.xml代码
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/start_service"
android:text="Start Service"/>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/stop_service"
android:text="Stop Service"/>
</LinearLayout>
-
修改MainActivity
public class MainActivity extends AppCompatActivity implements View.OnClickListener{
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button startService = (Button) findViewById(R.id.start_service);
Button 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;
}
}
}
可以看到:
这里在onCreate()
方法中分别获取到了Start Service
按钮和Stop Service
按钮的实例,并给它们注册了点击事件。
然后在Start Service
按钮的点击事件里,我们构建出了一个Intent对象,并调用startService()方法来启动 MyService这个服务。在Stop Serivce
按钮的点击事件里,我们同样构建出了一个Intent对象,并调用stopService()方法来停止 MyService这个服务。
startService()
和stopService()
方法都是定义在 Context类中的,所以我们在活动里可以直接调用这两个方法。注意,这里完全是由活动来决定服务何时停止的,如果没有点击Stop Service
按钮,服务就会一直处于运行状态。
那服务有没有什办法让自已停止下来呢?当然可以,只需要在MyService的任何一个位置调用stopSelf()
方法就能让这个服务停止下来了。
那么接下来又有一个问题需要思考了,我们如何才能证实服务已经成功启动或者停止了呢?最简单的方法就是在MyService的几个方法中加入打印日志,如下所示
现在运行程序,点击Start Service
按钮,观察logcat中的打印日志
MyService中的onCreate()
和onStartCommand()
方法都执行了,说明这个服务启动成功了。
然后再点击一下Stop Service
按钮,观察logcat中的日志
说明MyService确实成功停止下来了
话说回来,虽然我们已经学会了启动服务以及停止服务的方法,不知道你心里现在有没有个疑惑,那就是onCreate()
方法和onStartCommand()
方法到底有什么区别呢?因为刚刚点击Start Service
按钮后两个方法都执行了。
其实onCreate()
方法是在服务第一次创建的时候调用的,而onStartCommand()
方法则在每次启动服务的时候都会调用,由于刚オ我们是第一次点击Start Service
按钮,服务此时还未创建过,所以两个方法都会执行,之后如果你再连续多点击几次Start Service
按钮,你就会发现只有onStartCommand()
方法可以得到执行了。
2.3 活动和服务进行通信
虽然服务是在活动里启动的,但在启动了服务之后,活动与服务基本就没有什么关系了。我们在活动里调用startService()方法来启动MyService这个服务,然后MyService的onCreate()和onStartCommand()方法就会得到执行,之后服务会一直处于运行状态,但具体运行的是什么逻辑,活动就控制不了了。这就类似于活动通知了服务一下:“你可以启动了!”然后服务就去忙自己的事情了,但活动并不知道服务到底去做了什么事情,以及完成得如何。
那么有没有什么办法能让活动和服务的关系更紧密一些呢?例如在活动中指挥服务去干什么,服务就去干什么。当然可以,这就需要借助我们刚刚忽略的onBind()
方法了。
比如说,目前我们希望在MyService
里提供一个下载功能,然后在活动中可以决定何时开始下载,以及随时查看下载进度。实现这个功能的思路是创建一个专门的Binder
对象来对下载功能进行管理
-
修改MyService中的代码:
可以看到,这里我们新建了一个DownloadBinder
类,并让它继承自Binder
,然后在它的内部提供了开始下载以及查看下载进度的方法。当然这只是两个模拟方法,并没有实现真正的功能,我们在这两个方法中分别打印了一行日志。
接着,在MyService
中创建了DownloadBinder
的实例,然后在onBind()
方法里返回了这个实例,这样MyService
中的工作就全部完成了。
下面就要看一看,在活动中如何去调用服务里的这些方法了。先需要在布局文件里新增两个按钮
-
修改 activity_main.xml中的代码
这两个按钮分别是用于绑定服务和取消绑定服务的,那到底是谁需要去和服务绑定呢?当然就是活动了,当一个活动和服务绑定了之后,就可以调用该服务里的Binder提供的方法了。
-
修改MainActivity
可以看到,这里我们首先创建了一个ServiceConnection
的匿名类,在里面重写了onServiceConnected()
方法和onServiceDisconnected()
方法,这两个方法分别会在活动与服务成功绑定以及解除绑定的时候调用。
在onServiceConnected()
方法中,我们又通过向下转型得到了Down loadBinde
r的实例,有了这个实例,活动和服务之间的关系就变得非常紧密了。现在我们可以在活动中根据具体的场景来调用DownloadBinder
中的任何public()
方法,即实现了指挥服务干什么服务就去干什么的功能。这里仍然只是做了个简单的测试,在onServiceConnected()
方法中调用了DownloadBinder
的startDownload()
和getProgress()
方法。
当然,现在活动和服务其实还没进行绑定呢,这个功能是在Bind Service
按钮的点击事件里完成的。可以看到,这里我们仍然是构建出了一个Intent
对象,然后调用bindService()
方法将MainActivity和MyService进行绑定。
bindService()方法接收3个参数
:
第一个参数
就是刚刚构建出的Intent对象
第二个参数
是前面创建出的ServiceConnection的实例
第三个参数
则是一个标志位,这里传入BIND_AUTO_CREATE表示在活动和服务进行绑定后自动创建服务
这会使得MyService中的onCreate()方法得到执行,但onStartCommand()方法不会执行。
然后如果我们想解除活动和服务之间的绑定该怎么办呢?调用一下unbindService()
方法就可以了,这也是 Unbind Service
按钮的点击事件里实现的功能。
运行程序,点击Bind Service按钮,并查看日志
可以看到,首先是MyService
的onCreate()
方法得到了执行,然后startDownload()
和getProgress()
方法都得到了执行,说明我们确实已经在活动里成功调用了服务里提供的方法了。
另外需要注意,任何一个服务在整个应用程序范围内都是通用的,即MyService不仅可以和MainActivity绑定,还可以和任何一个其他的活动进行绑定.而且在绑定完成后它们都可以获取到相同的DownloadBinder实例。
3. 服务的生命周期
之前我们学习过了活动以及碎片的生命周期。类似地,服务也有自己的生命周期,前面我们使用到的 onCreate()、 onStartCommand()、onBind()和 onDestroy()等方法都是在服务的生命周期内可能回调的方法。
一旦在项目的任何位置调用了Context的startService()方法,相应的服务就会启动起来,并回调onStart Command()方法。如果这个服务之前还没有创建过, onCreate()方法会先于onStartCommand()方法执行。服务启动了之后会一直保持运行状态,直到stopService()或stopSelf()方法被调用。注意,虽然每调用一次startService()方法,onStartCommand()就会执行一次,但实际上每个服务都只会存在一个实例。所以不管你调用了多少次 startservice()方法,只需调用一次stopService()或stopSelf()方法,服务就会停止下来了。
另外,还可以调用Context的bindService()来获取一个服务的持久连接,这时就会回调服务中的onBind()方法。类似地,如果这个服务之前还没有创建过,onCreate()方法会先于unBind()方法执行。之后,调用方可以获取到onBind()方法里返回的IBinder对象的实例,这样就能自由地和服务进行通信了。只要调用方和服务之间的连接没有断开,服务就会一直保持运行状态。
当调用了startService()方法后,又去调用stopService()方法,这时服务中的onDestroy()方法就会执行,表示服务已经销毁了。类似地。当调用了bindService()方法后,又去调用 unbindservice()方法, ondestroy()方法也会执行,这两种情况都很好理解。但是需要注意,我们是完全有可能对一个服务既调用了startService()方法,又调用了bindService()方法的,这种情况下该如何才能让服务销毁掉呢?根据 Android系统的机制,一个服务只要被启动或者绑定了之后,就会一直处于运行状态,必须要让以上两种条件同时不满足,服务才能被销毁。所以,这种情况下要同时调用stopService()和unbindService()方法,onDestroy()方法才会执行
这样你就已经把服务的生命周期完整地走了一遍。
4. 更多关于服务
4.1 使用前台服务
服务几乎都是在后台运行的,一直以来它都是默默地做着辛苦的工作。但是服务的系统优先级还是比较低的,当系统出现内存不足的情况时,就有可能会回收掉正在后台运行的服务。如果你希望服务可以一直保持运行状态,而不会由于系统内存不足的原因导致被回收,就可以考虑使用前台服务。
前台服务
和普通服务
最大的区別就在于,它会一直有一个正在运行的图标在系统的状态栏显示,下拉状态栏后可以看到更加详细的信息,非常类似于通知的效果。当然有时候你也可能不仅仅是为了防止服务被回收掉才使用前台服务的,有些项目由于特殊的需求会要求必须使用前台服务,比如说彩云天气这款天气预报应用,它的服务在后台更新天气数据的同时,还会在系统状态栏一直显示当前的天气信息。
下面来看一下如何创建一个前台服务
-
修改MyService中的代码
在onCreate()方法中构建出一个Notification对象后,调用startForeground()方法
这个方法接收
两个参数
第一个参数
是通知的id,类似于notify()方法的第一个参数第二个参数
则是构建出的Notification对象调用startForeground()方法就会让MyService变成一个前台服务,并在系统状态栏显示出来。
重新运行程序并点击Start Service按钮,下拉状态栏就可以看到该通知的详细内容
4.2 使用IntentService
在前面我们已经知道了,服务中的代码都是默认运行在主线程当中的,如果直接在服务里去处理一些耗时的逻辑,就很容易出现ANR
(Application Not Responding)的情况。
这时就需要用到Android多线程编程的技术了,我们应该在每个具体的方法里开启一个子线程,然后在这里去处理那些耗时的逻辑。因此,一个比较标准的服务就可以写成如下形式:
但是这种服务一旦启动之后就会一直处于运行状态,必须调用stopService()
或者stopSelf()
方法才能让服务停止下来。所以,如果想要实现让一个服务在执行完毕后自动停止的功能,就可以这样写:
这种写法并不复杂,但是总会有一些程序员忘记开启线程,或者忘记调用stopSelf()方法,为了可以简单地创建一个异步的、会自动停止的服务,Android专门提供了一个IntentService类,这个类就很好地解决了前面提到的两种尴尬,下面我们来看一下它的用法
-
新建一个MyIntentService类继承自IntentService
代码如下:
public class MyIntentService extends IntentService {
public MyIntentService(){
super("MyIntentService");//调用父类的有参构造函数
}
@Override
protected void onHandleIntent(Intent intent){
//打印当前线程的id
Log.d("MyIntentService","Thread id is"+Thread.currentThread().getId());
}
@Override
public void onDestroy(){
super.onDestroy();
Log.d("MyIntentService","onDestroy executed");
}
}
这里首先要提供一个无参的构造函数,并且必须在其内部调用父类的有参构造函数。然后要在子类中去实现 onHandleIntent()这个抽象方法,在这个方法中可以去处理一些具体的逻辑,而且不用担心ANR的问题,因为这个方法已经是在子线程中运行的了。这里为了证实一下,我们在onHandleIntent()方法中打印了当前线程的id。另外根据IntentService的特性,这个服务在运行结束后应该是会自动停止的,所以我们又重写了onDestroy()方法,在这里也打印了一行日志,以证实服务是不是停止掉了。
-
接下来修改activity_main.xml中的代码
加入一个用于启动MylntentService这个服务的按钮
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
...
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/start_intent_service"
android:text="Start IntentService"/>
</LinearLayout>
-
修改MainActivity中的代码
可以看到,我们在Start IntentService按钮的点击事件里面去启动MyIntentService这个服务,并在这里打印了一下主线程的id,稍后用于和IntentService进行对比,你会发现,其实IntentService的用法和普通的服务没什么两样。
-
最后不要忘了在AndroidManifest.xml中进行注册
现在重新运行程序,点击Start IntentService按钮,查看打印日志
可以看到,不仅MyIntentService和MainActivity所在的线程id不一样,而且onDestroy()方法也得到了执行,说明MyIntentService在运行完毕后确实自动停止了。