Service的一点理解

来源:互联网 发布:网络爬虫 java 对比 编辑:程序博客网 时间:2024/06/10 19:22

    • 前言
    • 正文之-Service
    • Service几个遗漏的地方

前言

写了一半电脑蓝屏了,没有保存,差点就放弃写这个东西了。但是谁叫咱是程序员呢,好脾气不是吹的,调整一下来写第二遍。
标题是Service的一点理解,其实我本来想写跨进程通信的,但是Service搞得明白对理解跨进程通信有好处,所以顺便也一块写了。不过我是小小菜鸟一枚,大佬发现错误的话欢迎在评论里指正(如果有人看这篇文章的话)
PS:推荐一本书吧,Android开发艺术探索,好书,适合Android有点基础的开发者看。
**

正文之-Service

先贴一个网址吧(Service谷歌官方的API):
https://developer.android.google.cn/guide/components/services.html
中文版文档,妈妈再也不用担心我看不懂英文了。

1.Service是啥,能干什么?
service是一个可以在后台执行长时间运行操作而不提供用户界面的应用组件(面试可以这么回答)。简单来说就是不与用户交互在后台进行操作的组件
通常用来进行下载、播放音乐、I/O操作等。
2.怎样启动一个服务,区别是什么?
生命周期不同:
这里写图片描述
-startService()
当应用组件(如 Activity)通过调用 startService() 启动服务时,服务即处于“启动”状态。一旦启动,服务即可在后台无限期运行,即使启动服务的组件已被销毁也不受影响。很显然,当我们进行下载或者播放音乐的时候,我们不希望锁屏时就停止播放或下载。
-bindService()
当应用组件通过调用 bindService() 绑定到服务时,服务即处于“绑定”状态。绑定服务提供了一个客户端-服务器接口,允许组件与服务进行交互、发送请求、获取结果,甚至是利用进程间通信 (IPC) 跨进程执行这些操作。

在实际使用时,如何去选择两种启动方式呢?主要是看客户端与服务器端需不需要交互。比如说播放音乐,只需要在后台一直播放就好了,start方式就足够了。反之,我们需要从服务器的Service这里取得数据,那么就要通过bind这种方式。
3.如何创建一个Service
(1)首先用自己的类去继承Service类(或者其子类)重写需要的方法。
(2)在Manifest清单文件中进行注册。
(3)开启服务。
从传统上讲,您可以扩展两个类来创建启动服务:
Service
这是适用于所有服务的基类。扩展此类时,必须创建一个用于执行所有服务工作的新线程,因为默认情况下,服务将使用应用的主线程,这会降低应用正在运行的所有 Activity 的性能。
IntentService
这是 Service 的子类,它使用工作线程逐一处理所有启动请求。如果您不要求服务同时处理多个请求,这是最好的选择。 您只需实现 onHandleIntent() 方法即可,该方法会接收每个启动请求的 Intent,使您能够执行后台工作。
通常情况下我们可以使用IntentService来进行操作,因为实现简单,贴代码:

public class HelloIntentService extends IntentService {  /**   * IntentService必须要有一个无参构造,要调用super("xxx"); 不然报错    * xxx为你的服务的名字,我这里就是HelloIntentService   */  public HelloIntentService() {      super("HelloIntentService");  }  /**   * IntentService会在工作线程里处理请求,方法返回时会结束服务   * 因此我们就不需要手动去关闭服务了   */  @Override  protected void onHandleIntent(Intent intent) {      // 在这进行耗时操作      try {          Thread.sleep(5000);      } catch (InterruptedException e) {          // Restore interrupt status.          Thread.currentThread().interrupt();      }  }}

如果还重写其他回调方法(如 onCreate()、onStartCommand() 或 onDestroy()(onBind()除外)),请确保调用超类实现,以便 IntentService 能够妥善处理工作线程的生命周期。
若要求服务执行多线程(而不是通过工作队列处理启动请求),则可扩展 Service 类来处理每个 Intent。示例如下(每隔三秒显示Toast):

public class MyService extends Service {    private  final String TAG = this.getClass().getSimpleName();    private ServiceHandler serviceHandler;    private class ServiceHandler extends Handler{        public ServiceHandler(Looper looper) {            super(looper);        }        @Override        public void handleMessage(Message msg) {            Toast.makeText(MyService.this, ""+Thread.currentThread().getName(), Toast.LENGTH_SHORT).show();            showToast();        }    }    @Override    public int onStartCommand(Intent intent, int flags, int startId) {        Log.e(TAG, "onStartCommand: " );        showToast();        return START_STICKY;    }    public  void showToast(){        Timer timer = new Timer();        timer.schedule(new TimerTask() {            @Override            public void run() {                Message message = serviceHandler.obtainMessage();                serviceHandler.sendMessage(message);            }        },3000);    }    @Override    public void onCreate() {        Log.e(TAG, "onCreate: " );        HandlerThread thread = new HandlerThread("ServiceStartArguments");        thread.start();        Looper looper = thread.getLooper();        serviceHandler = new ServiceHandler(looper);    }    @Nullable    @Override    public IBinder onBind(Intent intent) {        return null;    }}

区别就在于我们需要手动创建一个线程去执行耗时操作,在这里我们用的是HandlerThread,因为不需要我们去手动创建looper。当然在onCreat()里我们只进行了一个操作,你也可以开启多个线程同时进行多项操作。例子里没有对Service进行销毁。自己用时记得销毁哦。
对了,以上都是通过startService()方式启动的,应该不会有人不知道怎么启动Service吧…以防万一我还是贴一下我启动的代码:

Intent intent = new Intent(this, MyService.class);startService(intent);

说完了startService(),接下来该说一说bindService()方式了。
步骤还是三步,一是创建一个类继承自Service或其子类,重写需要重写的方法;二是在清单文件注册;三就是绑定服务了。
首先来看一下我们需要绑定的Service:

package com.example.servicedemo;import android.app.Service;import android.content.Intent;import android.os.Binder;import android.os.IBinder;import android.support.annotation.Nullable;import android.widget.Toast;/** * Created by LZC on 2017/9/18. */public class BindService extends Service {    public class LocalBinder extends Binder{        BindService getService(){         return  BindService.this;        }    }    @Nullable    @Override    public IBinder onBind(Intent intent) {        return new LocalBinder();    }    public void  showToast(){        Toast.makeText(BindService.this, "服务", Toast.LENGTH_SHORT).show();    }}

前面我们说过了,bindService()的方式一般用在需要与后台进行交互的时候。而我在这写的交互就是showToast(),我们真正去开发的时候一般是从后台取得数据,到时候自己根据需求改就可以。
其实能够交互的原理非常简单,就是在我们的onBind()方法里返回了一个IBinder 对象。而这个IBinder 对象能够获得我们Service的一个实例。在客户端我们就能够拿到这个返回的IBinder 对象,通过这个IBinder 对象得到我们的Service的实例,通过这个实例我们就可以调用我们服务端的方法showToast()了。
接下来上客户端的代码:

package com.example.servicedemo;import android.content.ComponentName;import android.content.Intent;import android.content.ServiceConnection;import android.os.IBinder;import android.support.v7.app.AppCompatActivity;import android.os.Bundle;import android.view.View;public class BinderActivity extends AppCompatActivity {    private BindService binderService;    private ServiceConnection serviceConnection = new ServiceConnection() {        @Override        public void onServiceConnected(ComponentName name, IBinder service) {            //连接成功的时候             //service就是我们拿到的Binder对象            BindService.LocalBinder binder = (BindService.LocalBinder) service;            binderService = binder.getService();        }        @Override        public void onServiceDisconnected(ComponentName name) {            //当Service被意外杀死的时候才会调用,自己解除绑定这种不算        }    };    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_binder);        Intent intent = new Intent(this,BindService.class);        bindService(intent,serviceConnection,BIND_AUTO_CREATE);    }    public void bindService(View view) {        //这是个点击事件,点击出Toast        binderService.showToast();    }}

两种方式的区别应该很明显了,我再放一起比较一下:
startService(intent);
bindService(intent,serviceConnection,BIND_AUTO_CREATE);
很显然,bind多了两个参数。serviceConnection就是Service连接对象,通过这个对象我们才能获得后台服务的对象。BIND_AUTO_CREATE这个是个flag,除了它还有下面这三个:BIND_DEBUG_UNBIND,BIND_IMPORTANT,BIND_NOT_FOREGROUND。平时用BIND_DEBUG_UNBIND就行,BIND_DEBUG_UNBIND通常用于调试场景中判断绑定的服务是否正确,官方说只有调试的时候用这个,因为会造成内存泄漏,第三个和第四个是对立的,一个是允许Service具有前台优先级(如果你希望你的服务不那么容易被杀死),一个是没有前台优先级,只能运行在后台。

Service几个遗漏的地方

其实写到这里发现了几个遗漏的地方
1.在清单文件注册 Service的时候有挺多属性,我貌似应该介绍一下

<service          android:description="string resource"         android:enabled=["true" | "false"]         android:exported=["true" | "false"]         android:icon="drawable resource"         android:isolatedProcess=["true" | "false"]         android:label="string resource"         android:name="string"         android:permission="string"         android:process="string" ></service>
  • android:description:描述,没错就是描述,没什么蛋用,不过可以提示一下自个这个Service是干嘛的。建议用@string/xxx这样。

  • android:enabled:Service是否可以被系统实例化,默认为True,只要不是脑袋有问题就不会设为false,毕竟服务写出来就是要用的吗。

  • android:exported:其他应用程序的组件是否可以调用服务或与其交互。很显然,不跨进程时最好关闭,跨进程时需要打开。默认值取决于服务是否包含意图过滤器(intent fillter)没有过滤器时为false,有的时候为true。

  • android:icon:图标….我也不知道有啥用,下一个。

  • android:isolatedProcess:是否在特殊进程下运行。没用过,默认为false。

  • android:label:显示给用户的这个service的名字。如果不设置,将会默认使用application的label属性。

  • android:name:唯一必填属性,Service的路径。

  • android:permission:服务的权限声明。实体必须具有的权限的名称才能启动服务或绑定到该服务。 如果startService(),bindService()或stopService()的调用者尚未被授予此权限,则该方法将不起作用,并且Intent对象将不会传递到服务。查了n多文章才查到。一般是跨进程的时候使用,代码如下:
    服务器端(在Manifest里):

//自定义一个permission,与Application同级<permission        android:name="com.anddle.serviceaccess"        android:label="service permission"        android:protectionLevel="normal" />//在你的Service里面声明这条属性<service android:name=".AIDLService"            android:permission="com.anddle.serviceaccess"            >            <intent-filter>                <action android:name="com.example.lzc.server"></action>                <category android:name="android.intent.category.DEFAULT"></category>            </intent-filter>        </service>>

客户端代码(Manifest里):

//声明我们自定义的permission,不声明的话应用直接crash<uses-permission android:name="com.anddle.serviceaccess"/>

所以说permission属性还是可能会用到的!

  • android:process:为服务指定进程。默认启动的service是运行在主进程中的,设置后会运行在独立的进程中,比如说android:process=“:remote”(“:”的意思是当前包,和android:process=”com.example.lzc.ipcserver.remote”等价 )

2.startCommend()方法几个返回值代表什么意思也没有说
返回值就有下面四种:
START_STICKY:如果service进程被kill掉,保留service的状态为开始状态,但不保留递送的intent对象。随后系统会尝试重新创建service,由于服务状态为开始状态,所以创建服务后一定会调用onStartCommand(Intent,int,int)方法。如果在此期间没有任何启动命令被传递到service,那么参数Intent将为null。

  • START_NOT_STICKY:“非粘性的”。使用这个返回值时,如果在执行完onStartCommand后,服务被异常kill掉,系统不会自动重启该服务。

  • START_STICKY:如果service进程被kill掉,保留service的状态为开始状态,但不保留传递的intent对象。随后系统会尝试重新创建service,创建服务后一定会调用onStartCommand(Intent,int,int)方法。如果在此期间没有任何启动命令被传递到service,那么参数Intent将为null。适用于不执行命令、但无限期运行并等待作业的媒体播放器。

  • START_STICKY_COMPATIBILITY:START_STICKY的兼容版本,但不保证服务被kill后一定能重启。

  • START_REDELIVER_INTENT:重传Intent。使用这个返回值时,如果在执行完onStartCommand后,服务被异常kill掉,系统会自动重启该服务,并将Intent的值传入。适用于恢复后立即执行的操作,例如文件下载。
    3.start和bind混合使用时怎样才算彻底销毁一个Service。
    首先,问题是销毁Service,而不是停止Service,这俩不是一个概念。
    不管我们是先start后bind,还是先bind后start,只要你混合使用之后,Service的生命周期就会发生变化。销毁服务只有下面两种情况
    ①先解除绑定(onUnbind,如果绑定多个Service需要全部解绑,保证没有ServiceConnection还在连接)后停止服务(onStopService),此时就会调用Service的onDestroy();
    ②先停止服务(onStopService)后解绑(onUnbind),这样也会调用Service的onDestroy();
    不能够通过单一的stop||unbind去销毁服务。
    写的很枯燥,总结一下就是销毁服务时必须销毁干净,你start,bind几次,就要stop,unbind几次。
    到这Service应该说的差不多了,还剩一些常用但是比较零碎的知识,比如说怎么设置为前台服务啦,怎么样提高优先级啦,避免被杀死之类的,写在这感觉篇幅就太长了,又兴趣的话可以直接点击这儿去看一看。

原创粉丝点击