Android-Service小结

虽然是Android四大组件之一,但从开发Android以来基本上没怎么用过,真是惭愧,今天有时间来倒腾倒腾Service,作为一个假Android,还是总结下好 😄,省得后面需要了又到处去找。
一说起Service,印象里只有在需要处理耗时的后台任务时才会想起它,实际开发中应该也是这种情况居多吧。好了,废话不多说。下面开始整理:
按照大的分类来说Service可以分为两种:Start ServiceBound Service,他们都要继承自Service基类。

1. Start Service

1.先上代码,首先,自定义Service:
public class MyService extends Service {

    private static final String TAG = "MyService";

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

    @Override
    public void onCreate() {
        super.onCreate();
        Log.w(TAG, "in onCreate");
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {

        Log.w(TAG, "in onStartCommand");
        Log.w(TAG, "MyService: " + this);
        String name = intent.getStringExtra("name");
        Log.w(TAG, "name: " + name);
        return START_STICKY;
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.w(TAG, "in onDestroy");
    }
}

自定义Service只需要实现基类中的onBind(...)方法就可以了,这个方法的返回值只对Bound Service有用,这里直接返回nll就可以了。
后面的onCreate()onStartCommand(...)onDestroy(...)都是生命周期方法,一个service start的时候会先调用onCreate,再调用onStartCommand。
注意:同一个Service,只能用startService(...)启动一次,后面再启动也不会执行onCreate()方法了,直接执行onStartCommand(...),同理,一个service不管被启动多少次,只需要调用一次stopService()(或者service自己调用stopSelf())即可终止service。

然后看下onStartCommand(...)这个方法

onStartCommand(Intent intent, int flags, int startId)

intent:包含外部Client启动service时传递的一些参数。
flags: 默认是0,对应的常量是START_STICKY_COMPATIBILITY
startId: 当多次start该Service的时候,startId会递增。

返回值:
START_NOT_STICKY: 当service因系统内存不足被kill后,即时未来系统内存足够了也不会主动去重新创建该service,除非程序中再次调用startService()启动该service.
START_STICKY: 和START_NOT_STICKY不同,被kill后,未来系统内存足够后会自动重建该service,重建后会调用onStartCommand(),但其中的intent参数会是null,pendingintent除外。
START_REDELIVER_INTENT: 和START_STICKY唯一的不同是,重建后onstartCommand()方法中的intent为最后一次startService()中的intent.
最常用的就这三种,其他的类型需要的时候再去翻一下即可。

最后需要注意的是,如果用户强制kill了service,onDestroy()是不会执行的。

2. AndroidManifest.xml中注册

必须在AndroidManifest.xml中进行注册,如果不注册的话也不会崩溃异常,所以一定要注意:

<service
            android:name=".MyService"
            android:enabled="true"
            android:exported="false"
            android:icon="@drawable/ic_launcher_background"
            android:isolatedProcess="true"
            android:label="@string/app_name"
            android:permission="@string/app_name"
            android:process=":Myservice"/>

如果service要运行在单独的进程中,需要在process中指明此进程名称
当此service需要对其他App开放时,exported设为true即可。

3. 启动service
Intent serviceIntent = new Intent(MainActivity.this, MyService.class);
startService(serviceIntent);

调用startService(...)即可启动service。这里,当Client可通过intent传递参数给service,当service需要传递参数给Client的时候,可以使用广播,Client只要注册该广播就可以了。

以上就是Start Service的简单用法了。

2. Bound Service

Bound ServiceStart Service不同的是它的生命周期依赖于Client,当Client销毁的时候,它也就不存在了。另外,Bound Service可以通过Binder对象来和Client进行通信。

Bound Service的使用过程是这样的:
1. 自定义Service继承自Service,并实现onBind()方法,返回具体的Binder对象
2.Client通过ServiceConnection接口自定义一个ServiceConnection,实现onServerConnected(),并获取Service端Binder实例,通过binder实例进行service端其他公共方法的调用。
3.Client端通过bindService(...)方法将Service绑定到该Client上
4.Client在恰当的时候(比如在onDestory的时候),通过unBindService()解绑service.

另外,在Bound Service的具体使用过程中,根据onBind中返回的Binder对象的定义方式不同,又具体分为三种方式,每种方式又不同的特点,适应不同的场景。

2.1 继承Binder类

这是Bound Service最常用也是最简单的一种

局限:
Client和Service必须同属于一个进程,不能实现进程间通信(IPC)。否则会出现类似于 "android.os.BinderProxy cannot be cast to xxxx." 的错误

自定义Service:

public class BoundService1 extends Service {

    private static final String TAG = "BoundService1";

    /////// 自定义binder
    private MyBinder mBinder = new MyBinder();
    public class MyBinder extends Binder {
        BoundService1 getService() {
            return BoundService1.this;
        }
    }
    /////// 自定义binder

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        Log.w(TAG, "in onBind");
        return mBinder;
    }

    @Override
    public boolean onUnbind(Intent intent) {
        Log.w(TAG, "in onUnbind" );
        return super.onUnbind(intent);
    }

    @Override
    public void onCreate() {
        super.onCreate();
        Log.w(TAG, "in onCreate");
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.w(TAG, "in onDestroy");
    }
}

AndroidManifest.xml中注册:

<service android:name=".BoundService1"/>

Client中使用:

public class BActivity extends AppCompatActivity {

    private static final String TAG = "BActivity";

    private Button bindServiceBtn;
    private Button unBindServiceBtn;
    private Button startIntentService;

    private Intent serviceIntent;

    private ServiceConnection sc = new MyServiceConnection();
    private MyBinder mBinder;
    private BoundService1 mBindService;
    private boolean mBound;

    //////////////自定义ServiceConnection
    private class MyServiceConnection implements ServiceConnection {

        @Override
        public void onServiceConnected(ComponentName name, IBinder binder) {
            Log.w(TAG, "in MyServiceConnection onServiceConnected");
            mBinder = (MyBinder) binder;
            mBindService = mBinder.getService();

            mBound = true;
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            Log.w(TAG, "in MyServiceConnection onServiceDisconnected");
            mBound = false;
        }
    }
    //////////////自定义ServiceConnection

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_b);

        bindServiceBtn = findViewById(R.id.bind_service_btn);
        unBindServiceBtn = findViewById(R.id.unBind_service_btn);
        startIntentService = findViewById(R.id.start_intent_service_btn);

        bindServiceBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(BActivity.this, BoundService1.class);
                bindService(intent, sc, Context.BIND_AUTO_CREATE);
            }
        });

        unBindServiceBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                excuteUnBindService();
            }
        });

        startIntentService.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(BActivity.this, MyIntentService.class);
                startService(intent);
            }
        });
    }

    private void excuteUnBindService() {
        if (mBound) {
            unbindService(sc);
            mBound = false;
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        Log.w(TAG, "in onDestroy");
        excuteUnBindService();
    }
}

同样这里即使多次点击绑定,也是会绑定一次,BoundService1中的生命周期方法也只会执行一次绑定。

2.2 使用Messenger

通过Messenger方式返回Binder对象可以不用考虑Client-Service是否属于同一个进程的问题,并且,可以实现Client-Service之间的双向通信。

局限:
不支持严格意义上的多线程并发处理,实际上是以队列去处理

自定义Service:

public class BoundService2 extends Service {

    private static final String TAG = "BoundService2";
    public static final int MSG_FROM_CLIENT_TO_SERVICE = 1;
    public static final int MSG_FROM_SERVICE_TO_CLIENT = 2;

    private Messenger mClientMessenger;
    private Messenger mServiceMessenger = new Messenger(new ServiceHandler());

    class ServiceHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            Log.w(TAG, "thread name: " + Thread.currentThread().getName());
            switch (msg.what) {
                case MSG_FROM_CLIENT_TO_SERVICE:
                    Log.w(TAG, "receive msg from client");
                    mClientMessenger = msg.replyTo;

                    // service发送消息给client
                    Message toClientMsg = Message.obtain(null, MSG_FROM_SERVICE_TO_CLIENT);
                    try {
                        Log.w(TAG, "service begin send msg to client");
                        mClientMessenger.send(toClientMsg);
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                    break;

                default:
                    super.handleMessage(msg);
            }
        }
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        Log.w(TAG, "in onBind");
        return mServiceMessenger.getBinder();
    }

    @Override
    public boolean onUnbind(Intent intent) {
        Log.w(TAG, "in onUnbind");
        return super.onUnbind(intent);
    }

    @Override
    public void onDestroy() {
        Log.w(TAG, "in onDestroy");
        super.onDestroy();
    }
}

AndroidManifest.xml中注册:

<service android:name=".BoundService2"/>

Client中的使用:

public class CActivity extends AppCompatActivity {

    private static final String TAG = "CActivity";

    private Button bindServiceBtn;
    private Button unBindServiceBtn;
    private Button sendMsgToService;

    private ServiceConnection sc = new MyServiceConnection();
    private boolean mBound;
    private Messenger mServiceMessenger;
    private Handler mClientHandler = new MyClientHandler();
    private Messenger mClientMessenger = new Messenger(mClientHandler);

    /////////// Message handler && ServiceConnection
    private class MyClientHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            if (msg.what == BoundService2.MSG_FROM_SERVICE_TO_CLIENT) {
                Log.w(TAG, "receive msg from service.");
            }
        }
    }

    private class MyServiceConnection implements ServiceConnection {

        @Override
        public void onServiceConnected(ComponentName name, IBinder binder) {
            Log.w(TAG, "in MyServiceConnection onServiceConnected.");

            mServiceMessenger = new Messenger(binder);
            mBound = true;
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            Log.w(TAG, "in MyServiceConnection onServiceDisconnected.");
            mBound = false;
        }
    }
    /////////// Message handler && ServiceConnection

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_c);

        bindServiceBtn = findViewById(R.id.bind_service_btn);
        unBindServiceBtn = findViewById(R.id.unBind_service_btn);
        sendMsgToService = findViewById(R.id.send_msg_to_service);

        bindServiceBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(CActivity.this, BoundService2.class);
                bindService(intent, sc, Context.BIND_AUTO_CREATE);
            }
        });

        unBindServiceBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                excuteUnBindService();
            }
        });

        sendMsgToService.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                sayHello();
            }
        });
    }

    public void sayHello() {
        if (!mBound) {
            return;
        }

        Message msg = Message.obtain(null, BoundService2.MSG_FROM_CLIENT_TO_SERVICE,0,0);
        msg.replyTo = mClientMessenger;

        try {
            mServiceMessenger.send(msg);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    private void excuteUnBindService() {
        if (mBound) {
            unbindService(sc);
            mBound = false;
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        Log.w(TAG, "in onDestroy");
        excuteUnBindService();
    }
}

这里在ServiceConnection中的onServiceConnected(...)方法中根据service端传来的binder参数实例化一个Messenger对象,就可以使用它发送消息给Service了,
另外,在给service发送消息的时候,在消息中夹带一个Client端的Messenger实例:

Message msg = Message.obtain(null, BoundService2.MSG_FROM_CLIENT_TO_SERVICE,0,0);
msg.replyTo = mClientMessenger;

在service端获取到该Messenger实例,就可以在service端发送消息回Client端了。

3. AIDL (Android Interface Definition Language)

一般情况下,Messenger这种方式都是可以满足需求的,当然,通过自定义AIDL方式相对更加灵活。这种方式需要自己在项目中自定义xxx.aidl文件,然后系统会自动在gen目录下生成相应的接口类文件,接下来整个的流程与Messenger方式差别不大。

首先在服务端的src目录下按照下图中的样子创建


目录

在aidl.aidl下新建一个MyAIDLService.aidl文件,文件内容如下:

// MyAIDLService.aidl
package aidl.aidl;

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

interface MyAIDLService {

    // 这个AIDL service 对外的接口只有一个,就是获取字符串
    String getString();
}

注意这里的package后面的名称,与后面客户端的名称要一致,不然会出错。这个文件中我们只定义了一个对外的接口String getString()。这时候Build一下,编译器会自动在Build->generated->source->aidl->debug->aidl->aidl下生成对应的接口文件

接下来,在服务端创建一个MyService:

public class MyService extends Service {

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

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

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

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

    class MyBinder extends MyAIDLService.Stub {

        @Override
        public String getString() throws RemoteException {
            return "我是从服务器返回的";
        }
    }
}

这里只需要注意,onBind(...)中返回一个MyAIDLService.Stub的实例。

然后到AndroidManifest.xml中注册一下就可以了:

        <service android:name=".MyService">
            <intent-filter>
                <action android:name="com.server.aidl.service.MyService"/>
            </intent-filter>
        </service>

下面,来实现客户端的调用:
首先,将服务端的MyAIDLService.aidl文件拷贝过来,并要保持包名和服务端一致,如下图所示:


目录

接下来,直接调用就可以了:

public class MainActivity extends AppCompatActivity {

    private Button bindServiceBtn, unBindServiceBtn;
    private TextView tvData;
    private MyAIDLService mMyAIDLService;

    private ServiceConnection sc = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder binder) {
            // 这里获取Service的方式有点儿区别
            mMyAIDLService = MyAIDLService.Stub.asInterface(binder);
            try {
                String str = mMyAIDLService.getString();
                tvData.setText(str);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

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

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        bindServiceBtn = findViewById(R.id.bind_service);
        unBindServiceBtn = findViewById(R.id.unBind_service);
        tvData = findViewById(R.id.tv_from_server);

        bindServiceBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent();
                intent.setAction("com.server.aidl.service.MyService");
                // 从Android 5.0开始,隐式Intent绑定服务的方式已经不能使用了,这里需要设置Service所在服务端的包名
                intent.setPackage("com.server.aidl.service.serviceaidlserver");
                bindService(intent, sc, BIND_AUTO_CREATE);
            }
        });

        unBindServiceBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                unbindService(sc);
            }
        });
    }
}

这样,AIDL的方式也介绍完了。

最后, Bound Service还有很重要的一点和Start Service不同,就是解绑的时候一定要确认已经绑定的才能解绑,否则会崩溃,而Start Service不需要。

另外,四大组件中BroadcastReceiver不能作为Bound Service的Client,因为它的生命周期很短,而Bound Service的生命周期和Client的生命周期是绑定的。因此,在Android中,不允许BroadcastReceiver去bindService(...),当有此类需求时,可以用start service去代替。

需要补充的一点是:所有的Service都是运行在其所在进程的主线程上,如果Service和Client同属一个进程,则它就会运行在主线程,也就是UI线程上,此时要注意不要在Service中执行耗时任务,如果要执行耗时任务,就要在Service中新建线程,避免阻塞UI导致ANR;Service一旦创建,需要停止时都需要显示调用相应的方法(started service调用stopService()或Service自身调用stopSelf(),bound service需要unBindService()),否则started service将一直处于运行状态,对于bound service,则只有等到Client生命周期结束后才会销毁。




补充:

介绍一下IntentService

IntentService是系统提供给我们的一个继承自Service的特殊类,它的特殊性在于:

  1. 默认实现了onBind()方法,直接返回null
  2. 定义了抽象方法onHandleIntent(), 用户需要实现此方法,该方法用来处理耗时任务,并已经在新的线程中,用户无需再新建线程。
  3. 当耗时任务结束(onHandleIntent()方法执行完毕),此IntentService自动结束,无需人为调用方法使其结束
  4. IntentService处于任务时,也是按照队列的方式一个一个去处理,而非真正意义上的多线程并发方式.

简单定义:

public class MyIntentService extends IntentService {

    private static final String TAG = "MyIntentService";

    public MyIntentService() {
        super(TAG);
    }

    public MyIntentService(String name) {
        super(name);
    }

    @Override
    protected void onHandleIntent(@Nullable Intent intent) {
        Log.w(TAG, "in onHandleIntent");
        Log.w(TAG, "thread name: " + Thread.currentThread().getName());
    }
}

注册:

<service android:name=".MyIntentService"/>

使用:

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