探究Android四大组件之Service

1.前言

写这篇博客时候碰到一件极其苦逼的事情,花了两天时间写完了,结果没有保存直接关闭了,内心久久不能平静,终究还是平静了,只能静下来再写一遍。
  • 本篇文章会从最基础入手,探究在后台默默的劳动者-Service.

  • Service作为Android四大组件之一,主要用于在后台处理一些耗时的逻辑,或者去执行某些需要长期运行的任务。必要的时候我们甚至可以在程序退出的情况下,让Service在后台继续保持运行状态。

2.Service的基本用法

  • 第一步:我们要继承service类,实现自己的service。

  • 第二步:在androidManifest.xml中进行注册,如:

  • 第三步:在activity中进行启动、绑定、解绑或者停止service。

2.1.基本用法:定义一个Service

public class MyService extends Service {

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }
    /**
     * 服务创建时候调用
     */
    @Override
    public void onCreate() {
        super.onCreate();
        Logger.d("onCreated executed!");
    }

    /**
     * 服务每次启动时候都会调用
     * @param intent
     * @param flags
     * @param startId
     * @return
     */
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Logger.d("onStartCommand executed!");
        return super.onStartCommand(intent, flags, startId);
    }
   /**
     * 服务销毁时候调用
     */
    @Override
    public void onDestroy() {
        super.onDestroy();
        Logger.d("onDestroy executed!");
    }
}
  • 会发现我们必须重写onBind()方法。这个方法很重要,后面介绍。这里先忽略。
    我们另外再复写三个方法:onCreate()、onStartCommand()、onDestroy()
    这是Service中最常用的三个方法。

2.2.如何启动和停止Service

在Activity的布局文件中添加2个按钮,分别作为start和stop服务的开关。

  <Button
        android:layout_marginTop="10dp"
        android:id="@+id/start_service"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Start Service" />

   <Button
       android:id="@+id/stop_service"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:text="Stop Service" />

在Activity中

   startService.setOnClickListener(this);
   stopService.setOnClickListener(this);

     @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.start_service:
                //启动服务
                startService(new Intent(ServiceUseActivity.this, MyService.class));
                break;
            case R.id.stop_service:
                //停止服务
                stopService(new Intent(ServiceUseActivity.this, MyService.class));
                break;
            default:
                break;
        }
    }

启动服务:点击startService按钮观察

这里写图片描述

发现这里调用了onCreate()和onStartCommand()两个方法。说明服务已经启动成功了。你也可以在settings-developer options-running services中找到它。

停止服务:点击一下stopService按钮观察:

这里写图片描述

MyService确实已经停止下来了。(另外:如果没有stopService按钮怎么停止服务?其实在Myservice任何位置调用stopSelf()就能让服务停止下来了)

那么你心里有没有一点疑惑,onCreate()和onStartCommand()两个方法有什么区别,因为刚才点击启动服务的时候,两个方法都执行了。

其实onCreate()是在服务第一次创建的时候调用,而onStartCommand()在每次启动服务的时候都会调用。

由于我们第一次点击startService的时候,服务还未创建,所以两个方法都会执行。之后如果你重复多点几次startService,你会发现只有onStartCommand()可以得到执行了。

  • 对了,我的Log打印使用的是GIthub上一个叫Logger的开源项目实现的,非常好用。
    传送门: —— [ Logger ]

2.3.Activity和Service间进行通信

  • 你有没有发现,虽然我们再Activity里面启动了Service,但在启动之后,Activity和Service基本就没什么关系了。这就好比如:Activity告诉Service,“你可以启动了”。然后Service启动了,忙自己的事去了,但Activity并不知道Service去干什么了,以及完成得怎么样。

  • 那么我们前面忽略的onBind()方法可以上场了。

现在我们模拟在MyService中提供一个下载功能,然后在Activity中决定何时开始下载,以及随时查看下载进度。修改MyService中的代码如下:

public class MyService extends Service {

    private DownLoadBinder mBinder = new DownLoadBinder();

    @Override
    public void onCreate() {
        super.onCreate();
        Logger.d("onCreated executed!");
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Logger.d("onStartCommand executed!");
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Logger.d("onDestroy executed!");
    }

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

    class DownLoadBinder extends Binder {
        public void startDownLoad() {
            Logger.d("startDownLoad executed!");
        }

        public int getProgress() {
            Logger.d("getProgress executed!");
            return 0;
        }
    }
}

可以看到,我们新建了一个DownLoadBinder 类,并让它集成自Binder。然后再它的内部实现了开始下载以及提供下载进度的方法,当然,只是两个模拟方法,并没有真正实现功能。

然后,在MyService中创建DownLoadBinder 的实例,并且在onBind()方法中返回了这个实例。这样MyService的工作就完成了。这个mBinder,你可以把它理解为代理人(或者说代理对象,它可以完成MyService中的一些操作,也就是说,外界拿到了Binder对象,就实现了跟Service的通信了)

然后,我们再Activity的布局文件中添加2个按钮,分别作为绑定服务和解绑服务的(没错,这是第二种启动服务的方法,后面再讲区别)。

    <Button
        android:id="@+id/bind_service"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Bind Service" />

    <Button
        android:id="@+id/unbind_service"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Unbind Service" />
  • 那么你可能会问,谁去和Service绑定啊?当然就是Activity了。当Activity和Service绑定后,就可以去调用Service里的Binder提供的方法了。这样就实现了通信。话不多说,先看Activity代码。
   @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_service_use);
        startService.setOnClickListener(this);
        stopService.setOnClickListener(this);
        bindService.setOnClickListener(this);
        unbindService.setOnClickListener(this);
    }

 private MyService.DownLoadBinder downLoadBinder;

 private ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            downLoadBinder = (MyService.DownLoadBinder) service;
            downLoadBinder.startDownLoad();
            downLoadBinder.getProgress();
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

  @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.start_service:
                startService(new Intent(ServiceUseActivity.this, MyService.class));
                break;

            case R.id.stop_service:
                stopService(new Intent(ServiceUseActivity.this, MyService.class));
                break;

            case R.id.bind_service:
                Intent bindIntent = new Intent(ServiceUseActivity.this, MyService.class);
                bindService(bindIntent, connection, BIND_AUTO_CREATE);
                break;

            case R.id.unbind_service:
                unbindService(connection);
                break;
            default:
                break;
        }
    }

首先创建了ServiceConnection 的匿名类,重写了2 个方法。其中:

  • onServiceConnected: Activity与Service成功绑定的时候调用
    在这个方法里面,通过强转把IBinder 转化为MyService.DownLoadBinder
    拿到这个代理对象的实例,Activity和Service之间的关系就联系很紧密了。现在我们可以根据场景调用MyService.DownLoadBinder中的任何public方法(我们调用2个模拟下载的方法)。即实现了指挥Service干什么Service就干什么的功能。

  • onServiceDisconnected: Activity与Service成功解绑的时候调用

  • 当然,现在还没绑定呢。当点击bindService按钮的时候, 执行bindService(bindIntent, connection, BIND_AUTO_CREATE);第二个参数就是我们创建的ServiceConnection 实例BIND_AUTO_CREATE表示Activity和Service绑定后自动创建Service。现在点击bindService按钮观察Log:
    这里写图片描述

startDownLoad()和getProgress()都得到了执行,说明确实在Activity里面调用了Service的方法了。

  • 需要注意的是:任何一个Service,在整个应用程序里面都是通用的,也就是说MyService可以和任何一个Activity进行绑定,绑定后其他的Activity也能获取到DownLoadBinder 的实例。

3.startService和bindService的区别

1、生命周期上的区别

执行startService时,Service会经历生命周期:onCreate->onStartCommand。当执行stopService时,Service经历生命周期:->onDestroy方法。调用者如果没有stopService,Service会一直在后台运行,下次调用者再起来仍然可以stopService。

执行bindService时,Service会经历生命周期:onCreate->onBind。这个时候调用者和Service绑定在一起。调用者调用unbindService方法或者调用者Context不存在了(如Activity被finish了),Service就会经历生命周期:onUnbind->onDestroy。这里所谓的绑定在一起就是说两者共存亡了。

多次调用startService,该Service只能被创建一次,即该Service的生命周期:onCreate方法只会被调用一次。但是每次调用startService,onStartCommand方法都会被调用。

多次调用bindService:分两种:
一个App里,同一个Activity多次bind一个服务,只有第一次会调用onBind、onServiceConnected。
一个App里,不同的Activity去bind一个服务,第一次会调用onBind、onServiceConnected,后面的bind只会调用onServiceConnected。

注意:若多次调用unBindService,第一次会解绑,后面会报错。

这里写图片描述

这个看源码就可以知道原因了。由于之前不小心关掉了,没有保存,源码分析篇幅较长,这里直接写大致的结果了。

  1. 绑定服务,首先要做的事情就是先用一个Map记录当前绑定服务所需的一些信息。 然后启动服务。
  2. 解绑服务,先从早前的Map集合中移除记录,然后停止服务。
  3. 如果再次解绑,无非就是再到这个map集合中找找有没有这条记录,没有就抛出服务没有注册的异常,也就是早前根本没有注册过任何服务。

注意:一个Activity bind(注意:不是startService方式)上一个Service后,如果Activity finish前没有调用unBind,App会崩溃,可以考虑在onDestroy中调用unbindService。上面我没有这么写,因为我们并不是在Activity的onCreate中绑定服务,而是在按钮的点击事件中,所以如果没有点击按钮绑定服务,直接点击解除绑定,就会出现上图的错误。

2、既使用startService又使用bindService的情况

如果一个Service又被启动又被绑定,则该Service会一直在后台运行。首先不管如何调用,onCreate始终只会调用一次。对应startService调用多少次,Service的onStart方法便会调用多少次。
Service的终止,需要unbindService和stopService同时调用才行。不管startService与bindService的调用顺序,如果先调用unbindService,此时服务不会自动终止,再调用stopService之后,服务才会终止;如果先调用stopService,此时服务也不会终止,而再调用unbindService或者之前调用bindService的Context不存在了(如Activity被finish的时候)之后,服务才会自动停止。

那么,什么情况下既使用startService,又使用bindService呢?

如果你只是想要启动一个后台服务长期进行某项任务,那么使用startService便可以了。如果你还想要与正在运行的Service取得联系,那么有两种方法:一种是使用broadcast,另一种是使用bindService。前者的缺点是如果交流较为频繁,容易造成性能上的问题,而后者则没有这些问题。因此,这种情况就需要startService和bindService一起使用了。

4.使用前台Service

Service几乎都是在后台运行的,一直以来它都是默默地做着辛苦的工作。但是Service的系统优先级还是比较低的,当系统出现内存不足情况时,就有可能会回收掉正在后台运行的Service。如果你希望Service可以一直保持运行状态,而不会由于系统内存不足的原因导致被回收,就可以考虑使用前台Service。

前台Service和普通Service最大的区别就在于,它会一直有一个正在运行的图标在系统的状态栏显示,下拉状态栏后可以看到更加详细的信息,非常类似于通知的效果。当然有时候你也可能不仅仅是为了防止Service被回收才使用前台Service,有些项目由于特殊的需求会要求必须使用前台Service,比如说墨迹天气,它的Service在后台更新天气数据的同时,还会在系统状态栏一直显示当前天气的信息,如下图所示:


这里写图片描述

如何才能创建一个前台服务呢?其实很简单。

这里写图片描述

这里只是修改了MyService中onCreate()方法的代码。可以看到,我们首先创建了一个Notification对象,只不过这里并没有使用NotificationManager来将通知显示出来。而是调用startForeground()方法就可以让MyService变成一个前台Service,并会将通知的图片显示出来。

现在重新运行一下程序,并点击Start Service或Bind Service按钮,MyService就会以前台Service的模式启动了,并且在系统状态栏会弹出一个通栏图标,下拉状态栏后可以看到通知的详细内容,如下图所示。

这里写图片描述

这样,一个前台服务就搞定了,是不是特别简单。

5.Service和Thread的区别

我刚接触serviced的时候曾有这样的疑惑,Service和Thread到底有什么关系呢?什么时候应该用Service,什么时候又应该用Thread?

答案可能会有点让你吃惊,因为Service和Thread之间没有任何关系!

之所以有不少人会把它们联系起来,主要就是因为Service的后台概念。Thread我们大家都知道,是用于开启一个子线程,在这里去执行一些耗时操作就不会阻塞主线程的运行。而Service我们最初理解的时候,总会觉得它是用来处理一些后台任务的,一些比较耗时的操作也可以放在这里运行,这就会让人产生混淆了。但是,如果我告诉你Service其实是运行在主线程里的,你还会觉得它和Thread有什么关系吗?让我们看一下这个残酷的事实吧。

在Activity的onCreate()方法里加入一行打印当前线程id的语句:

 Logger.d("Activity thread id is"+Thread.currentThread().getId());

然后在MyService的onCreate()方法里也加入一行打印当前线程id的语句:

 Logger.d("MyService thread id is " + Thread.currentThread().getId());

运行:

这里写图片描述

可以看到,它们的线程id完全是一样的,由此证实了Service确实是运行在主线程里的,也就是说如果你在Service里编写了非常耗时的代码,程序必定会出现ANR的。

你可能会惊呼,这不是坑爹么!?那我要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就可以写成:

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

class MyBinder extends Binder {  

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

6.使用IntentService

上面的写法中,可以看到一个方法为stopSelf()。因为onStartCommand方法中的,肯定是通过startService方式启动的服务。那么要关闭服务就要在外界(Activity中)调用stopService(),或者Service调用自身的stopSelf()方法了。这样Service在执行完毕后才会自动停止。

那么,经常有程序员忘记开启线程,或者忘记调用stopself()。为了可以简单地创建一个异步的、会自动停止的服务,Android特地提供了一个IntentService类,这个类就能很好的解决前面的两种尴尬。

新建一个MyIntentService类,继承自IntentService。代码如下:

这里写图片描述

首先,提供一个无参构造,然后实现onHandleIntent这个抽象方法,这里面去处理一些逻辑,不用担心ANR,因为这个方法就是在子线程的。打印线程id证明,另外,根据IntentService的特性,服务在运行结束后应该是会自动停止的,所以复写了onDestroy方法,打印下日志。

修改布局文件,添加一个按钮,启动MyIntentService。


这里写图片描述

修改Activity中代码:
不要忘记添加setOnclickListener()。

这里写图片描述

运行结果

这里写图片描述
这里写图片描述

证明了确实不在主线程,而且onDestroy得到了执行,说明MyIntentService运行完毕收自动停止了。

集开启线程和自动停止于一身,IntentService还是博得了很多程序员的喜爱。

7.Service高级进阶

前面我们已经学习了:Service的基本用法、Service和Activity进行通信、Service的销毁方式、Service与Thread的关系、以及如何创建前台Service、如何使用IntentService。

以上所提到的这些知识点,基本上涵盖了大部分日常开发工作当中可能使用到的Service技术。不过关于Service其实还有一个更加高端的使用技巧没有介绍,即远程Service的用法。

使用远程Service甚至可以实现Android跨进程通信的功能,下面就让我们具体地学习一下。

关于远程Service里面有很多的坑,网上很多资料也是以前用Eclipse做的,还有android5.0之后不能用隐式intent启动远程Service的问题等等。现在我们都是用Android studio做开发了,所以下面的内容我们都基于Android studio。我会尽量写得详细一些。

首先,将一个普通的Service转换成远程Service其实非常简单,只需要在注册Service的时候将它的android:process属性指定成:remote就可以了,如下:注册一个RemoteService

 <service
            android:name=".serviceuse.RemoteService"
            android:process=":remote">
 </service>

但是你可以尝试,以bindService方式启动这个服务,会发现程序直接崩溃掉了。这是由于在Bind Service按钮的点击事件里面我们会让Activity和RemoteService建立关联,但是现在这个RemoteService已经是一个远程Service了,Activity和Service运行在两个不同的进程当中,这时就不能再使用传统的建立关联的方式,程序也就崩溃了。

那么如何才能让Activity与一个远程Service建立关联呢?

这就要使用AIDL来进行跨进程通信了(IPC)。

AIDL(Android Interface Definition Language)是Android接口定义语言的意思,它可以用于让某个Service与多个应用程序组件之间进行跨进程通信,从而可以实现多个应用程序共享同一个Service的功能。

在Android studio 实现远程服务(Service)AIDL的方法与在eclipse中实现有些不同,Android studio中自带了AIDL创建的方式,而eclipse中需要手动创建;

下面详解实现远程服务(Service)AIDL的方法:

首先要说一下,由于是跨进程通信,我们最起码需要2个应用来实现了。


这里写图片描述

这是前面我们一直在在使用的项目,project : CoolDemos,module : app(也就是说我们的第一个应用还是使用这个module,在这个module里面创建一个远程Service;然后待会我们再创建一个module,在新建的module里面调用这个module里面的Service,说得非常详细了吧)。

具体步骤:
1. 创建第一个应用(我们的第一个应用已经创建好了,module名为app)
2. 创建 RemoteService 类,继承自Service。(remote即远程的意思。如图,已完成)
3. 创建AIDL文件, PublicBusiness 接口类。

首先,你先观察下,项目切换在project视图下,创建AIDL文件之前在目录 build–>generated–>source–>aidl–>androidTest–>debug下面发现还没有任何文件,如图(我这里是因为已经创建好了,里面有东西的。)


这里写图片描述

现在创建MyAIDLService,如图:

这里写图片描述

创建后,看main目录下出现aidl文件夹和MyAIDLService.aidl文件和包,将MyAIDLService.aidl文件中的basicTypes方法删除(这里我已经删掉了)

这里写图片描述

4-在这个MyAIDLService接口类中写入提供远程调用的方法(如图,这里我还是写的下载和查看下载进度两个方法),再确保MyAIDLService.aidl所在的包名与项目中默认的包名一致,如果不一致,则改aidl的包名,改成一致(我们默认就是一致的),如果一致,点击 Build–>Make Project(就是图中绿色的按钮),生成相应的Java文件(build->debug目录下)。再点击生成,生成效果如图。

这里写图片描述

第一部分完成,我们说过要新建一个module,在新建module里面使用我们当前module里面的Service。那么现在,我们新建一个module,取名为:useaidldemo。

这里写图片描述

将 app这个module中main目录下的aidl文件夹全部复制到 useaidldemo工程 的main目录下即可;

这里写图片描述

Ok,配置都完成了,并没有多复杂。

下面上代码:

首先,在app(提供远程Service的module)里面,配置下远程Service。

这里写图片描述

android:process=”:remote”——配置为远程服务
android:exported=”true”———表明别的app能访问
android:name=”com.example.lmy.cooldemos.serviceuse.RemoteService” 指定我们的远程Service的全路径

配置intent-filter,表明RemoteService可以响应带有这个action的Intent。这个action是由自己定义的。

我们可以把Action的名字设置成RemoteService的全路径名字。在这种情况下android:exported默认为true。

然后我们看看远程Service中的代码:

public class RemoteService extends Service {

    private MyBinder myBinder=new MyBinder();

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        Logger.d("onBind executed!");
        return myBinder;
    }

    @Override
    public boolean onUnbind(Intent intent) {
        Logger.d("onUnbind executed!");
        return super.onUnbind(intent);
    }

    @Override
    public void onCreate() {
        super.onCreate();
        Logger.d("onCreate executed!");
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Logger.d("onStartCommand executed!");
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Logger.d("onDestroy executed!");
    }

    //注意继承的是远程服务MyAIDLService.Stub,不是Binder
    class MyBinder extends MyAIDLService.Stub{

        @Override
        public void startDownLoad() throws RemoteException {
            Logger.d("startDownLoad executed!");
            //这里还能调用服务中的其他方法
            banKa();
        }

        @Override
        public int getProgress() throws RemoteException {
            Logger.d("getProgress executed!");
            return 0;
        }
    }

     private void banKa() {
        Logger.d("办卡不?");
    }
}

注意:这里我们就要用到aidl了。我们创建的MyBinder 继承自MyAIDLService.Stub,而不是Binder。然后创建了一个MyBinder 实例,在onBind()方法中将MyBinder 的实例返回。

那么你会问了,这个MyBinder继承自Stub,这里为什么可以这样写呢?因为Stub其实就是Binder的子类,所以在onBind()方法中可以直接返回Stub的实现。

另外我们依旧在MyBinder中写入了模拟下载和观察下载进度的方法,后面会在另一个应用里面启动这个应用中的Service,并调用这里面的方法。

然后两个module的main目录下的MyAIDLService .aidl 接口类,一致写为如下:关联上RemoteService中的代理对象中的方法。

interface MyAIDLService {
    void startDownLoad();
    int getProgress();
}

最后一步,在我们的第二个应用里面,调用这个应用里面的RemoteService.

第二个应用(useaidldemo)中MainActivity的布局如图(咱们不管界面美不美观,嘿嘿):
这里写图片描述

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    @butterknife.InjectView(R.id.startRemoteService)
    Button startRemoteService;
    @butterknife.InjectView(R.id.stopRemoteService)
    Button stopRemoteService;
    @butterknife.InjectView(R.id.bindRemoteService)
    Button bindRemoteService;
    @butterknife.InjectView(R.id.unbindRemoteService)
    Button unbindRemoteService;
    @butterknife.InjectView(R.id.useRemoteMethod)
    Button useRemoteMethod;

    //远程Service所在包的全路径
    private static final String PACKAGE_NAME = "com.example.lmy.cooldemos";
    //远程Setvice的类名全路径
    private static final String CLASS_NAME = "com.example.lmy.cooldemos.serviceuse.RemoteService";


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        butterknife.ButterKnife.inject(this);
        startRemoteService.setOnClickListener(this);
        stopRemoteService.setOnClickListener(this);
        bindRemoteService.setOnClickListener(this);
        unbindRemoteService.setOnClickListener(this);
        useRemoteMethod.setOnClickListener(this);
    }

    //我们定义的AIDL
    MyAIDLService myAIDLService;

    //实现ServiceConnection
    private ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            //使用了MyAIDLService.Stub.asInterface()方法将传入的IBinder对象传换成了MyAIDLService对象,
            //接下来就可以调用在MyAIDLService.aidl文件中定义的所有接口了。
            myAIDLService = MyAIDLService.Stub.asInterface(service);
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

    @Override
    public void onClick(View view) {
        Intent intent ;
        switch (view.getId()) {
            //启动远程服务
            case R.id.startRemoteService:
                intent = new Intent();
                intent.setComponent(new ComponentName(PACKAGE_NAME, CLASS_NAME));
                startService(intent);
                break;

            //停止远程服务
            case R.id.stopRemoteService:
                intent = new Intent();
                intent.setComponent(new ComponentName(PACKAGE_NAME, CLASS_NAME));
                stopService(intent);
                break;

            //绑定远程服务
            case R.id.bindRemoteService:
                intent = new Intent();
                intent.setComponent(new ComponentName(PACKAGE_NAME, CLASS_NAME));
                bindService(intent, connection, BIND_AUTO_CREATE);
                break;

            //解绑远程服务
            case R.id.unbindRemoteService:
                unbindService(connection);
                break;

            //调用远程服务的方法
            case R.id.useRemoteMethod:
                try {
                    myAIDLService.startDownLoad();
                    myAIDLService.getProgress();
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            default:
                break;
        }
    }
}

注意:这里调用远程服务不能这么写:

//Intent的action指定成远程Service的类名的全路径。
 Intent intent = new Intent(CLASS_NAME );  
 startService(intent);  
 ```
 因为android5.0后不让用隐式意图启动服务了。所以这么是行不通的。

我们是这么实现的:
```python
Intent intent = new Intent();
intent.setComponent(new ComponentName(PACKAGE_NAME ,CLASS_NAME ));
startService(intent);

其中,ComponentName()中传入的参数是一个是Service所在的包的名字,还有一个参数就是Service的类的名字。

然后测试吧,注意:要把2个module都安装到手机上哦。然后在打开我们的第二个module(useaidldemo),运行:

点击启动远程Service:

这里写图片描述

调用了我们的RemoteService中的onCreate和onStartCommand方法,说明我们确实在一个应用中开启了另一个应用的Service。
另外注意看:这里的包名后面带上了:remote标识。

点击停止远程Service,调用了RemoteService的onDestroy方法。跟我们调用自己应用中的Service的生命周期是一样的。
这里写图片描述

点击绑定远程Service:

这里写图片描述

点击解绑远程Service:

这里写图片描述

同样的,生命周期走得准确无误。

最后点击调用远程Service中的方法:

这里写图片描述

确实调用了RemoteService中的两个public方法,并且在其中还调用了RemoteService中的其它方法,实现了跨进程通信。

另外:不同的进程之间传递数据,Android对这类数据的格式支持是非常有限的,基本上只能传递Java的基本数据类型、字符串、List或Map等。

那么如果我想传递一个自定义的类该怎么办呢?

这就必须要让这个类去实现Parcelable接口,并且要给这个类也定义一个同名的AIDL文件。这些如果你感兴趣,可以自己去研究一下。

以上就是我要跟大家一起探究的关于Android Service的所有东西,其实还准备写一个实际项目中使用Service的案例,但碍于篇幅过长,而且之前博客忘了保存,导致重写浪费了很多时间,后面希望能坚持出新的文章。
  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值