第十七章 Android 常见的Service

1. 引言

现实生活中的服务很常见,各式各样的人都在从事服务这个行业。但是Android中的服务呢?Android中的服务在哪里?我们什么时候需要服务呢?Android 中的服务怎么用?这就是这篇文章想要表述的内容。

2. 服务

服务(Service)是Android 实现程序后台运行的解决方案。看完这句话,马上就知道,服务在后台。没错,Android 中的服务是存在后台中的,它适合去执行哪些不需要与用户交互的但是还被要求长期运行的任务。比如,你在聊QQ的时候,喜欢听音乐,这时候你的音乐就是在后台挂着,即使你开了另外一个应用程序,当前的应用程序还是不会被关闭。
  不过需要说明的是,Service服务不是运行在一个独立的程序中的,而是依赖于创建服务的这个应用程序中的。如果这个程序被杀死,服务也会停止。
  前面说服务是在后台的。但是实际上服务不会自动开启线程,所有的服务都是在主线程中运行的。而我们知道主线程不能执行耗时的操作,否则会引起线程阻塞,所以我们需要在服务上创建子线程,由子线程去实现需要的具体工作。

3. 服务的用法

3.1 服务的生命周期

因为我们知道Service是依赖于应用程序的,应用程序终止了,服务也终止了。所以了解Service的生命周期非常重要。

service的生命周期

Service的生命周期有两种:

  1. Started Service的生命周期:
  • onCreate() 创建服务。
  • onStartCommand() 服务开始运行
  • onDestory() 服务停止
    【说明】
  • 程序调用:context.startService()会触发执行生命周期中的onCreate(),onStartCommond()的回调方法,此时服务正式运行。
  • 如果service没有运行,则安卓会先调用onCreate()然后再调用onStartCommond()方法,所以onStartCommand()可能会被调用很多次。
  • 如果在程序中调用:context.stopService()会触发执行Service生命周期中的onDestroy()回调方法,会让服务停止。
  • stopService()的时候直接onDestroy,如果是调用者自己直接退出而没有调用stopService()的话,Service会一直在后台运行。该Service的调用者再启动该Service后可以通过stopService关闭Service;stopSelf()
  • 所以StartService的生命周期为:onCreate --> onStartCommand(可多次调用) --> onDestroy。
  1. Bound Service的生命周期:
  • onCreate()创建服务
  • onBind() 绑定服务,服务开始运行
  • onUnbind() 解绑服务
  • onDestory() 服务停止
    【说明】
  • 在程序中调用:context.bindService()会触发执行Service生命周期中的onCreate()、onBind()回调方法,此时服务开始运行;
  • onBind将返回给客户端一个IBind接口实例,IBind允许客户端回调服务的方法,比如得到Service运行的状态或其他操作。此后调用者(Context,例如Activity)会和Service绑定在一起;
  • 如果调用Service的调用者Context退出了,那么会依次调用Service生命周期中的onUnbind()、onDestroy()回调方法,会让服务停止;
  • 所以BindService的生命周期为:onCreate --> onBind(只一次,不可多次绑定) --> onUnbind --> onDestory。
    【备注:】
  • Service是不能自己启动的,只有通过 Context 对象调用startService() 和bindService() 方法来启动。
  • 在Service每一次的开启关闭过程中,只有onStartCommand()可被多次调用(通过多次startService调用),其他onCreate()、onBind()、onUnbind()、onDestory()在一个生命周期中只能被调用一次。
  • Service可以在和多场合的应用中使用,比如播放多媒体的时候用户启动了其他Activity这个时候程序要在后台继续播放,比如检测SD卡上文件的变化,再或者在后台记录你地理信息位置的改变等等,总之服务总是藏在后头的。

3.2 服务的概念总结

  • Service在后台运行,不可以与用户直接交互;
  • 一个服务不是一个单独的进程。服务对象本身并不意味着它是在自己的进程中运行,除非另有规定,否则它与运行程序是同在一个进程中;
  • 一个服务不是一个单独的线程。Service和其他组件一样,默认情况下,Service中的所有代码都是运行在主线程中;
  • Service存在的价值虽然不如Activity那么清晰。但是一般都让Service执行耗时较长的操作。例如:播放音乐、下载文件、上传文件等等。但是因为Service默认运行在主线程中,因此不能直接用它来做耗时的请求或者动作,最好在Service中启动新线程来运行耗时的任务;
  • 需要通过某一个Activity或其他Context对象来启动Service。context.startService() 或 context.bindService();
  • Service很大程度上充当了应用程序后台线程管理器的角色。(如果Activity中新开启一个线程,当该Acitivyt关闭后,该线程依然在工作,但是与开启它的Activity失去联系。也就是说此时的这个线程处于失去管理的状态。但是使用Service,则可以对后台运行的线程有效地管理。)

3.3 服务的面试总结点

  1. 为什么不使用后台线程而使用Service?
  • Service可以放在独立的进程中,所以更安全;
  • 使用Service可以依赖现有的binder机制,不需要在应用层面上处理线程同步的繁杂工作;
  • 系统可以重新启动异常死去的Service。
  1. Service 与 Activity 的相同点与不同点:
  • 不同点:Activity是与用户交互的组件,即可以看到UI界面,而Service是在后台运行、无需界面;
  • 相同点:使用Activity 时我们需要在配置文件中声明<activity>标签,同样的使用Service 也需要在配置文件中声明<service>标签。都具有一定的生命周期。

3.4 服务的基本用法

3.4.1 定义一个服务

项目新建一个服务,包名->New ->Service->Service ,然后弹出一个界面,

创建服务

这里将创建的Service命名为MyService,Exproted属性表示是否允许其他程序访问这个服务,Enable属性表示是否启用这个服务,将两个都勾选然后Finish()创建。


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");
    }
}

定义的这个类继承Service,创建完之后只有一个onBind()方法。这个方法是Service中的唯一的一个抽象方法。定义完了服务之后,自然要再服务中处理需要的操作。然后重写Service的另一些方法。


public class MyService extends Service {
 
....

    @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()方法会在启动服务的时候调用,onDestory()方法会在服务销毁的时候调用。
  通常情况下,我们在启动服务之后,在onStartCommand()的方法里面编写代码逻辑,当服务销毁之后,应该在onDestory()的方法里面回收哪些不再使用的资源。
  最后还必须得在AndroidMainfest.xml文件中注册。安卓四大组件都必须要再这里注册。因为我们创建代码的时候,Android Studio已经帮我们创建好了。

    <application>
....
        <service
            android:name=".MyService"
            android:enabled="true"
            android:exported="true"></service>
    </application>

到这里就定义完了一个Service服务了。

3.4.2 启动和停止服务

定义完成服务之后,就需要考虑怎么去启动和停止这个服务。主要是借助Intent的方法来启动和停止服务。
1.activity_main.xml文件中

<?xml version="1.0" encoding="utf-8"?>
<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/btn_start_service"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:text="开启服务"/>

    <Button
        android:id="@+id/btn_stop_service"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:text="停止服务"/>


</LinearLayout>

2.MainActivity.java

public class MainActivity extends AppCompatActivity implements OnClickListener{

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button btn_start_service= (Button) findViewById(R.id.btn_start_service);
        Button btn_stop_service= (Button) findViewById(R.id.btn_stop_service);

        btn_start_service.setOnClickListener(this);
        btn_stop_service.setOnClickListener(this);
    }

    @Override
    public void onClick(View view) {
        switch (view.getId()){
            case R.id.btn_start_service:
                Intent intent=new Intent(this,MyService.class);
                startService(intent);
                break;
            case R.id.btn_stop_service:
                Intent intent1=new Intent(this,MyService.class);
                stopService(intent1);
                break;
        }
    }
}

通过startService()和stopService这两个方法来启动和停止service。
3.MyService.java 显示执行步骤


public class MyService extends Service {
    private static final String TAG = "MyService";

    @Override
    public void onCreate() {
        super.onCreate();
        Log.e(TAG, "onCreate: " );
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.e(TAG, "onStartCommand: " );
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.e(TAG, "onDestroy: " );
    }

}

打印结果:

启动和停止服务

当然你可以多次点击开启服务的按钮发现onStartCommand()的方法被多次执行,点击关闭服务的按钮,发现没动静。

3.4.2 活动和服务进行通信

之前在MyService中,我介绍了新添加的几个方法,而没有说onBind()方法。那这个方法是干什么用的呢?在上面介绍定义服务的时候,点击开始服务的时候,MyService中的onCreate() 和onStartCommand()方法就会执行,直到onDestory()方法调用才销毁,那么在这之间的过程怎么控制呢?假设我想控制活动去指挥服务怎么办,这时候就需要用到onBind()方法。
1.新建一个MyBindService的方法,然后定义一个内部类,在内部类中定义两个方法,然后实例化内部类,最后在onBind()方法返回内部类的实例。

public class MyBindService extends Service {
    private static final String TAG = "MyBindService";
    public MyBindService() {
    }

    @Override
    public IBinder onBind(Intent intent) {
        return  mBinder;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        Log.e(TAG, "onCreate: " );
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.e(TAG, "onStartCommand: " );
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.e(TAG, "onDestroy: " );
    }
    private  DownloadBinder mBinder=new DownloadBinder();

    class DownloadBinder extends Binder{
        public  void  startDownload(){
            Log.e(TAG, "startDownload: 开始下载");
        }
        public  int  getProgress(){
            Log.e(TAG, "getProgress: 当前下载量" );
            return 0;
        }

    }
}

2.在activity_main2.xml,两个按钮,分别的绑定和取消服务的。这时候就需要Activity来和Service进行绑定了。

<?xml version="1.0" encoding="utf-8"?>
<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/btn_start_service"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:text="绑定服务"/>

    <Button
        android:id="@+id/btn_stop_service"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:text="取消服务"/>
</LinearLayout>

3.定义一个Main2Activity,当Activity和Service进行绑定后,就可以调用它Binder提供的方法。


public class Main2Activity extends AppCompatActivity implements View.OnClickListener {

    private MyBindService.DownloadBinder downloadBinder;
    private ServiceConnection connection=new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
            downloadBinder = (MyBindService.DownloadBinder) iBinder;
            downloadBinder.startDownload();
            downloadBinder.getProgress();
        }

        @Override
        public void onServiceDisconnected(ComponentName componentName) {

        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main2);
        Button btn_start_service= (Button) findViewById(R.id.btn_start_service);
        Button btn_stop_service= (Button) findViewById(R.id.btn_stop_service);

        btn_start_service.setOnClickListener(this);
        btn_stop_service.setOnClickListener(this);
    }

    @Override
    public void onClick(View view) {
        switch (view.getId()) {
            case R.id.btn_start_service:
                Intent intent = new Intent(this, MyBindService.class);
              bindService(intent,connection,BIND_AUTO_CREATE);//绑定服务
                break;
            case R.id.btn_stop_service:
              unbindService(connection); //解绑服务
                break;
        }
    }
}

这里首先创建一个ServiceConnection的匿名类,然后在里面重写onServiceConnected()和onServiceDisconnected()方法,这两个方法会在Activity与Service成功绑定和连接断开的时候调用。在onServiceConnected()方法中,我们通过向下转型得到DownloadBinder的实例,有了这个实例,活动和服务之间的关系就变得非常紧密了。现在我们可以在Activity中根据具体的场景来调用DownloadBinder中的任何一个public 方法,实现了Activity指挥Service去干的功能。
  当然,现在Activity和Service还没有绑定,这个功能是在点击 绑定按钮的时候触发点击事件时完成的。我们仍然用Intent,然后调用bindService()方法将Main2Activity和MyService进行绑定。bindService()方法接收3个参数。,第一个参数是intent对象,第二个是之前创建的ServiceConnection实例。第三个参数是一个标志位实例 BIND_AUTO_CREATE表示Activity和Service绑定之后自动创建service。这会使得onCreate()方法得到实现,onStartCommand()方法不会得到执行。

绑定服务打印日志

4. 服务的高级技巧使用

4.1 使用前台服务

服务几乎都是在后台运行的,一直以来都是吃的少,干得多,啥脏活累死都揽着干,但是它的优先级还是相当低的。当系统的内存比较少的时候,就会回收掉正在后台运行的服务。如果你想保持服务的运行状态,而不会由于系统的内存不足而回收掉。这时候就可以考虑前台服务。前台服务和普通服务的最大区别,它会一直有一个运行图标在状态栏显示,例如QQ,微信,网易云音乐等。需要下拉状态栏才能看到,非常类似于通知效果。当然有时候它的作用可不仅仅是为了防止服务被回收掉才使用前台服务的,有些项目由于特殊的要求必须使用前台服务,例如网易云的点击播放按钮,显示当前播放的曲目。
创建一个前台服务:
1.修改MyService的服务


public class MyBindService extends Service {
···
    @Override
    public void onCreate() {
        super.onCreate();
        Log.e(TAG, "onCreate: " );
        Intent intent= new Intent(this,Main2Activity.class);
        PendingIntent pi=PendingIntent.getActivity(this,0,intent,0);
        Notification notification= new NotificationCompat.Builder(this)
                .setContentTitle("标题")
                .setContentText("内容")
                .setWhen(System.currentTimeMillis())
                .setSmallIcon(R.mipmap.ic_launcher_round)
                .setLargeIcon(BitmapFactory.decodeResource(getResources(),R.mipmap.ic_launcher))
                .setContentIntent(pi)
                .build();
        startForeground(1,notification);

    }

 
}

这里只是修改onCreate()方法中的代码,通过构建Notification对象,来将通知显示出来。调用startForeground()方法会让MyService变成一个前台服务,并在系统状态栏显示。运行效果如下所示。

前台服务

4.2 使用 IntentService

在本文的开头我们知道Service服务的代码都是在主程序中运行的,如果直接在服务中去处理一些耗时操作,很容易出现ANR(Application Not Responsing)程序无响应。所以这时候就需要用到Android 多线程编程的技术,我们应该在服务的每个具体的方法里面开启一个子线程。然后在这里去处理哪些耗时的操作。所以标准的服务应该写如下形式:

public class MyBindService extends Service {
 @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.e(TAG, "onStartCommand: " );
        new Thread(new Runnable() {
            @Override
            public void run() {
                //处理具体的逻辑
                stopSelf();//停止服务
            }
        }).start();
        return super.onStartCommand(intent, flags, startId);
    }
}

这种服务一旦启动之后,就会一直处于运行状态,所以调用stopSelf()或stopService()方法才能让服务停止。这样写就实现了一个服务在执行完毕之后自动关闭服务的功能。
  但是如果忘记开线程了或者忘记调用 stopSelf()方法了怎么办。为了可以简单创建一个异步的会自动停止的功能。Android 提供了IntentService类。
1.新建一个MyIntentService类


public class MyIntentService extends IntentService {
    public MyIntentService() {
        super("MyIntentService");            //调用父类的有参构造函数
    }

    @Override
    protected void onHandleIntent(@Nullable Intent intent) {
     //打印当前线程的id
        Log.d("MyIntentService","当前线程的id="+Thread.currentThread().getId());
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.d("MyIntentService","onDestroy");

    }
}

这里首先提供了一个无参的构造函数,然后必须在内部调用父类的有参构造函数。然后在子类中实现onHandleIntent()这个抽象方法,在这方法中处理抽象的逻辑,因为这个方法是在子线程中执行的,所以不需要单选ANR的问题。这里打印当前线程的id,另外为了验证服务运行结束之后会自动关闭,重写onDestory()的方法,打印下日志。
2.修改activity_main2.xml文件,添加一个按钮

  <Button
        android:id="@+id/btn_log_service_id"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:text="打印线程的id"/>

3.修改Main2Activity中的代码,使用方法和前面的按钮一样

public class Main2Activity extends AppCompatActivity implements View.OnClickListener {
···
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main2);
····
        Button btn_log_service_id= (Button) findViewById(R.id.btn_log_service_id);
        btn_log_service_id.setOnClickListener(this);
    }

    @Override
    public void onClick(View view) {
        switch (view.getId()) {
           ···
            case R.id.btn_log_service_id:
                //打印主线程的id
                Log.d("Main2Activity", "主线程的id= "+Thread.currentThread().getId());
                Intent intentService = new Intent(this, MyIntentService.class);
                startService(intentService);
                break;
        }
    }
}

4.在AndroidMainfes.xml文件中洪注册

     <service android:name=".bindservice.MyIntentService"/>

运行结果:

IntentService

可以看到MyIntentService 的线程id和主线程的id不一样,而且MyIntentService 的onDestory()方法也得到了执行。说明MyIntentService在运行完毕之后确实自动停止了。集开县城和自动停止与一身。

项目github地址:https://github.com/wangxin3119/myServiceDemo

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,470评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,393评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,577评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,176评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,189评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,155评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,041评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,903评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,319评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,539评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,703评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,417评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,013评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,664评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,818评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,711评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,601评论 2 353

推荐阅读更多精彩内容