Service要点全解析

1、Service概述

Service的主要作用是,让系统可以在后台干一些不与用户交互的操作,这些操作可能会比较耗时,比如去下载一些网络资源等;也可能是一项长期运行的工作,比如说监听电话来电、播放音乐等。初听起来,Service与线程Thread很像,但Service和Thread完全是两个不同的东西啊。

(1)Service不是运行在一个独立的进程中,它和我们的应用程序在同一个进程中;

(2)Service也不是一个线程,相反,Service是运行在主线程的,因此我们不能直接在Service中干上面那些耗时操作,因为它会很耗CPU,阻塞主线程,很容易出现ANR错误(Application
Not Responding),合适的做法是,在Service中开启一个Thread,进行上面的耗时操作。

如果我们要在Service中开启线程进行工作,我们也可以使用Service的一个子类IntentService,IntentService类中已经开启了线程,我们只需要实现一个方法即可,后面具体介绍。

2、Service用法

和Activity类似,我们要使用Service,只需要通过Intent发出请求即可。当然,在使用Service前,记得在AndroidManifest.xml中进行声明。启动方式有两种:

我们先在Activity中加入两组四个按钮,一组为startService的启动按钮和相应的停止按钮;一组为bindService的启动按钮和相应的停止按钮。如图:

启动方式一:startService()

在Activity中,我们如下使用:

public class MainActivity extends Activity implements OnClickListener{

    private Button startServiceBtn,stopServiceBtn,bindServiceBtn,unBindServiceBtn;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        startServiceBtn = (Button) findViewById(R.id.startService);
        stopServiceBtn = (Button) findViewById(R.id.stopService);
        bindServiceBtn = (Button) findViewById(R.id.bindService);
        unBindServiceBtn = (Button) findViewById(R.id.unBindService);
        startServiceBtn.setOnClickListener(this);
        stopServiceBtn.setOnClickListener(this);
        bindServiceBtn.setOnClickListener(this);
        unBindServiceBtn.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
        case R.id.startService://通过startService()的方式启动Service
            Intent intent = new Intent(this,ServiceTest.class);
            startService(intent);
            break;
        case R.id.stopService:
            stopService(new Intent(this,ServiceTest.class));
            break;
        case R.id.bindService://通过bindService()的方式启动Service
            //TODO
            break;
        case R.id.unBindService:
            //TODO
            break;
        }
    }
}

我们的Service如下:

public class ServiceTest extends Service {
    @Override
    public void onCreate() {
        super.onCreate();
        Log.d("TAG", "onCreate");
    }

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

    @Override
    public IBinder onBind(Intent arg0) {
        Log.d("TAG", "onBind");
        return null;
    }

    @Override
    public boolean onUnbind(Intent intent) {
        Log.d("TAG", "onUnbind");
        return super.onUnbind(intent);
    }

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

上面,点击“startService”按钮,我们就通过startService()将传进来的Intent中的Service启动了,启动时Log打印如下:

说明,我们第一次启动Service的时候,会执行onCreate()和onStartCommand()方法,如果我们这个时候,再次点击“startService”按钮,此时打印如下:

可以看到,如果一个Service已经运行了,再次启动这个Service,只会进入onStartCommand(),onCreate()方法只会在第一次启动的时候进行初始化。Service不像我们的Activity,Activity每次通过Intent启动时都会创建一个新的Activity(默认模式下),然后放入到栈中。而同一个Service,应用中只会存在一个实例,在第一次创建时通过onCreate初始化,运行后,再次启动只是进入onStartCommand。

点击“stopService”后,Service被销毁,进入onDestroy()方法。不管前面我们启动了多少次Service,只要在外部调用一次Context.stopService()或者在Service内部自己调用一次stopSelf(),Service就会被销毁。

上面这种启动方式,在启动完Service后,这个Service就开始在后台运行了,同时,也与启动它的Activity失去了联系,因为不能通过ServiceTest service =
new ServiceTest()的方式启动Service,因而我们的Activity中不能获取到ServiceTest的实例,也就不能够控制启动后的Service干Activity想干的事,这个Service只能干它内部定义好的功能,没有与启动它的Activity交互能力。

为了解决与启动Service的组件的通信能力,有一种解决方案就是通过广播的形式,我们在Activity中发出一些想用操作广播,在Service中注册该广播,Service接收到该广播信息后,完成相应的功能。但是这种方案不够优雅,频繁发送广播比较消耗性能,同时,由于广播接受者中的onReceive()中,不能执行长时间的工作,时间超过后,可能就直接跳出了方法。因此,这种方案不是首选。

启动方式二:bindService()

如上面所述,如果要求我们的Service能够和启动Service的组件进行通信,我们可以使用bindService的启动方式。

首先改造Service,Service和组件之间是通过管道IBinder接口进行通信的。

public class ServiceTest extends Service {
    private MyLocalBinder mBinder = new MyLocalBinder();
    @Override
    public void onCreate() {
        super.onCreate();
        Log.d("TAG", "onCreate");
    }
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.d("TAG", "onStartCommand");
        return super.onStartCommand(intent, flags, startId);
    }

    class MyLocalBinder extends Binder{
        public ServiceTest getServiceInstance(){
            return ServiceTest.this;
        }
        //...这里也可以继续写方法对外提供
    }

    @Override
    public IBinder onBind(Intent arg0) {
        Log.d("TAG", "onBind");
        return mBinder;
    }

     //对外提供的访问方法
    public void downLoad(){
        System.out.println("下载操作方法...");
    }

    @Override
    public boolean onUnbind(Intent intent) {
        Log.d("TAG", "onUnbind");
        return super.onUnbind(intent);
    }

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

继续改造我们的Activity,在ASctivity中获得连接通道,如下:

private ServiceTest mService;
    private boolean isConnected = false;//我们在使用bindService时,最好定义一个是否连接的标志,方便我们在组件中通信前的判断操作

    ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName className, IBinder binder) {
            ServiceTest.MyLocalBinder localBinder = (MyLocalBinder)binder;    //先获得管道
            mService = localBinder.getServiceInstance();    //通过管道,拿到Service的实例
            isConnected = true;
            mService.downLoad();//拿到Service实例后想干嘛干嘛
        }

        //注意:这个方法当Service意外运行失败时调用,如系统杀死这个Service或者运行Service时遇到崩溃,调用unBinderService并不会调用该方法
        @Override
        public void onServiceDisconnected(ComponentName arg0) {
            isConnected = false;
        }
    };

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
        case R.id.startService://通过startService()的方式启动Service
            Intent intent = new Intent(this,ServiceTest.class);
            startService(intent);
            break;
        case R.id.stopService:
            stopService(new Intent(this,ServiceTest.class));
            break;
        case R.id.bindService://通过bindService()的方式启动Service
            Intent intent2 = new Intent(this,ServiceTest.class);
            bindService(intent2, connection, BIND_AUTO_CREATE);
            break;
        case R.id.unBindService:
            unbindService(connection);
            break;
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if(isConnected){
            unbindService(connection);
            isConnected = false;
        }
    }

点击“bindService”按钮,我们就通过bindService()将传进来的Intent中的Service启动了,启动时Log打印如下:

我们通过bindService()方法第一次启动后,会进入Service的onCreate()和onBind()方法。如果我们另一个组件(如Activity),又对同一个Service发起了bindService()操作(也就是说在bindService()中传入了不同的ServiceConnection),此时只会进入onBind()方法。也就是说onCreate也只是在Service第一次创建时执行。

我们点击“unBindService”时,打印如下:

此时走的是onUnbind和onDestroy方法。

可以看到,不管通过哪种方式启动Service,同一个Service在整个应用程序中只会有一个实例存在,如果通过bindService启动,获得的IBinder实例也都是同一个。四大组件中,只有在Activity、Service、Content
Providers中通过bindService启动Service。

3、Service生命周期

从上面的打印日志可以看到,两种启动Service方式走的生命周期方法是不同的,官方的生命周期图很好地描述了两种启动方式下的回调:

两种方式都是只有在第一次启动没有运行的Service时,才会进入onCreate()方法。当Service启动后,后面多次继;启动该Service,只会进入onStartCommand()或者onBind()。

(1)当我们通过startService()方法启动Service时,不管前面我们启动了多少次Service,只要在外部调用一次stopService()或者在Service内部自己调用一次stopSelf(),Service就会被销毁;

(2)当我们通过bindService()启动Service时,前面我们多次启动Service后,当没有客户端连接后(即所有客户端发出了unBindService),这个Service将会被系统销毁;

(3)当这个Service既被startService启动,又被bindService启动时,即使当所有连接的客户端断开连接后,Service也不会被销毁,除非再调用一次stopService或内部使用stopSelf(),这个时候Service才会被销毁。或者说如果我们调用了stopService或内部使用stopSelf(),Service也不会被销毁,只有当所有的客户端断开连接后,Service才会被销毁。也就是说,在这种情况下,Service必须在既没有任何Activity关联又停止的情况下,Service才会被销毁。这种情况下的生命周期如下:

注意:为了让我们的Service不过多的浪费CPU资源、内存资源,我们需要在必要的时候进行解除绑定。

(1)当我们的Service只在Activity被用户可见的时候,才与Activity进行交互,那我们应该在Activity的onStart()中bindService,在onStop()中unBindService();

(2)如果我们希望Activity即使在后台时也能够与Service交互,那我们应该在onCreate()中bindService(),在onDestroy()中unBindService(),即Activity在整个生命周期中要与Service交互。

4、IntentService

正如我们第一部分所谈到的,Service和Thread完全是两个不同的东西,Service主要功能是可以在后台执行一些操作,这些操作不需要与用户交互。Service是直接运行在主线中中的,如果我们需要在Service中执行耗时操作,为了避免ANR错误,是需要在Service中开启线程来执行的。对于使用Service有开启线程需求的开发者来说,Android提供了IntentService给用户,IntentService内部已经帮我们开启了线程,我们只需要实现它定义的onHandleIntent()方法,在里面实现我们的功能即可。注意,IntentService不能处理多个线程的请求,但是可以处理多个启动Service的请求。

IntentService提供的功能:

(1)内部创建了一个工作线程,来处理每个启动Service的请求传来的Intent;

(2)内部有一个工作队列,来分别处理多个启动Service的请求,因此避免了多线程的问题;

(3)在所有的请求处理完后,自动停止服务,因此我们不必在Service内部自己写stopSelf();

(4)提供了默认onBind()的实现,直接返回null,意味着IntentService只能通过startService()的方式启动;

(5)提供了默认onStartCommand()的实现,它将我们的Intent放入到了工作队列中,然后执行我们具体实现的onHandleIntent()方法。

一个简单实例如下:

public class IntentServiceTest extends IntentService {
    public IntentServiceTest(){//提供一个默认的构造方法,调用父类构造方法,传入工作线程的名字
        super("IntentServiceTest");
    }

    @Override
    protected void onHandleIntent(Intent arg0) {
        long endTime = System.currentTimeMillis() + 5*1000;
          while (System.currentTimeMillis() < endTime) {
              synchronized (this) {
                  try {
                      wait(endTime - System.currentTimeMillis());
                  } catch (Exception e) {
                  }
              }
          }
    }
}

使用IntentService主要是注意两点,一是定义一个默认构造方法,里面调用父类构造方法,并定义工作线程的名字;第二是实现onHandleIntent()方法,我们在这里面具体实现我们Service需要做的事情。可以看到,如果我们要使用开启线程的Service,IntentService提供了一种非常好的方案,让用户只需要关注与onHandleIntent接口的具体实现即可,同时用工作队列的形式支持多启动服务的访问。

5、让我们的Service到前台运行

我们的Service默认都是在后台默默运行的,用户基本察觉不到有Service在运行。此时,Service的优先级是比较低的,当系统资源不足的时候,很容易被销毁。因此,如果我们想让用户知道有Service在后台运行,如音乐播放器,或者想让Service一直保持运行状态,不容易被系统回收,此时,就可以考虑使用前台Service。前台Service必须要设置有一个Notification到手机的状态栏,类似360一样,因此前台Service能够与用户进行交互,它的优先级也就提高了,在内存不足的时候,不会优先去回收前台Service。

创建前台Service也很简单,就是设置一个Notification到状态栏,如下:

@Override
    public void onCreate() {
        super.onCreate();
        Log.d("TAG", "onCreate");
        Notification notification  = new Notification(R.drawable.ic_launcher,"前台Service通知来了",System.currentTimeMillis());
        Intent notificationIntent = new Intent(this,MainActivity.class);
        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
        notification.setLatestEventInfo(this, "通知标题", "前台Service内容", pendingIntent);
        //设置到前台运行,第一个参数为通知notification的唯一ID
        startForeground(1, notification);
    }

运行效果如下:

如果,我们要移除掉这个前台Service,只需要调用stopService()即可。这个方法并不会停止Service,只是移除掉Notification。

6、如何保证Service不被杀死

(本部分内容来自:http://www.cnblogs.com/rossoneri/p/4530216.html

这个倒是有点流氓软件的意思,但有些特定情况还是需要服务能保持开启不被杀死,当然这样做我还是在程序里添加了关闭服务的按钮,也就是开启了就杀不死,除非在软件里关闭。

服务不被杀死分3种来讨论

1.系统根据资源分配情况杀死服务

2.用户通过 settings -> Apps -> Running -> Stop 方式杀死服务

3.用户通过 settings -> Apps -> Downloaded -> Force
Stop
 方式杀死服务

第一种情况:

用户不干预,完全靠系统来控制,办法有很多。比如 onStartCommand() 方法的返回值设为 START_STICKY ,服务就会在资源紧张的时候被杀掉,然后在资源足够的时候再恢复。当然也可设置为前台服务,使其有高的优先级,在资源紧张的时候也不会被杀掉。

第二种情况:

用户干预,主动杀掉运行中的服务。这个过程杀死服务会通过服务的生命周期,也就是会调用 onDestory() 方法,这时候一个方案就是在 onDestory() 中发送广播开启自己。这样杀死服务后会立即启动。如下:

@Overridepublicvoid onCreate() {
// TODO Auto-generated method stubsuper.onCreate();

mBR = new BroadcastReceiver() {
@Overridepublicvoid onReceive(Context context, Intent intent) {
// TODO Auto-generated method stubIntent a = new Intent(ServiceA.this, ServiceA.class);
startService(a);
}
};
mIF = new IntentFilter();
mIF.addAction("listener");
registerReceiver(mBR, mIF);
}

@Overridepublicvoid onDestroy() {
// TODO Auto-generated method stubsuper.onDestroy();

Intent intent = new Intent();
intent.setAction("listener");
sendBroadcast(intent);

unregisterReceiver(mBR);
}

当然,从理论上来讲这个方案是可行的,实验一下也可以。但有些情况下,发送的广播在消息队列中排的靠后,就有可能服务还没接收到广播就销毁了(这是我对实验结果的猜想,具体执行步骤暂时还不了解)。所以为了能让这个机制完美运行,可以开启两个服务,相互监听,相互启动。服务A监听B的广播来启动B,服务B监听A的广播来启动A。经过实验,这个方案可行,并且用360杀掉后几秒后服务也还是能自启的。到这里再说一句,如果不是某些功能需要的服务,不建议这么做,会降低用户体验。

也就是如下操作,启动服务时,我们开启两个Service,A和B,在A的onCreate中注册B的广播事件,在B的onCreate中注册A的广播事件,当A被销毁时,进入onDestroy()方法时,发送A的广播,B接收到广播之后再启动A;同理,如果B被销毁,发送广播给A,让A启动B。

第三种情况:

强制关闭就没有办法。这个好像是从包的level去关的,并不走完整的生命周期。所以在服务里加代码是无法被调用的。处理这个情况的唯一方法是屏蔽掉 force
stop
和 uninstall 按钮,让其不可用。方法自己去找吧。当然有些手机自带的清理功能就是从这个地方清理的,比如华为的清理。所以第三种情况我也没有什么更好的办法了。

时间: 2024-10-11 05:53:49

Service要点全解析的相关文章

Service使用全解析

Service 使用全解析 什么是Service Service是Android 的四大组件之一,主要处理一些耗时的后台操作逻辑,或者轮询操作等需要长期在后台运行的任务.甚至在程序退出之后,可以让Service继续在后台运行. Service的启动方式有三种:三种方式对应着三种不同的生命周期. startService启动服务.(简单使用) bindService绑定服务的方式启动服务. 先启动服务之后绑定服务. Service 的简单使用 startService启动服务是最简单的一种方式.我

(转载)Oracle AWR报告指标全解析

Oracle AWR报告指标全解析 2014-10-16 14:48:04 分类: Oracle [性能调优]Oracle AWR报告指标全解析 2013/08/31 BY MACLEAN LIU 26条评论 [性能调优]Oracle AWR报告指标全解析 开Oracle调优鹰眼,深入理解AWR性能报告:http://www.askmaclean.com/archives/awr-hawk-eyes-training.html 开Oracle调优鹰眼,深入理解AWR性能报告 第二讲: http:

【凯子哥带你学Framework】Activity界面显示全解析

前几天凯子哥写的Framework层的解析文章<Activity启动过程全解析>,反响还不错,这说明“写让大家都能看懂的Framework解析文章”的思想是基本正确的. 我个人觉得,深入分析的文章必不可少,但是对于更多的Android开发者——即只想做应用层开发,不想了解底层实现细节——来说,“整体上把握,重要环节深入“是更好的学习方式.因为这样既可以有完整的知识体系,又不会在浩瀚的源码世界里迷失兴趣和方向. 所以呢,今天凯子哥又带来一篇文章,接着上一篇的结尾,重点介绍Activity开启之后

Android异步载入全解析之IntentService

Android异步载入全解析之IntentService 搞什么IntentService 前面我们说了那么多,异步处理都使用钦定的AsyncTask.再不济也使用的Thread,那么这个IntentService是个什么鬼. 相对与前面我们提到的这两种异步载入的方式来说.IntentService有一个最大的特点.就是--IntentService不受大部分UI生命周期的影响.它为后台线程提供了一个更直接的操作方式.只是,IntentService的不足主要体如今下面几点: 不能够直接和UI做

微信核心功能全解析

最近做了一套及时通讯软件,其中很多功能和微信是相仿的,下面详细介绍一下具体实现. 做及时通讯肯定要用xmpp协议,微信和一些及时通讯软件也是用的这套协议,只是纵向开发深度不同. 1.复写语音按钮 @SuppressLint("NewApi") public class RecordButton extends Button  { public RecordButton(Context context) { super(context); init(); } public RecordB

【转载】【凯子哥带你学Framework】Activity界面显示全解析(下)

如何验证上一个问题 首先,说明一下运行条件 //主题 name="AppTheme" parent="@android:style/Theme.Holo.Light.NoActionBar" //编译版本 android { compileSdkVersion 19 buildToolsVersion '19.1.0' defaultConfig { applicationId "com.socks.uitestapp" minSdkVersio

Android系统启动过程全解析

Android系统是一款基于Linux的移动操作系统,那么Android是如何启动起来的呢?本文就详细阐述Android系统的启动过程. 从内核之上,我们首先应该从文件系统的init开始,因为 init 是内核进入文件系统后第一个运行的程序,通常我们可以在linux的命令行中指定内核第一个调用谁,如果没指定那么内核将会到/sbin/./bin/ 等目录下查找默认的init,如果没有找到那么就报告出错. init.c位置:system/core/init/init.c. 在init.c的main函

Android 之 IPC 进程通信全解析

Android 之 IPC 进程通信全解析 本篇博客的框架 什么是IPC IPC(Inter-Process Communication) 进程间通信,是指两个不同进程之间数据交换的过程. 在明确其之前,需要先搞懂几个概念: 线程:CPU可调度的最小单位,是程序执行流的最小单元:线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源. 进程: 一个执行单元,在PC 和移动设备上一

Gson全解析(上)-Gson基础

前言 最近在研究Retrofit中使用的Gson的时候,发现对Gson的一些深层次的概念和使用比较模糊,所以这里做一个知识点的归纳整理. Gson(又称Google Gson)是Google公司发布的一个开放源代码的Java库,主要用途为序列化Java对象为JSON字符串,或反序列化JSON字符串成Java对象.而JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式,易于人阅读和编写,同时也易于机器解析和生成,广泛应用于各种数据的交互中,尤其是服务器与客户