Service要点全解析

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/shakespeare001/article/details/51463559

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 按钮,让其不可用。方法自己去找吧。当然有些手机自带的清理功能就是从这个地方清理的,比如华为的清理。所以第三种情况我也没有什么更好的办法了。


没有更多推荐了,返回首页