Android Service全面解析

Service概念及用途

A service is an application component that can perform long-running operations in the background and does not provide a user interface。
通常service用来执行一些耗时操作,或者后台执行不提供用户交互界面的操作。其他的应用组件可以启动Service,即便用户切换了其他应用,启动的Service仍可在后台运行。一个组件可以与Service绑定并与之交互,甚至是跨进程通信(IPC)。例如,一个Service可以在后台执行网络请求、播放音乐、执行文件读写操作或者与 content provider交互等。

Service生命周期

为了创建Service,需要继承Service类。并重写它的回调方法,这些回调方法反应了Service的生命周期,并提供了绑定Service的机制。最重要的Service的生命周期回调方法如下所示:

  • onStartCommand()
    当其他组件调用startService()方法请求启动Service时,该方法被回调。一旦Service启动,它会在后台独立运行。当Service执行完以后,需调用stopSelf() 或 stopService()方法停止Service。(若您只希望bind Service,则无需调用这些方法)
  • onBind()
    当其他组件调用bindService()方法请求绑定Service时,该方法被回调。该方法返回一个IBinder接口,该接口是Service与绑定的组件进行交互的桥梁。若Service未绑定其他组件,该方法应返回null。
  • onCreate()
    当Service第一次创建时,回调该方法。该方法只被回调一次,并在onStartCommand() 或 onBind()方法被回调之前执行。若Service处于运行状态,该方法不会回调。
  • onDestroy()
    当Service被销毁时回调,在该方法中应清除一些占用的资源,如停止线程、结束绑定注册的监听器或broadcast receiver 等。该方法是Service中的最后一个回调。

这里写图片描述

如果某个组件通过调用startService()启动了Service(系统会回调onStartCommand()方法),那么直到在Service中手动调用stopSelf()方法、或在其他组件中手动调用stopService()方法,该Service才会停止。

如果某个组件通过调用bindService()绑定了Service(系统会回调onBind()方法),只要该组件与Service处于绑定状态,Service就会一直运行,当Service不再与组件绑定时,该Service将被destroy。

上面两条路径并不是毫不相干的:当调用startService()后,您仍可以bind该Service。比如,当播放音乐时,需调用startService()启动指定播放的音乐,当需要获取该音乐的播放进度时,则需要调用bindService(),在这种情况下,直到Service被unbind ,调用stopService() 或stopSelf()都不能停止该Service。

当系统内存低时,系统将强制停止Service的运行;若Service绑定了正在与用户交互的activity,那么该Service将不大可能被系统kill。如果创建的是前台Service,那么该Service几乎不会被kill。否则,当创建了一个长时间在后台运行的Service后,系统会降低该Service在后台任务栈中的级别——这意味着它容易被kill,所以在开发Service时,需要使Service变得容易被restart,因为一旦Service被kill,再restart它需要其资源可用时才行,当然这也取决于onStartCommand()方法返回的值。
onStartCommand()方法必须返回一个整数,这个整数是一个描述了在系统的kill事件中,系统应该如何继续这个服务的值。onStartCommand()有4种返回值:

  • START_STICKY
    若系统在onStartCommand()执行并返回后kill了service,那么service会被recreate并回调onStartCommand()。注意不要重新传递最后一个Intent。相反,系统回调onStartCommand()时回传一个空的Intent,除非有 pending intents传递,否则Intent将为null。该模式适合做一些类似播放音乐的操作。
  • START_NOT_STICKY
    “非粘性的”。若执行完onStartCommand()方法后,系统就kill了service,不要再重新创建service,除非系统回传了一个pending intent。这避免了在不必要的时候运行service,您的应用也可以restart任何未完成的操作。
  • START_REDELIVER_INTENT
    若系统在onStartCommand()执行并返回后kill了service,那么service会被recreate并回调onStartCommand()并将最后一个Intent回传至该方法。任何 pending intents都会被轮流传递。该模式适合做一些类似下载文件的操作。
  • START_STICKY_COMPATIBILITY
    START_STICKY的兼容版本,但不保证服务被kill后一定能重启。

Service的注册

在manifest文件中注册service的方式如下:

<manifest 
   ...
    <application
        ...
        <service android:name="com.hx.servicetest.MyService" /> 
    </application>
</manifest>

除此之外,在<\service>标签中还可以配置其他属性:

  • android:name —>服务全限定类名(唯一不可缺省的)
  • android:label —>服务的名字,如果此项不设置,那么默认显示的服务名则为类名
  • android:icon —>服务的图标
  • android:permission —>申明此服务的权限,这意味着只有提供了该权限的应用才能控制或连接此服务
  • android:process —>表示该服务是否运行在另外一个进程,如果设置了此项,那么将会在包名后面加上这段字符串表示另一进程的名字
  • android:enabled —>如果此项设置为 true,那么 Service 将会默认被系统启动,不设置默认此项为 false
  • android:exported —>表示该服务是否能够被其他应用程序所控制或连接,不设置默认此项为 false

Service的启动还可以使用隐式Intent,在<\service>中配置intent-filter即可,则Service可以响应带有指定action的Intent。

<service android:name="com.hx.servicetest.MyRemoteService" >
         <intent-filter>  
              <action android:name="com.hx.servicetest.MyAIDLService"/>  
         </intent-filter>
</service>

Service的启动

有了 Service 类我们如何启动他呢,有两种方法:

  • Context.startService()
  • Context.bindService()

当然,service也可以同时在上述两种方式下运行。这涉及到Service中两个回调方法的执行:onStartCommand()(通过start方式启动一个service时回调的方法)、onBind()(通过bind方式启动一个service回调的方法)。
无论通过那种方式启动service(start、bind、start&bind),任何组件(甚至其他应用的组件)都可以使用service。并通过Intent传递参数。当然,您也可以将Service在manifest文件中配置成私有的,不允许其他应用访问。

startService

其他组件调用startService()方法启动一个Service。一旦启动,Service将一直运行在后台即便启动Service的组件已被destroy。通常,一个被start的Service会在后台执行单独的操作,也并不给启动它的组件返回结果。比如说,一个start的Service执行在后台下载或上传一个文件的操作,完成之后,Service应自己停止。

一般使用如下两种方式创建一个start Service:

  • 继承Service类
    请务必在Service中开启线程来执行耗时操作,因为Service运行在主线程中。
  • 继承IntentService类
    IntentService继承于Service,若Service不需要同时处理多个请求,那么使用IntentService将是最好选择:您只需要重写onHandleIntent()方法,该方法接收一个回传的Intent参数,您可以在方法内进行耗时操作,因为它默认开启了一个子线程,操作执行完成后也无需手动调用stopSelf()方法,onHandleIntent()会自动调用该方法。

(1)继承Service类
如果你需要在Service中执行多线程而不是处理一个请求队列,那么需要继承Service类,分别处理每个Intent。在Service中执行操作时,处理每个请求都需要开启一个新线程(new Thread()),并且同一时刻一个线程只能处理一个请求,写法很简单,不再赘述。
下面演示一个同一个线程处理多个任务的例子,使用HandlerThread实现,如果你对HandlerThread不了解,可以看这篇文章Android HandlerThread 全面解析

public class MyService extends Service {
    private HandlerThread mThread;
    private Handler mHandler;

    @Override  
    public void onCreate() {  
        super.onCreate();  
        MainActivity.showlog("onCreate()");
        initBackThread();
    }  

    private void initBackThread() {
        mThread = new HandlerThread("ServiceStartArguments");
        mThread.start();
        mHandler = new Handler(mThread.getLooper()) {
            @Override
            public void handleMessage(Message msg) {
                //模拟耗时线程操作
                MainActivity.showlog("processing...msg.arg1="+msg.arg1);
                try {
                    Thread.sleep(5000);
                } catch (Exception e) {
                    Thread.currentThread().interrupt();
                }
                MainActivity.showlog("stopSelf...msg.arg1="+msg.arg1);
                //当所有操作完成后,服务自己停止
                stopSelf(msg.arg1);
            }
        };
    }

    @Override  
    public int onStartCommand(Intent intent, int flags, int startId) {  
        MainActivity.showlog("onStartCommand()");

        Message msg = mHandler.obtainMessage();
        msg.arg1 = startId;
        mHandler.sendMessage(msg);

        return START_STICKY;
    }  

    @Override  
    public void onDestroy() {  
        super.onDestroy();  
        MainActivity.showlog("onDestroy()");  
    }  

    @Override  
    public IBinder onBind(Intent intent) {
        MainActivity.showlog("onBind()");
        return null;
    }    
}  

添加Button点击事件:

    @Override  
    public void onClick(View v) {  
        switch (v.getId()) {  
        case R.id.start_service:
            showlog("click Start Service button");
            Intent it1 = new Intent(this, MyService.class);  
            startService(it1);  
            break;  
        case R.id.stop_service: 
            showlog("click Stop Service button"); 
            Intent it2 = new Intent(this, MyService.class);  
            stopService(it2);  
            break;   
        default:  
            break;  
        }  
    }  

这样的话,一个简单的带有Service功能的程序就写好了,现在我们将程序运行起来,并点击一下Start Service按钮,可以看到LogCat的打印日志如下:
这里写图片描述
流程:点击->onCreate->onStartCommand->耗时事件处理(5S)->onDestroy

那么如果我连续两次点击Start Service按钮呢?这个时候的打印日志如下:
这里写图片描述
流程:第一次点击->onCreate->onStartCommand->耗时事件处理(5S)
第二次点击->onStartCommand->耗时事件处理(5S)->onDestroy
这里的耗时处理是借助HandlerThread来实现的,多个任务在同一个线程中,第一个事件处理完成后再进行第二个事件处理,直到最后一个任务处理完毕,才会停止Service,使用的是stopSelf(int startId);关于stopSelf的使用,可以参考后面 Service的销毁 一节内容。

点击Start Service然后再点击Stop Service按钮就可以将MyService立即停止掉了,Log如下:
这里写图片描述
注:多个启动Service的请求可能导致onStartCommand()多次调用,但只需调用stopSelf() 、 stopService()这两个方法之一,就可立即停止该服务。

上面如果我们的耗时任务时间够长,在MyService停止之前点击”返回”,Activity被干掉了,但是我们的服务仍然在运行,可以查看 设置–>应用–>正在运行,截图如下:
这里写图片描述

(2)继承IntentService类
在大多数情况下,start Service并不会同时处理多个请求,因为处理多线程较为危险,所以继承IntentService类带创建Service是个不错选择。
使用IntentService的要点如下:

  • 默认在子线程中处理回传到onStartCommand()方法中的Intent;
  • 在重写的onHandleIntent()方法中处理按时间排序的Intent队列,所以不用担心多线程(multi-threading)带来的问题。
  • 当所有请求处理完成后,自动停止service,无需手动调用stopSelf()方法;
  • 默认实现了onBind()方法,并返回null;
  • 默认实现了onStartCommand()方法,并将回传的Intent以序列的形式发送给onHandleIntent(),您只需重写该方法并处理Intent即可。

综上所述,您只需重写onHandleIntent()方法即可,当然,还需要创建一个构造方法,示例如下:

public class MyIntentService extends IntentService {        

    public MyIntentService() {
        super("MyIntentService");
    }

    @Override  
    public void onHandleIntent(Intent intent) {
        //模拟耗时线程操作
        MainActivity.showlog("processing...");
        try {
            Thread.sleep(5000);
        } catch (Exception e) {
            Thread.currentThread().interrupt();
        } 
    }  
}  

如果您还希望在IntentService的继承类中重写其他生命周期方法,如onCreate()、onStartCommand() 或 onDestroy(),那么请先调用各自的父类方法以保证子线程能够正常启动。
比如,要实现onStartCommand()方法,需返回其父类方法:

    @Override  
    public int onStartCommand(Intent intent, int flags, int startId) {  
        MainActivity.showlog("onStartCommand()");
        return super.onStartCommand(intent, flags, startId);  
    } 

注:除onHandleIntent()外,onBind()方法也无需调用其父类方法。

bindService

上面学习了startService()启动 Service,不过这样的话Service和Activity的关系并不大,只是Activity通知了Service一下:“你可以启动了。”然后Service就去忙自己的事情了。那么有没有什么办法能让它们俩的关联更多一些呢?比如说在Activity中可以指定让Service去执行什么任务,当然可以,只需要让Activity和Service建立关联就好了。
bindService()方法的意思是,把这个 Service 和调用 Service 的客户类绑起来,如果这个客户类被销毁,Service 也会被销毁。用这个方法的一个好处是,bindService() 方法执行后 Service 会回调 onBind() 方法,你可以从这里返回一个实现了 IBind 接口的类,在客户端操作这个类就能和这个服务通信了,比如得到 Service 运行的状态或其他操作。如果 Service 还没有运行,使用这个方法启动 Service 就会 onCreate() 方法而不会调用 onStartCommand()。

观察MyService中的代码,你会发现有一个onBind()方法我们都没有使用到,这个方法其实就是用于和Activity建立关联的,重新写一个MyBindService,如下所示:

public class MyBindService extends Service {    
    private MyBinder mBinder = new MyBinder();

    @Override  
    public void onCreate() {  
        super.onCreate();  
        MainActivity.showlog("onCreate()");  
    }  

    @Override  
    public int onStartCommand(Intent intent, int flags, int startId) {  
        MainActivity.showlog("onStartCommand()");
        return super.onStartCommand(intent, flags, startId);  
    }  

    @Override  
    public void onDestroy() {  
        super.onDestroy();  
        MainActivity.showlog("onDestroy()");  
    }  

    //Service自定义方法
    public void doSomethingInService(){  
        MainActivity.showlog("doSomethingInService()");  
    }

    //复写onBind方法,并且返回IBinder的实现类
    @Override  
    public IBinder onBind(Intent intent) {
        MainActivity.showlog("onBind()");
        return mBinder;  
    }  

    @Override  
    public boolean onUnbind(Intent intent) {  
        MainActivity.showlog("onUnbind()");  
        return super.onUnbind(intent);  
    } 

    //内部类,扩展自Binder类
    class MyBinder extends Binder {  
        //MyBinder自定义方法
        public void doSomethingInBinder() {  
            MainActivity.showlog("doSomethingInBinder()");  
        }
        public MyBindService getService(){
            return MyBindService.this;  
        }  
    }    
} 

这里我们新增了一个MyBinder类继承自Binder类,然后在MyBinder中添加了一个doSomethingInBinder()方法用于在后台执行任务,而且在Service中还写了一个doSomethingInService()方法,同样可以执行后台任务,其实这里只是打印了一行日志。
接下来再修改MainActivity中的代码,让MainActivity和MyBindService之间建立关联,代码如下所示:

public class MainActivity extends Activity implements OnClickListener {    
    private Button startService;    
    private Button stopService; 
    private Button startIntentService;    
    private Button stopIntentService;
    private Button bindService;    
    private Button unbindService;    
    private MyBindService.MyBinder myBinder;  
    private boolean isConnected = false;

    private ServiceConnection connection = new ServiceConnection() {   
        @Override  
        public void onServiceDisconnected(ComponentName name) {  
            showlog("onServiceDisconnected"); 
            isConnected = false;
        }  

        @Override  
        public void onServiceConnected(ComponentName name, IBinder iBinder) {
            showlog("onServiceConnected"); 
            myBinder = (MyBindService.MyBinder) iBinder;  
            myBinder.doSomethingInBinder();  
            MyBindService service = myBinder.getService();  
            service.doSomethingInService();  
            isConnected = true;  
        }  
    };  

    @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);
        startIntentService = (Button) findViewById(R.id.start_intent_service);  
        stopIntentService = (Button) findViewById(R.id.stop_intent_service);
        bindService = (Button) findViewById(R.id.bind_service);  
        unbindService = (Button) findViewById(R.id.unbind_service);  
        startService.setOnClickListener(this);  
        stopService.setOnClickListener(this);  
        startIntentService.setOnClickListener(this);  
        stopIntentService.setOnClickListener(this);
        bindService.setOnClickListener(this);  
        unbindService.setOnClickListener(this);  
    }  

    @Override  
    public void onClick(View v) {  
        switch (v.getId()) {  
        case R.id.start_service:
            showlog("click Start Service button");
            Intent it1 = new Intent(this, MyService.class);  
            startService(it1);  
            break;  
        case R.id.stop_service: 
            showlog("click Stop Service button"); 
            Intent it2 = new Intent(this, MyService.class);  
            stopService(it2);  
            break;  
        case R.id.start_intent_service:
            showlog("click Start IntentService button");
            Intent it3 = new Intent(this, MyIntentService.class);  
            startService(it3);  
            break;  
        case R.id.stop_intent_service: 
            showlog("click Stop IntentService button"); 
            Intent it4 = new Intent(this, MyIntentService.class);  
            stopService(it4);  
            break;  
        case R.id.bind_service:  
            showlog("click Bind Service button");
            Intent it5 = new Intent(this, MyBindService.class);  
            bindService(it5, connection, BIND_AUTO_CREATE);  
            break;  
        case R.id.unbind_service:
            showlog("click Unbind Service button"); 
            if(isConnected){  
                unbindService(connection);  
            }  
            break;  
        default:  
            break;  
        }  
    }  

    public static void showlog(String info) {
        System.out.print("Watson "+info+"\n");
    }

}

可以看到,这里我们首先创建了一个ServiceConnection的匿名类,在里面重写了onServiceConnected()方法和onServiceDisconnected()方法,这两个方法分别会在Activity与Service建立关联和解除关联的时候调用。在onServiceConnected()方法中,我们又通过向下转型得到了MyBinder的实例,有了这个实例,Activity和Service之间的关系就变得非常紧密了。现在我们可以在Activity中根据具体的场景来调用MyBinder中的任何public方法,即实现了Activity指挥Service干什么Service就去干什么的功能。
当然,现在Activity和Service其实还没关联起来了呢,这个功能是在Bind Service按钮的点击事件里完成的。可以看到,这里我们仍然是构建出了一个Intent对象,然后调用bindService()方法将Activity和Service进行绑定。bindService()方法接收三个参数,第一个参数就是刚刚构建出的Intent对象,第二个参数是前面创建出的ServiceConnection的实例,第三个参数是一个标志位,有两个flag, BIND_DEBUG_UNBIND BIND_AUTO_CREATE,前者用于调试,后者默认使用,这里传入BIND_AUTO_CREATE表示在Activity和Service建立关联后自动创建Service,这会使得MyService中的onCreate()方法得到执行,但onStartCommand()方法不会执行。
现在让我们重新运行一下程序吧,在MainActivity中点击一下Bind Service按钮,LogCat里的打印日志如下图所示:
这里写图片描述

由于在绑定Service的时候指定的标志位是BIND_AUTO_CREATE,说明点击Bind Service按钮的时候Service也会被创建,这时应该怎么销毁Service呢?其实也很简单,点击一下Unbind Service按钮,将Activity和Service的关联解除就可以了。Log如下:
这里写图片描述

另外需要注意,任何一个Service在整个应用程序范围内都是通用的,即MyService不仅可以和MainActivity建立关联,还可以和任何一个Activity建立关联,而且在建立关联时它们都可以获取到相同的MyBinder实例。

Service的销毁

stopService/unbindService

在Service的启动这一部分,我们已经简单介绍了销毁Service的方法。

  • startService—>stopService
  • bindService—>unbindService

以上这两种销毁的方式都很好理解。
但有几点需要注意一下:
(1)你应当知道在调用 bindService 绑定到Service的时候,你就应当保证在某处调用 unbindService 解除绑定(尽管 Activity 被 finish 的时候绑定会自动解除,并且Service会自动停止);
(2)你应当注意使用 startService 启动服务之后,一定要使用 stopService停止服务,不管你是否使用bindService;
(3)同时使用 startService 与 bindService 要注意到,Service 的终止,需要unbindService与stopService同时调用,才能终止 Service,不管 startService 与 bindService 的调用顺序,如果先调用 unbindService 此时服务不会自动终止,再调用 stopService 之后服务才会停止,如果先调用 stopService 此时服务也不会终止,而再调用 unbindService 或者之前调用 bindService 的 Context 不存在了(如Activity 被 finish 的时候)之后服务才会自动停止;
(4)当在旋转手机屏幕的时候,当手机屏幕在“横”“竖”变换时,此时如果你的 Activity 如果会自动旋转的话,旋转其实是 Activity 的重新创建,因此旋转之前的使用 bindService 建立的连接便会断开(Context 不存在了),对应服务的生命周期与上述相同。
(5)unbindService 解除绑定,参数为之前创建的 ServiceConnection 接口对象。另外,多次调用 unbindService 来释放相同的连接会抛出异常,因此我创建了一个 boolean 变量来判断是否 unbindService 已经被调用过。

stopSelf

对于StartService启动的服务,Service本身还提供了另外一个方法让自己停止—>stopSelf。
若系统正在处理多个调用onStartCommand()请求,那么在启动一个请求时,你不应当在此时停止该Service。为了避免这个问题,您可以调用stopSelf(int)方法,以确保请求停止的Service是最新的启动请求。这就是说,当调用stopSelf(int)方法时,传入的ID代表启动请求(该ID会传递至onStartCommand()),该ID与请求停止的ID一致。则如果在调用stopSelf(int)之前,Service收到一个新的Start请求,ID将无法匹配,Service并不会停止。具体的例子参见上面Service启动一节。

    ...
    private void initBackThread() {
        mThread = new HandlerThread("ServiceStartArguments");
        mThread.start();
        mHandler = new Handler(mThread.getLooper()) {
            @Override
            public void handleMessage(Message msg) {
                //线程操作
                MainActivity.showlog("processing...msg.arg1="+msg.arg1);
                try {
                    Thread.sleep(5000);
                } catch (Exception e) {
                    Thread.currentThread().interrupt();
                }
                MainActivity.showlog("stopSelf...msg.arg1="+msg.arg1);
                stopSelf(msg.arg1);
            }
        };
    }

    @Override  
    public int onStartCommand(Intent intent, int flags, int startId) {  
        MainActivity.showlog("onStartCommand()");

        Message msg = mHandler.obtainMessage();
        msg.arg1 = startId;
        mHandler.sendMessage(msg);

        return START_STICKY;
    }  
    ...

Service和Thread的区别

Thread我们大家都知道,是用于开启一个子线程,在这里去执行一些耗时操作就不会阻塞主线程的运行。而Service我们最初理解的时候,总会觉得它是用来处理一些后台任务的,一些比较耗时的操作也可以放在这里运行,这就会让人产生混淆了。但是,如果我告诉你Service其实是运行在主线程里的,你还会觉得它和Thread有什么关系吗?
在MainActivity的onCreate()方法里加入一行打印当前线程id的Log:

showlog("MainActivity thread id is " + Thread.currentThread().getId());

同时在MyService的onCreate()方法里加入打印当前线程id的Log:

MainActivity.showlog("MyService thread id is " + Thread.currentThread().getId());

现在重新运行一下程序,并点击Start Service按钮,会看到如下Log信息:
这里写图片描述
可以看到,它们的线程id完全是一样的,由此证实了Service确实是运行在主线程里的,也就是说如果你在Service里编写了非常耗时的代码,程序也会出现ANR的。

下面我详细的来解释一下:

  • Thread:Thread 是程序执行的最小单元,它是分配CPU的基本单位。可以用 Thread 来执行一些异步的操作。
  • Service:Service 是android的一种机制,当它运行的时候如果是Local Service,那么对应的 Service 是运行在主进程的 main 线程上的。如:onCreate,onStart 这些函数在被系统调用的时候都是在主进程的 main 线程上运行的。如果是Remote Service,那么对应的 Service 则是运行在独立进程的 main 线程上。 因此请不要把 Service 理解成线程,它跟线程半毛钱的关系都没有!

Android的后台就是指,它的运行是完全不依赖UI的。即使Activity被销毁,或者程序被关闭,只要进程还在,Service就可以继续运行。比如说一些应用程序,始终需要与服务器之间始终保持着心跳连接,就可以使用Service来实现。你可能又会问,前面不是刚刚验证过Service是运行在主线程里的么?在这里一直执行着心跳连接,难道就不会阻塞主线程的运行吗?当然会,但是我们可以在Service中再创建一个子线程,然后在这里去处理耗时逻辑就没问题了。
既然在Service里也要创建一个子线程,那为什么不直接在Activity里创建呢?这是因为Activity很难对Thread进行控制,当Activity被销毁之后,就没有任何其它的办法可以再重新获取到之前创建的子线程的实例。而且在一个Activity中创建的子线程,另一个Activity无法对其进行操作。但是Service就不同了,所有的Activity都可以与Service进行关联,然后可以很方便地操作其中的方法,即使Activity被销毁了,之后只要重新与Service建立关联,就又能够获取到原有的Service中Binder的实例。因此,使用Service来处理后台任务,Activity就可以放心地finish,完全不需要担心无法对后台任务进行控制的情况。你也可以在 Service 里注册 BroadcastReceiver,在其他地方通过发送 broadcast 来控制它,当然这些都是 Thread 做不到的。
一个比较标准的Service就可以写成:

@Override  
public int onStartCommand(Intent intent, int flags, int startId) {  
    new Thread(new Runnable() {  
        @Override  
        public void run() {  
            // 开始执行后台任务  
        }  
    }).start();  
    return super.onStartCommand(intent, flags, startId);  
}  

class MyBinder extends Binder {  

    public void startDownload() {  
        new Thread(new Runnable() {  
            @Override  
            public void run() {  
                // 执行具体的下载任务  
            }  
        }).start();  
    }    
}  

创建前台Service

Service几乎都是在后台运行的,一直以来它都是默默地做着辛苦的工作。但是Service的系统优先级还是比较低的,当系统出现内存不足情况时,就有可能会回收掉正在后台运行的Service。如果你希望Service可以一直保持运行状态,而不会由于系统内存不足的原因导致被回收,就可以考虑使用前台Service。前台Service和普通Service最大的区别就在于,它会一直有一个正在运行的图标在系统的状态栏显示,下拉状态栏后可以看到更加详细的信息,非常类似于通知的效果,只有前台Service被destroy后,状态栏显示才能消失。当然有时候你也可能不仅仅是为了防止Service被回收才使用前台Service,有些项目由于特殊的需求会要求必须使用前台Service,比如说墨迹天气,它的Service在后台更新天气数据的同时,还会在系统状态栏一直显示当前天气的信息,如下图所示:
这里写图片描述

来看一下如何才能创建一个前台Service吧,其实并不复杂,修改MyService中的代码,如下所示:

    @Override  
    public void onCreate() {  
        super.onCreate();
        MainActivity.showlog("onCreate()");
        initBackThread();

        Intent notificationIntent = new Intent(this, MainActivity.class);  
      /*第二个参数现在不再使用了
        第四个参数描述:
        FLAG_CANCEL_CURRENT:如果当前系统中已经存在一个相同的PendingIntent对象,那么就将先将已有的PendingIntent取消,然后重新生成一个PendingIntent对象。
        FLAG_NO_CREATE:如果当前系统中存在相同的PendingIntent对象,系统将不会创建该PendingIntent对象而是直接返回null。
        FLAG_ONE_SHOT:该PendingIntent只作用一次。在该PendingIntent对象通过send()方法触发过后,PendingIntent将自动调用cancel()进行销毁,那么如果你再调用send()方法的话,系统将会返回一个SendIntentException。
        FLAG_UPDATE_CURRENT:如果系统中有一个和你描述的PendingIntent对等的PendingInent,那么系统将使用该PendingIntent对象,但是会使用新的Intent来更新之前PendingIntent中的Intent对象数据,例如更新Intent中的Extras*/
        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT);  
        Notification notification = new Notification.Builder(this)
            .setSmallIcon(R.drawable.ic_launcher)
            .setWhen(System.currentTimeMillis())
            .setTicker("有通知到来") 
            .setContentTitle("这是通知的标题") 
            .setContentText("这是通知的内容")
            .setOngoing(true)
            .setContentIntent(pendingIntent)
            .build();
        /*使用startForeground,如果id为0,那么notification将不会显示*/
        startForeground(1, notification);
    }

这里只是修改了MyService中onCreate()方法的代码。可以看到,我们创建了一个Notification对象,然后设置了它的布局和数据,并在这里设置了点击通知后就打开MainActivity。然后调用startForeground()方法就可以让MyService变成一个前台Service,并会将通知的图片显示出来。
现在重新运行一下程序,并点击Start Service或Bind Service按钮,MyService就会以前台Service的模式启动了,并且在系统状态栏会弹出一个通栏图标,下拉状态栏后可以看到通知的详细内容,如下图所示。
这里写图片描述

可以调用stopForeground(Boolean bool)来移除前台Service。该方法需传入一个boolean型变量,表示是否也一并清除状态栏上的notification。该方法并不停止Service,如果停止正在前台运行的Service,那么notification 也会一并被清除。

最后我们看一下进程的分类:

  • 前台进程 Foreground process
    • 当前用户操作的Activity所在进程
    • 绑定了当前用户操作的Activity的Service所在进程
    • 调用了startForeground()的Service 典型场景:后台播放音乐
  • 可见进程 Visible process
    • 处于暂停状态的Activity
    • 绑定到暂停状态的Activity的Service
  • 服务进程 Service process
    • 通过startService()启动的Service
  • 后台进程 Background process
    • 处于停止状态的Activity
  • 空进程 Empty process

远程Service的使用

什么是远程Service

从上面可知,Service其实是运行在主线程里的,如果直接在Service中处理一些耗时的逻辑,就会导致程序ANR。让我们来验证一下吧,修改MyService代码,在onCreate()方法中让线程睡眠60秒,如下所示:

@Override  
    public void onCreate() {  
        super.onCreate();  
        MainActivity.showlog("onCreate()");

        try {  
            Thread.sleep(60000);  
        } catch (InterruptedException e) {  
            e.printStackTrace();  
        } 
    }  

点击一下Start Service按钮或Bind Service按钮,程序就会阻塞住无法进行任何其它操作,过一段时间后就会弹出ANR的提示框,如下图所示:
这里写图片描述

现在来看看远程Service的用法,如果将MyService转换成一个远程Service,还会不会有ANR的情况呢?让我们来动手尝试一下吧。将一个普通的Service转换成远程Service其实非常简单,只需要在注册Service的时候将它的android:process属性指定成:remote就可以了,代码如下所示:

<service android:name="com.hx.servicetest.MyService" 
         android:process=":remote"/>

重新运行程序,并点击一下Start Service按钮,你会看到控制台立刻打印了onCreate()的信息,而且主界面并没有阻塞住,也不会出现ANR。大概过了一分钟后,又会看到onStartCommand()打印了出来。
为什么将MyService转换成远程Service后就不会导致程序ANR了呢?这是由于,使用了远程Service后,MyService已经在另外一个进程当中运行了,所以只会阻塞该进程中的主线程,并不会影响到当前的应用程序。

那既然远程Service这么好用,干脆以后我们把所有的Service都转换成远程Service吧,还省得再开启线程了。其实不然,远程Service非但不好用,甚至可以称得上是较为难用。一般情况下如果可以不使用远程Service,就尽量不要使用它。
下面就来看一下它的弊端吧,首先将MyService的onCreate()方法中让线程睡眠的代码去除掉,然后重新运行程序,并点击一下Bind Service按钮,你会发现程序崩溃了!为什么点击Start Service按钮程序就不会崩溃,而点击Bind Service按钮就会崩溃呢?这是由于在Bind Service按钮的点击事件里面我们会让MainActivity和MyService建立关联,但是目前MyService已经是一个远程Service了,Activity和Service运行在两个不同的进程当中,这时就不能再使用传统的建立关联的方式,程序也就崩溃了。

调用远程Service

那么如何才能让Activity与一个远程Service建立关联呢?这就要使用AIDL来进行跨进程通信了(IPC)。
AIDL(Android Interface Definition Language)是Android接口定义语言的意思,它可以用于让某个Service与多个应用程序组件之间进行跨进程通信,从而可以实现多个应用程序共享同一个Service的功能。
下面我们就来一步步地看一下AIDL的用法到底是怎样的。首先需要新建一个AIDL文件,在这个文件中定义好Activity需要与Service进行通信的方法。新建MyAIDLService.aidl文件,代码如下所示:

package com.hx.servicetest;

interface MyAIDLService {  
    int plus(int a, int b);  
    String toUpperCase(String str);  
} 

点击保存之后,在gen目录下通过aapt就会生成一个对应的Java文件,如下图所示:
这里写图片描述

然后我们写一个MyRemoteService,在里面实现我们刚刚定义好的MyAIDLService接口,如下所示:

public class MyRemoteService extends Service {

    @Override  
    public void onCreate() {  
        super.onCreate();  
        MainActivity.showlog("onCreate()");  
    }  

    @Override  
    public int onStartCommand(Intent intent, int flags, int startId) {  
        MainActivity.showlog("onStartCommand()");
        return super.onStartCommand(intent, flags, startId);  
    }  

    @Override  
    public void onDestroy() {  
        super.onDestroy();  
        MainActivity.showlog("onDestroy()");  
    }  

    @Override  
    public IBinder onBind(Intent intent) {
        MainActivity.showlog("onBind()");
        return mBinder;  
    }  

    @Override  
    public boolean onUnbind(Intent intent) {  
        MainActivity.showlog("onUnbind()");  
        return super.onUnbind(intent);  
    } 

    MyAIDLService.Stub mBinder = new Stub() {         
        @Override  
        public String toUpperCase(String str) throws RemoteException {  
            if (str != null) {  
                return str.toUpperCase();  
            }  
            return null;  
        }  

        @Override  
        public int plus(int a, int b) throws RemoteException {  
            return a + b;  
        }  
    }; 
} 

这里先是对MyAIDLService.Stub进行了实现,重写里了toUpperCase()和plus()这两个方法。这两个方法的作用分别是将一个字符串全部转换成大写格式,以及将两个传入的整数进行相加。然后在onBind()方法中将MyAIDLService.Stub的实现返回。这里为什么可以这样写呢?因为Stub其实就是Binder的子类,所以在onBind()方法中可以直接返回Stub的实现。
接下来修改MainActivity中的代码,如下所示:

public class MainActivity extends Activity implements OnClickListener {  

    private Button startService;    
    private Button stopService; 
    private Button startIntentService;    
    private Button stopIntentService;
    private Button bindService;    
    private Button unbindService;    
    private Button bindRemoteService;

    private MyAIDLService myAIDLService;   
    private ServiceConnection connection = new ServiceConnection() {  
        @Override  
        public void onServiceDisconnected(ComponentName name) {}  

        @Override  
        public void onServiceConnected(ComponentName name, IBinder service) {  
            myAIDLService = MyAIDLService.Stub.asInterface(service);  
            try {  
                int result = myAIDLService.plus(7, 8);  
                String upperStr = myAIDLService.toUpperCase("hello watson");  
                showlog("result is " + result);  
                showlog("upperStr is " + upperStr);  
            } catch (RemoteException e) {  
                e.printStackTrace();  
            }  
        }  
    }; 

    @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);
        startIntentService = (Button) findViewById(R.id.start_intent_service);  
        stopIntentService = (Button) findViewById(R.id.stop_intent_service);
        bindService = (Button) findViewById(R.id.bind_service);  
        unbindService = (Button) findViewById(R.id.unbind_service); 
        bindRemoteService = (Button) findViewById(R.id.bind_remote_service);  
        startService.setOnClickListener(this);  
        stopService.setOnClickListener(this);  
        startIntentService.setOnClickListener(this);  
        stopIntentService.setOnClickListener(this);
        bindService.setOnClickListener(this);  
        unbindService.setOnClickListener(this);
        bindRemoteService.setOnClickListener(this);
    }  

    @Override  
    public void onClick(View v) {  
        switch (v.getId()) {  
        case R.id.start_service:
            showlog("click Start Service button");
            Intent it1 = new Intent(this, MyService.class);  
            startService(it1);  
            break;  
        case R.id.stop_service: 
            showlog("click Stop Service button"); 
            Intent it2 = new Intent(this, MyService.class);  
            stopService(it2);  
            break;  
        case R.id.start_intent_service:
            showlog("click Start IntentService button");
            Intent it3 = new Intent(this, MyIntentService.class);  
            startService(it3);  
            break;  
        case R.id.stop_intent_service: 
            showlog("click Stop IntentService button"); 
            Intent it4 = new Intent(this, MyIntentService.class);  
            stopService(it4);  
            break;  
        case R.id.bind_service:  
            showlog("click Bind Service button");
            Intent it5 = new Intent(this, MyBindService.class);  
            bindService(it5, connection, BIND_AUTO_CREATE);  
            break;  
        case R.id.unbind_service:
            showlog("click Unbind Service button"); 
            if(isConnected == true){  
                unbindService(connection);  
            }  
            break; 
        case R.id.bind_remote_service:
            showlog("click Bind Remote Service button");
            Intent it6 = new Intent(this, MyRemoteService.class);  
            bindService(it6, connection, BIND_AUTO_CREATE);
            break;
        default:  
            break;  
        }  
    }  

    public static void showlog(String info) {
        System.out.print("Watson "+info+"\n");
    }

}

我们只是修改了ServiceConnection中的代码。可以看到,这里首先使用了MyAIDLService.Stub.asInterface()方法将传入的IBinder对象传换成了MyAIDLService对象,接下来就可以调用在MyAIDLService.aidl文件中定义的所有接口了。这里我们先是调用了plus()方法,并传入了7和8作为参数,然后又调用了toUpperCase()方法,并传入hello world字符串作为参数,最后将调用方法的返回结果打印出来。
现在重新运行程序,并点击一下Bind Remote Service按钮,可以看到打印日志如下所示:
这里写图片描述

由此可见,我们确实已经成功实现跨进程通信了,在一个进程中访问到了另外一个进程中的方法。
不过你也可以看出,目前的跨进程通信其实并没有什么实质上的作用,因为这只是在一个Activity里调用了同一个应用程序的Service里的方法。而跨进程通信的真正意义是为了让一个应用程序去访问另一个应用程序中的Service,以实现共享Service的功能。那么下面我们自然要学习一下,如何才能在其它的应用程序中调用到MyRemoteService里的方法。

看看这里我们bind Service的方式:

Intent it6 = new Intent(this, MyRemoteService.class);  
bindService(it6, connection, BIND_AUTO_CREATE);

这里在构建Intent的时候是使用MyRemoteService.class来指定要绑定哪一个Service的,但是在另一个应用程序中去绑定Service的时候并没有MyRemoteService这个类,这时就必须使用到隐式Intent了。现在修改AndroidManifest.xml中的代码,给MyRemoteService加上一个action,如下所示:

<service android:name="com.hx.servicetest.MyRemoteService" 
         android:exported="true" >
         <intent-filter>  
              <action android:name="com.hx.servicetest.MyAIDLService"/>  
         </intent-filter>
</service>

这就说明,MyRemoteService可以响应带有com.hx.servicetest.MyAIDLService这个action的Intent。
现在重新运行一下程序,这样就把远程Service端的工作全部完成了。
然后创建一个新的Android项目,起名为ClientTest,我们就尝试在这个程序中远程调用MyRemoteService中的方法。
ClientTest中的Activity如果想要和MyRemoteService建立关联其实也不难,首先需要将MyAIDLService.aidl文件从ServiceTest项目中拷贝过来,注意要将原有的包路径一起拷贝过来,完成后项目的结构如下图所示:
这里写图片描述

接下来打开MainActivity,在其中加入和MyRemoteService建立关联的代码,如下所示:

public class MainActivity extends Activity implements OnClickListener {  

    private Button bindRemoteService;    
    private Button unbindRemoteService;    
    private boolean isConnected = false;

    private MyAIDLService myAIDLService;   
    private ServiceConnection connection = new ServiceConnection() {  
        @Override  
        public void onServiceDisconnected(ComponentName name) {
            isConnected = false;
        }  

        @Override  
        public void onServiceConnected(ComponentName name, IBinder service) {
            isConnected = true;
            myAIDLService = MyAIDLService.Stub.asInterface(service);  
            try {  
                int result = myAIDLService.plus(13, 19);  
                String upperStr = myAIDLService.toUpperCase("hello aidl service");  
                showlog("result is " + result);  
                showlog("upperStr is " + upperStr);  
            } catch (RemoteException e) {  
                e.printStackTrace();  
            }  
        }  
    }; 

    @Override  
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState); 
        setContentView(R.layout.activity_main);
        bindRemoteService = (Button) findViewById(R.id.bind_service);  
        unbindRemoteService = (Button) findViewById(R.id.unbind_service);  
        bindRemoteService.setOnClickListener(this);  
        unbindRemoteService.setOnClickListener(this);
    }  

    @Override  
    public void onClick(View v) {  
        switch (v.getId()) {  
        case R.id.bind_service:
            Intent intent = new Intent("com.hx.servicetest.MyAIDLService");  
            bindService(intent, connection, BIND_AUTO_CREATE);  
            break;  
        case R.id.unbind_service:
            if(isConnected){  
                unbindService(connection);  
            } 
            break;
        default:  
            break;  
        }  
    }  

    public static void showlog(String info) {
        System.out.print("Watson "+info+"\n");
    }  
}

这部分代码大家一定非常眼熟吧?没错,这和在ServiceTest的MainActivity中的代码几乎是完全相同的,只是在让Activity和Service建立关联的时候我们使用了隐式Intent,将Intent的action指定成了com.hx.servicetest.MyAIDLService。
在当前Activity和MyService建立关联之后,我们仍然是调用了plus()和toUpperCase()这两个方法,远程的MyService会对传入的参数进行处理并返回结果,然后将结果打印出来。
这样的话,ClientTest中的代码也就全部完成了,现在运行一下这个项目,点击Bind Remote Service按钮,此时就会去和远程的MyRemoteService建立关联,观察LogCat中的打印信息如下所示:
这里写图片描述

然后点击Unbind Remote Service按钮,此时会去和远程的MyRemoteService断开关联,MyRemoteService也会自定销毁,看Log吧:
这里写图片描述

至此,我们的跨进程通信功能已经完美实现了。
当然这里只是简单地实现了功能,如果你想了解Binder机制的具体实现原理,以及通过远程调用传递自定义类(Bean),请移步至我的另外一篇文章Android Binder机制完全解析

Demo下载地址

  • 10
    点赞
  • 40
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值