Service工作中的方方面面

版权声明:本文为小斑马伟原创文章,转载请注明出处!

一、service和线程的区别和场景

线程:是程序执行的最小单元。也是分配CPU的基本单位。在android中,UI线程分为主线程和工作线程。主线程一般负责UI的绘制和事件的响应操作,为了保证UI线程的响应能力, 一般主线程不会执行耗时的逻辑操作,否者容易造成ANR和用户体验效果不佳问题。
Service:是Android提供的一种机制,四大主件之一。运行在主线程中,是系统进程托管。

二、如何管理service生命周期

  • 1.通过绑定服务,来进行service和activity之间的绑定。
  • 2.直接开启一个服务。两种生命周期不一样。但都会回调onCreate和onDestory方法。
    Service的生命周期:一种是通过startService创建的生命周期,另外一种是通过bindservice创建的生命周期 两种都是从onCreate开始和onDestory结束。通过生命周期的方法,可以监控服务整体的执行过程。包括创建,运行,销毁。而服务有效的生命周期分别是在onStartCommand和onBind开始的。
    startService:它分别会回调onCreate和onStartCommand方法。一个Service被startService多次启动之后,它的onCreate方法只会调用一次。而onStartCommand方法调用的次数和startService次数是一样的。
    stopService:是关闭Service服务。手动调用之后,会调用onDestory方法。如果一个Service被启动,且被绑定的时候,如果没有解绑的情况下,stopService是无法停止服务的。
    bindService: 作用就是绑定service服务,分别会调用onCreate和onBind方法。
    unBindService: 作用就是解绑service服务,内部会调用onUnbind方法和onDestory来解绑服务和销毁服务的操作。

启动和绑定Service服务先后次序的问题

  • 1.先绑定服务后启动服务: 当service先与绑定状态运行了,然后再启动服务。绑定服务将会转换为启动服务状态。 如果之前绑定的Activity被销毁了,也不会影响服务的运行。直到收到停止服务的时候,才会停止运行下去。
  • 2.先启动服务后绑定服务:如果当前serfice服务已经启动了,再进行绑定。不会转换为绑定服务状态。会与Activity绑定,即使Activity解除绑定了,服务还有在后台运行。

启动服务的优先级比绑定服务的优先级要高,服务再其托管进程的主线程中运行(UI线程)不会创建自己的线程,也不会单独在进程中运行。如果在服务中做耗时的操作,必须开启子线程。

三、Service和IntentService区别

Service是用于后台服务的,当应用程序被挂到后台的时候,为了保证应用的某些组件后台可以继续工作。这时候就会引用Service。service不是一个独立的进程,更不是独立的线程。它是依赖于我们应用程序的主线程的。不建议在service中编写耗时的逻辑和操作,否者会引起ANR。因此引入IntentService。
IntentService:是继承并处理异步请求的一个类 。内部有一个工作线程HandlerThread来处理耗时操作。使用上跟service是一样的。在执行完任务之后,IntentService会自动的停止??梢远嗥鹌舳疘ntentService,而每次耗时的操作都会以工作队列的方式在IntentService的回调方法中执行。每次只执行一个工作线程,执行完第一个依次执行下一个。

    1. IntentService是继承并处理异步请求的一个类。
    1. 内有一个工作线程来处理耗时操作 。
    1. IntentService内部则是通过消息的方式发送给HandlerThread的,然后由Handler中的Looper来处理消息。

IntentService源码分析
IntentService构造方法

/**
 * Creates an IntentService.  Invoked by your subclass's constructor.
 *
 * @param name Used to name the worker thread, important only for debugging.
 */
public IntentService(String name) {
    super();
    mName = name;
}

分析:该构造方法需在子类中调用,用于创建一个IntentService对象。参数name用于定义工作线程的名称,仅仅用于调式作用。我们知道Service服务的生命周期是从onCreate方法开始的。那么就来看看IntentService#onCreate方法吧。
IntentService#onCreate方法

public void onCreate() {
    // TODO: It would be nice to have an option to hold a partial wakelock
    // during processing, and to have a static startService(Context, Intent)
    // method that would launch the service & hand off a wakelock.

    super.onCreate();
    HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
    thread.start();

    mServiceLooper = thread.getLooper();
    mServiceHandler = new ServiceHandler(mServiceLooper);
}

分析:该方法首先利用HandlerThread类创建了一个循环的工作线程thread,然后将工作线程中的Looper对象作为参数来创建ServiceHandler消息执行者。由另一篇博客Android HandlerThread 源码分析可知,HandlerThread+Handler构建成了一个带有消息循环机制的异步任务处理机制。因此开发者就可以将异步任务封装成消息的形式发送到工作线程中去执行了。Service服务生命周期第二步执行IntentService#onStartCommand方法。

IntentService#onStartCommand方法

/**
 * You should not override this method for your IntentService. Instead,
 * override {@link #onHandleIntent}, which the system calls when the IntentService
 * receives a start request.
 * @see android.app.Service#onStartCommand
 */
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    onStart(intent, startId);
    return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
}

分析:在IntentService子类中你无需重写该方法。然后你需要重写onHandlerIntent方法,
系统会在IntentService接受一个请求开始调用该方法。我们看到在该方法中仅仅是调用了onStart方法而已,跟踪代码:

四、Parcelable(android 特定序列化的接口)和Serializable

因为存在内存中的对象都是暂时的,为了对象的状态保存下来。需要把对象写到磁盘和其他地方中。过程就是对象序列化。
序列化:内存中对象-->磁盘
反序列化:磁盘中对象-->内存
区别:从实现上和效率上区分

/**
 * Created by Mjj on 2018/8/11.
 */

public class SerializableImplement implements Serializable {
/**
 * 生成序列号标识
 */
private static final long serialVersionUID = -2083503801443301445L;

private int id;
private String name;


public int getId() {
    return id;
}

public void setId(int id) {
    this.id = id;
}

public String getName() {
    return name;
}

public void setName(String name) {
    this.name = name;
}

Serializable接口:专门对对象序列化和反序列化操作的。声明了一个serialVersionID标识。用来辅助我们序列化和反序列化过程的。只有当序列化和反序列化的ID相同。才能被反序列化。序列化的过程时:会把serialVersionID写到序列化的文件中,反序列化的时候会在序列化的文件中检查这个ID。判断是否一致,如果一致进行反序列化,否者失败。
实现简单,但是内存开销非常大。

public class ParcableImplement implements Parcelable {
public int id;
public String name;

/**
 * 当前对象的内容描述,一般返回0即可
 *
 * @return
 */
@Override
public int describeContents() {
    return 0;
}

protected ParcableImplement(Parcel in) {
    this.id = in.readInt();
    this.name = in.readString();
}

/**
 * 将当前对象写入序列化结构中
 *
 * @param dest
 * @param flags
 */
@Override
public void writeToParcel(Parcel dest, int flags) {
    dest.writeInt(this.id);
    dest.writeString(this.name);
}

/**
 * public static final一个都不能少,内部对象CREATOR的名称也不能改变,必须全部大写。
 * 重写接口中的两个方法:
 * createFromParcel(Parcel in) 实现从Parcel容器中读取传递数据值,封装成Parcelable对象返回逻辑层,
 * newArray(int size) 创建一个类型为T,长度为size的数组,供外部类反序列化本类数组使用。
 */
public static final Creator<ParcableImplement> CREATOR = new Creator<ParcableImplement>() {
    /**
     * 从序列化后的对象中创建原始对象
     */
    @Override
    public ParcableImplement createFromParcel(Parcel in) {
        return new ParcableImplement(in);
    }

    /**
     * 创建指定长度的原始对象数组
     * @param size
     * @return
     */
    @Override
    public ParcableImplement[] newArray(int size) {
        return new ParcableImplement[size];
    }
};

Parcelable接口:在性能上比Serializable接口好,因为内存开销方面比Serializable要小。在内存间传输数据一般推举用Parcelable接口。缺点就是代码实现麻烦。通过writeToParcel映射成Parcel对象。然后通过Creator映射成我们的对象。可以简单把Parcel对象看作是读写流。

五、AIDL

Binder:最常见的应用就是AIDL。是android接口定义语言,是进程间通信(IPC)机制。在android中一个进程无法去访问另外一个进程的内存。这是就可以使用AIDL,实现客户端和服务端进程通信的机制,进程间相互通信都认可的一个接口。通过这个接口,定义好自己的AIDL的接口文件,通过IDE的编译系统就可以自动生成Binder接口。

  • 1.创建AIDL:实体对象、新建AIDL文件、make工程
  • 2.服务端:新建Service、创建Binder对象、定义方法
  • 3.客户端:实现serviceConnection、BindService

Person类: 用于序列化和反序列化操作。为了服务端和客户端跨进程通信使用。

public class Person implements Parcelable {
private String mName;

public Person(String name) {
    mName = name;
}

protected Person(Parcel in) {
    mName = in.readString();
}

public static final Creator<Person> CREATOR = new Creator<Person>() {
    @Override
    public Person createFromParcel(Parcel in) {
        return new Person(in);
    }

    @Override
    public Person[] newArray(int size) {
        return new Person[size];
    }
};

@Override
public int describeContents() {
    return 0;
}

@Override
public void writeToParcel(Parcel dest, int flags) {
    dest.writeString(mName);
}

@Override
public String toString() {
    return "Person{" +
            "mName='" + mName + '\'' +
            '}';
}

AIDE文件

package com.zhonghong.bean;

parcelable Person;

IMyAidl文件

package com.zhonghong.aidl;

import com.zhonghong.bean.Person;

interface IMyAidl {
    void addPerson(in Person person);

    List<Person> getPersonList(); 
}

服务端service实现

public class MyAidlService extends Service {
private final String TAG = this.getClass().getSimpleName();

private ArrayList<Person> mPersons;

/**
 * 创建生成的本地 Binder 对象,实现 AIDL 制定的方法
 */
private IBinder mIBinder = new IMyAidl.Stub() {
    
    @Override
    public List<Person> getPersonList() throws RemoteException {
        return mPersons;
    }
    
    @Override
    public void addPerson(Person person) throws RemoteException {
        mPersons.add(person);
    }
};
/**
 * 客户端与服务端绑定时的回调,返回 mIBinder 后客户端就可以通过它远程调用服务端的方法,即实现了通讯
 *
 * @param intent
 * @return
 */
@Override
public IBinder onBind(Intent intent) {
     mPersons = new ArrayList<Person>();
     Log.d(TAG, "MyAidlService onBind");
     return mIBinder;
}

客户端:

public class MyServiceActivity extends Activity {
private Button mBtn;

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

    mBtn.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            Intent intent1 = new Intent(getApplicationContext(),
                    MyAidlService.class);
            bindService(intent1, mConnection, BIND_AUTO_CREATE);
            addPerson();
        }
    });
}

private IMyAidl mAidl;

private ServiceConnection mConnection = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        // 连接后拿到 Binder,转换成 AIDL,在不同进程会返回个代理
        mAidl = IMyAidl.Stub.asInterface(service);
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {
        mAidl = null;
    }
};

List<Person> personList;

public void addPerson() {
    Random random = new Random();
    Person person = new Person("shixin" + random.nextInt(10));
    
    try {
        mAidl.addPerson(person);
        personList = mAidl.getPersonList();
    } catch (RemoteException e) {
        e.printStackTrace();
    }
}

Aidl本质就是简化了进程间通信的一种方式,内核实质还是Binder机制。Stub内部类就是Bidner包装的一个类。
一、构造方法

/**
 * Construct the stub at attach it to the interface.
 * 构造方法
 */
public Stub() {
    this.attachInterface(this, DESCRIPTOR);
}
 //binder中的方法
public void attachInterface(IInterface owner, String descriptor) {
   mOwner = owner;
   mDescriptor = descriptor;
}

构造方法中调用了binder中的attachInterface(Interface owner, String descriptor)方法,其中descriptor??梢钥醋鍪墙痰奈ㄒ槐晔?,,IInterface c参数则把Stub自己传了进去,这个在后面asInterface()方法中会用到。
二、asInterface(android.os.IBinder obj)方法。这个方法是在绑定服务成功后客户端调用的,用于在获取到服务端返回的IBInder对象后,将其转换为对应的具有功能方法的对象,毕竟IBinder只是一个具有跨进程传输的接口。(类似是把一个接口转换成它对应的实现类,可以这么理解,
但并不是这样的)。
binder和现在的进程是同一进程,就返回Stub类,否则返回Stub的代理类Proxy类。至于Proxy。

android.os.IInterface iin = queryLocalInterface (DESCRIPTOR) 得到的结果返回不同的值。如果iin不空就返回iin,否则就返回Stub的一个代理类??匆幌聁ueryLocalInterface (DESCRIPTOR)
binder和现在的进程是同一进程,就返回Stub类,否则返回Stub的代理类Proxy类。至于Proxy.

add方法但是并没有真正的去做add方法的业务功能,而是把参数封装到了Parcel 中。通过binder传递给了远程的服务??梢钥闯霭蠖ㄔ冻谭窈螅饔迷冻谭竦姆椒ㄊ峭ü葱姓飧龃砝嘀械亩杂Ψ椒?,
该方法再通过 mRemote.transact(Stub.TRANSACTION_add, _data, _reply, 0)把数据传递到远程服务,等远程服务执行完后再通过代理 _result = _reply.readInt();
获取到结果返回给我们的程序的。

Stub中的onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags)方法。这个方法作为服务端的方法,负责接收远程客户端通过mRemote.transact(Stub.TRANSACTION_add, _data, _reply, 0);发送过来的数据并处理的。

  • 1、asInterface(android.os.IBinder obj) 用来根据不同进程返回Stub类自己还是Proxy代理类
  • 2、代理类proxy 用来向远程进程包装数据、发送数据、解析返回结果
  • 3、onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags)方法 作为服务端进程调用,用来处理客户端进程传递过来的数据
  • 4、接口的功能方法,Stub继承了接口,这些方法用来处理客户端进程的具体业务。
?著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,992评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,212评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,535评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,197评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,310评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,383评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,409评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,191评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,621评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,910评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,084评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,763评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,403评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,083评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,318评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,946评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,967评论 2 351

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,932评论 25 707
  • 用两张图告诉你,为什么你的 App 会卡顿? - Android - 掘金 Cover 有什么料? 从这篇文章中你...
    hw1212阅读 12,709评论 2 59
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,644评论 18 139
  • 当今江湖,始终有一把剑的传说,一把穿云剑。 古人云,一支穿云箭,千军万马来相见。这把剑并不是那支箭。 那支穿云箭,...
    软萌淑瓜瓜呱呱呱阅读 576评论 12 14
  • 《怀香记》是明代传奇戏曲家陆采根据《晋书.贾谧传》所记载的“韩寿偷香”的爱情故事所改编的剧本。 “韩寿偷香”是中国...
    水乡醉客阅读 551评论 0 2