Android Service简析及双进程守护实现

Service

service是Android四大组件之一,和Activity同级别
但是service不能自己运行,只能后台运行没有界面交互,并且可以和其他组件进行交互
比如:控制歌曲后台播放

如何开启Service

有两种方式,看官方图解生命周期示意图


Service生命周期.png

Context.startService(Intent service)

startService方式启动,会走OnCreat---->onStartCommand---->OnDestroy
如果多次调用startService不会每次都执行OnCreat(),OnCreat()只会调用一次,
但是每次都会调用onStartCommand()

Context.bindService(Intent service, ServiceConnection conn,int flags)

bindService方式启动,会走OnCreat---->onBind---->onUnbind---->OnDestroy
单独用bindService方式启动,flags最好用Context.BIND_AUTO_CREATE
总结:

  1. start方式启动,Service和Activity(Context,大多情况是activity)的生命周期是不关联的
    调用者退出后service仍然存在
  2. Bind方式绑定服务,Service和Activity的生命周期是关联的,除了绑定还能操作service,
    Service随着调用者退出而销毁

但是:也可以同时用两种方式启动服务
停止服务的时候需要同时stopService和unbindService,才能停止服务

IntentService和Servicede 区别

public abstract class IntentService extends Service {
    private volatile Looper mServiceLooper;
    private volatile ServiceHandler mServiceHandler;
    private String mName;
    private boolean mRedelivery;
    ......
    private final class ServiceHandler extends Handler {
        public ServiceHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) {
            onHandleIntent((Intent)msg.obj);
            stopSelf(msg.arg1);
        }
    }
  ......
}
  1. IntentService 继承自 Service
  2. Service运行在主线程中,不能做耗时处理,否则会ANR
  3. IntentService 是创建一个工作线程处理多线程任务,会自动结束,不需要手动调用stopService
  4. IntentService 重写了onBind()返回null
    重写了onstartCommand()提供了默认实现,将请求的intent添加到队列中

不建议通过bindservice方式启动IntentService
IntentService本质 = Handler + HandlerThread(继承Thread):

跨进程通信AIDL和Binder

要实现不同apk之间通信,服务端和客户端要有相同的aidl,并且包名一致

aidl文件:

package com.qingguoguo.******;

// Declare any non-default types here with import statements

interface MyAidl {
    String getUserName();
    String getUserPwd();
}

客户端演示代码:

public void initData(Bundle bundle) {
        Intent intent = new Intent();
        intent.setAction("com.study.aidl.user");
        // 在Android 5.0之后google出于安全的角度禁止了隐式声明Intent来启动Service.也禁止使用Intent filter.否则就会抛异常
        intent.setPackage("com.qingguoguo.*******");
        //启动服务
        bindService(intent, new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                mYAidl = MyAidl.Stub.asInterface(service);
                try {
                    Log.e("TAG", "调试:" + mYAidl.getUserName() + "," + mYAidl.getUserPwd());
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }

            @Override
            public void onServiceDisconnected(ComponentName name) {
            }
        }, Context.BIND_AUTO_CREATE);
    }

服务端:
需要注册有对应的服务,并设置Action,重写onBind方法

 @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        LogUtils.i(TAG,"MessageService onBind");
        return mIBinder;
    }

    private IBinder mIBinder = new MyAidl.Stub() {
        @Override
        public String getUserName() throws RemoteException {
            return "qingguoguo";
        }

        @Override
        public String getUserPwd() throws RemoteException {
            return "123456";
        }
    };

先打开服务端app,再打开客户端app,onServiceConnected就会打印出相关信息

public interface MyAidl extends android.os.IInterface
{
/** Local-side IPC implementation stub class. */
public static abstract class Stub extends android.os.Binder implements com.qingguoguo.connotationjoke.MyAidl
{
private static final java.lang.String DESCRIPTOR = "com.qingguoguo.connotationjoke.MyAidl";
/** Construct the stub at attach it to the interface. */
public Stub()
{
this.attachInterface(this, DESCRIPTOR);
}
/**
 * Cast an IBinder object into an com.qingguoguo.connotationjoke.MyAidl interface,
 * generating a proxy if needed.
 */
public static com.qingguoguo.connotationjoke.MyAidl asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof com.qingguoguo.connotationjoke.MyAidl))) {
return ((com.qingguoguo.connotationjoke.MyAidl)iin);
}
return new com.qingguoguo.connotationjoke.MyAidl.Stub.Proxy(obj);
}
@Override public android.os.IBinder asBinder()
{
return this;
}
@Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
{
switch (code)
{
case INTERFACE_TRANSACTION:
{
reply.writeString(DESCRIPTOR);
return true;
}
case TRANSACTION_getUserName:
{
data.enforceInterface(DESCRIPTOR);
java.lang.String _result = this.getUserName();
reply.writeNoException();
reply.writeString(_result);
return true;
}
case TRANSACTION_getUserPwd:
{
data.enforceInterface(DESCRIPTOR);
java.lang.String _result = this.getUserPwd();
reply.writeNoException();
reply.writeString(_result);
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
private static class Proxy implements com.qingguoguo.connotationjoke.MyAidl
{
private android.os.IBinder mRemote;
Proxy(android.os.IBinder remote)
{
mRemote = remote;
}
@Override public android.os.IBinder asBinder()
{
return mRemote;
}
public java.lang.String getInterfaceDescriptor()
{
return DESCRIPTOR;
}
@Override public java.lang.String getUserName() throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
java.lang.String _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_getUserName, _data, _reply, 0);
_reply.readException();
_result = _reply.readString();
}
finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
@Override public java.lang.String getUserPwd() throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
java.lang.String _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_getUserPwd, _data, _reply, 0);
_reply.readException();
_result = _reply.readString();
}
finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
}
static final int TRANSACTION_getUserName = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_getUserPwd = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
}
public java.lang.String getUserName() throws android.os.RemoteException;
public java.lang.String getUserPwd() throws android.os.RemoteException;
}

这个文件是开发工具帮我们生成的
MyAidl是一个接口
Stub是一个抽象类,继承了Binder,实现了MyAidl接口
Proxy是一个static类,也实现了MyAidl,可以看做是MyAidl接口的代理,实现了里面的抽象方法

通信流程:

客户端通过bindService连接服务端,会调用服务端Service的onBind方法
返回一个UserCalcAIDL.Stub的mBinder实例,然后将该实例返回给客户端的
onServiceConnected()方法里面,有两个参数有一个IBinder就是服务端返回的mBinder,
然后客户端通过该实例建立一个新的AIDL.Stub.Proxy对象,
我们在客户端调用获取信息方法的时候其实就是调用的AIDL.Stub.Proxy里面的getUserName()方法,
通过mBinder的onTransact()方法写入数据,然后获取数据

Android系统进程间通信机制Binder的总体架构,它由
Client、Server、Service Manager和驱动程序Binder四个组件构成。
Service Manager,它是整个Binder机制的守护进程,用来管理开发者创建的各种Server,
并且向Client提供查询Server远程接口的功能。
Service Manager在用户空间的源代码位于frameworks/base/cmds/servicemanager目录下,主要是由binder.h、binder.c和service_manager.c三个文件组成

Service为什么被杀死

  1. 手机内存不足
  2. 第三方的流氓软件清理
  3. 三方Rom定制,在退出应用的时候做了一些事情

进程的优先级

  • 前台进程
  • 可见进程
  • 服务进程
  • 后台进程
  • 空进程
    越是前台越不容易被杀,越是后面越容易被杀

LowmemoryKiller的工作机制

LowmemoryKiller会在内存不足的时候扫描所有的用户进程,
找到不是太重要的进程杀死,至于LowmemoryKiller杀进程够不够狠,
要看当前的内存使用情况,内存越少,下手越狠,相同级别的,占用内存大,先杀死

针对于此我们可以做以下优化
1.提高进程的优先级,其实就是减小进程的p->oomkilladj(越小越重要),
如启动Service调用startForeground()尽量提高进程的优先级;

2.当应用退到后台适当释放资源然后降低APP的内存占用量,因为在oom_adj相同的时候,
会优先干掉内存消耗大的进程;

3.对于要一直在后台运行的Service,占用内存要小。

双进程守护实现

双进程守护相互唤醒以及5.0以上的JobSheduler

public class GuardService extends Service {
    public final static String TAG = "GuardService";
    private final static int GUARD_SERVICE_ID = 2;

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

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return mIBinder;
    }

    private IBinder mIBinder = new GuardServiceAidl.Stub() {
    };

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        //提高进程的优先级
        startForeground(GUARD_SERVICE_ID, new Notification());
        //绑定建立连接
        bindService(new Intent(this, MessageService.class),
                mConnection, Context.BIND_IMPORTANT);
        return START_STICKY;

    }

    private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            //连接上
            ToastUtils.showShort("建立连接 MessageService");
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            //断开连接,重新启动,绑定
            startService(new Intent(GuardService.this, MessageService.class));
            bindService(new Intent(GuardService.this, MessageService.class), mConnection, Context.BIND_IMPORTANT);
        }
    };

JobService 必须要在5.0以上
并在xml中添加权限

//注册清单中添加权限
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>

<service
     android:name=".doublesevice.JobWakeUpService"
     android:enabled="true"
     android:permission="android.permission.BIND_JOB_SERVICE"/>

if (Build.VERSION.SDK_INT>Build.VERSION_CODES.LOLLIPOP){
       startService(new Intent(this, JobWakeUpService.class));
}

JobScheduler API为你的应用执行一个操作。与AlarmManager不同的是这个执行时间是不确定的。除此之外,JobScheduler API允许同时执行多个任务。这允许你的应用执行某些指定的任务时不需要考虑时机控制引起的电池消耗


@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public class JobWakeUpService extends JobService {
    private final static int JOB_WAKEUP_SERVICE_ID = 2;
    private static final java.lang.String TAG = "JobWakeUpService";

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        //开启轮寻
        ComponentName componentName = new ComponentName(this, JobWakeUpService.class);
        JobInfo.Builder jobBuilder = new JobInfo.Builder(JOB_WAKEUP_SERVICE_ID, componentName);
        //设置轮寻时间
        jobBuilder.setPeriodic(5000);
        JobScheduler jobScheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
        jobScheduler.schedule(jobBuilder.build());
        return START_STICKY;
    }

    @Override
    public boolean onStartJob(JobParameters params) {
        LogUtils.i(TAG,"onStartJob:"+params.toString());
        //开启定时任务,轮寻,看MessageService有没有被杀死
        if (!serviceAlive(MessageService.class.getName())) {
            startService(new Intent(this, MessageService.class));
        }
        return false;
    }


    /**
     * 判断服务是否还活着
     *
     * @param serviceName 包名+服务的类名
     */
    private boolean serviceAlive(String serviceName) {
        boolean isWork = false;
        ActivityManager activityManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
        List<ActivityManager.RunningServiceInfo> runningServices = activityManager.getRunningServices(100);
        if (runningServices.isEmpty()) {
            return false;
        }
        for (int i = 0; i < runningServices.size(); i++) {
            String className = runningServices.get(i).service.getClassName();
            if (serviceName.equals(className)) {
                isWork = true;
                break;
            }
        }
        return isWork;
    }

    @Override
    public boolean onStopJob(JobParameters params) {
        return false;
    }
}

服务Service与线程Thread的区别

  • 两者概念的迥异

    • Thread 是程序执行的最小单元,它是分配CPU的基本单位
      android系统中UI线程也是线程的一种,当然Thread还可以用于执行一些耗时异步的操作。

    • Service是Android的一种机制,服务是运行在主线程上的,它是由系统进程托管。
      它与其他组件之间的通信类似于client和server,是一种轻量级的IPC通信,这种通信的载体是binder
      它是在linux层交换信息的一种IPC,而所谓的Service后台任务只不过是指没有UI的组件罢了。

  • 两者的执行任务迥异

    • 在android系统中,线程一般指的是工作线程(即后台线程),而主线程是一种特殊的工作线程,它负责将事件分派给相应的用户界面小工具,如绘图事件及事件响应,因此为了保证应用 UI 的响应能力主线程上不可执行耗时操作。如果执行的操作不能很快完成,则应确保它们在单独的工作线程执行。

    • Service 则是android系统中的组件,一般情况下它运行于主线程中,因此在Service中是不可以执行耗时操作的,否则系统会报ANR异常,之所以称Service为后台服务,大部分原因是它本身没有UI,用户无法感知(当然也可以利用某些手段让用户知道),但如果需要让Service执行耗时任务,可在Service中开启单独线程去执行。

  • 两者使用场景

    • 当要执行耗时的网络或者数据库查询以及其他阻塞UI线程或密集使用CPU的任务时,都应该使用工作线程(Thread),这样才能保证UI线程不被占用而影响用户体验。

    • 在应用程序中,如果需要长时间的在后台运行,而且不需要交互的情况下,使用服务。比如播放音乐,通过Service+Notification方式在后台执行同时在通知栏显示着。

  • 两者的最佳使用方式

    在大部分情况下,Thread和Service都会结合着使用
    比如下载文件,一般会通过Service在后台执行+Notification在通知栏显示+Thread异步下载
    再如应用程序会维持一个Service来从网络中获取推送服务。
    在Android官方看来也是如此,所以官网提供了一个Thread与Service的结合
    来方便我们执行后台耗时任务,它就是IntentService,当然 IntentService并不适用于所有的场景
    但它的优点是使用方便、代码简洁,不需要我们创建Service实例并同时也创建线程
    某些场景下还是非常赞的!
    由于IntentService是单个worker thread,所以任务需要排队,因此不适合大多数的多任务情况。

  • 两者的真正关系

    • 两者没有半毛钱关系。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容

  • 2.1 Activity 2.1.1 Activity的生命周期全面分析 典型情况下的生命周期:在用户参与的情况下...
    AndroidMaster阅读 3,017评论 0 8
  • 1.什么是Activity?问的不太多,说点有深度的 四大组件之一,一般的,一个用户交互界面对应一个activit...
    JoonyLee阅读 5,724评论 2 51
  • 【Android Service】 Service 简介(★★★) 很多情况下,一些与用户很少需要产生交互的应用程...
    Rtia阅读 3,135评论 1 21
  • 本代码示例是基于聚合数据全国车辆违章查询API的调用,使用前你需要:①:通过 https://www.juhe.c...
    yaojq阅读 658评论 0 1
  • 今早醒来感觉左边肩膀痛,下了床左臂抬起来都有点困难,赶紧转转左臂缓解下疼痛。我知道是晚上喂夜奶后太困没有盖...
    宽雅阅读 218评论 0 1