当前位置: 代码迷 >> Android >> Android基础札记(十一)- Service基础和注意事项以及Activity与Service的通信
  详细解决方案

Android基础札记(十一)- Service基础和注意事项以及Activity与Service的通信

热度:18   发布时间:2016-04-28 02:01:19.0
Android基础笔记(十一)- Service基础和注意事项以及Activity与Service的通信

  • Service的基本概念
  • 为什么要有Service
  • Service的基本用法
  • 电话窃听器的小案例
  • Service和Activity通信
  • Service和Thread的关系

向光明而行!

Service的基本概念

Service是Android的四大组件之一,在每一个应用程序中都扮演者非常重要的角色。

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

既然都是被用于处理耗时的操作,那么我们什么时候使用Service,什么时候使用Thread呢?这就涉及到进程与线程之间的关系了,我们进入到下一节。

为什么要有Service

首先我们要知道,当一个应用开启后,并且没有任何其他的组件运行时,Android操作系统就给我们的应用开启了一个新的Linux进程并在里面创建了一个独立的线程。默认的,应用的所有组件都会运行在相同进程和线程(这个线程被称为”main”主线程)中运行。

进程创建后,就有了生命周期。Android操作系统会尽可能长时间的去维系一个应用进程保持运行。当然,如果为了创建一个新的或者更为重要的进程,Android操作系统也会移除旧的进程去释放内存。

根据进程中运行的组件和组件的状态,Andriod系统维护着一个”优先级关系“,来决定哪个进程被保持运行或者被杀死,最不重要的进程最先被杀死。

下面是优先级关系的列表(第一个进程最重要也是最后被杀死)

  1. 前台进程(Foreground process)
    简单来说,当用户和应用程序交互时就是前台进程,此时会调用Activity的onResume()方法。
  2. 可视进程
    当一个应用程序不可以和用户交互,但是仍然可以被用户看到时就是可是进程,此时会调用Activity的onPause()方法。
  3. 服务进程
    当使用startService()开启一个服务时,这个服务就处于服务进程中。
  4. 后台进程
    当应用的Activity对当前用户都不可见时就是后台进程,此时会调用Activity的onStop()方法。
  5. 空进程
    应用进程中没有任何活动的组件时,就是空进程。当应用中没有服务,并且一直按返回键直到回到桌面时,这个应用就编程了空进程了。

当我们的应用开启了子线程并执行逻辑时,被用户按返回键变成了空进程时。如果有更重要的应用被启动,那么空进程可能会被Android操作系统杀死,我们的子线程中的逻辑也就会被中断,这样逻辑就不安全了。这是这个原因,Android才为我们提供了更稳定的Service方法。

Service的基本用法

Service最基本的用法自然就是如何启动一个Service了,启动Service的方法和启动Activity很类似,都需要借助Intent来实现,下面我们就通过一个具体的例子来看一下。

首先要明确,创建一个简单服务的基本步骤:
①写一个类继承Service,并重写其中的onCreate()onStartCommand()onDestory()等生命周期方法。其中onBind()方法是服务和Activity通信使用的,后面在讲解。
②Service是四大组件之一,需要在清单文件中声明。
③使用startService(intent)启动服务和使用stopService(intent)停止服务。

接下来我们就创建一个简单的工程,画一个界面来操作一下。

首先,写一个类继承Service,并重写其中的onCreate()onStartCommand()onDestory()等生命周期方法,并在其中打印一些Log。代码如下:

public class MyService extends Service{    @Override    public IBinder onBind(Intent intent) {        return null;    }    @Override    public void onCreate() {        super.onCreate();        System.out.println("服务第一次被创建时调用");    }    @Override    public int onStartCommand(Intent intent, int flags, int startId) {        System.out.println("服务启动了");        return super.onStartCommand(intent, flags, startId);    }    @Override    public void onDestroy() {        super.onDestroy();        System.out.println("服务销毁了");    }}

其次,在清单文件中声明服务。

 <application        ... >        ...        <service android:name="com.bzh.service01.MyService" >        </service></application>

最后,做好以上这些操作后,我们就可以启动服务了。我们通过如下代码来启动和停止服务。

public void start(View v) {    // 准备好开启服务的意图    Intent service = new Intent(this, MyService.class);    // 开启服务    startService(service);}public void stop(View v) {    Intent service = new Intent(this, MyService.class);    // 停止服务    stopService(service);}

值得说明的是,当启动一个Service的时候,会调用该Service中的onCreate()onStartCommand()方法。当我们再次点击启动服务按钮时,就只有onStartCommand()方法执行了,onCreate()方法并没有执行。

之所以这样是由于onCreate()方法只会在Service第一次被创建的时候调用,如果当前Service已经被创建过了,不管怎样调用启动服务的方法,onCreate()方法都不会再执行。

而当我们点击停止服务是,onDesctory()方法会被调用。请看一下,调用的测试图。

当我们启动服务后,我们可以在后台查看正在运行的服务。

电话窃听器的小案例

接下来,再通过一个小案例巩固一下服务、TelephonyManager和录音设备的使用。

开发的基本步骤是:
①创建PhoneService类并继承Service;在AndroidManifest.xml文件中声明服务。
②获取到电话管理服务器(TelephonyManager),监听电话到来的状态;
③判断电话的三种状态(空闲、响铃、接通),并在相应的方法中获取录音设备(MediaRecorder)、配置参数、录制、释放资源;
④在应用启动时启动服务,退出时销毁服务。

第一步,创建PhoneService类并继承Service;在AndroidManifest.xml文件中声明服务。请看下面的简略代码;

public class PhoneService extends Service {    @Override    public IBinder onBind(Intent intent) {        return null;    }    @Override    public void onCreate() {        super.onCreate();        System.out.println("服务第一次被创建");    }    @Override    public int onStartCommand(Intent intent, int flags, int startId) {        System.out.println("服务启动");        return super.onStartCommand(intent, flags, startId);    }    @Override    public void onDestroy() {        super.onDestroy();        System.out.println("服务销毁");    }}

在清单文件中声明服务:

<service android:name="com.bzh.phonelistener.PhoneService" ></service>

第二步,获取到电话管理服务器(TelephonyManager),监听电话到来的状态;
我们在PhoneService类的onCreate()方法中,拿到电话管理者的实例,并监听电话状态的改变。

@Overridepublic void onCreate() {    super.onCreate();    System.out.println("服务第一次被创建");    // 拿到电话服务的管理者实例    TelephonyManager telephonyManager = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);    // 去监听电话状态的改变    telephonyManager.listen(new MyPhoneListener(), PhoneStateListener.LISTEN_CALL_STATE);}

值得注意的是,我们去监听电话状态影响到了用户的隐私,所以需要添加权限。

<!-- 监听电话状态所需的权限 --><uses-permission android:name="android.permission.READ_PHONE_STATE" />

第三步,判断电话的三种状态(空闲、响铃、接通),并在相应的方法中获取录音设备(MediaRecorder)、配置参数、录制、释放资源;

在上一步中TelephonyManager对象中listen(PhoneStateListener listener, int events)方法,可以注册一个监听对象去接收指定电话设备状态的通知,那么我们下面的重点就在这个监听对象上了。

我们创建一个内容类,继承PhoneStateListener,并重写onCallStateChanged(int state, String incomingNumber)方法,接收改变的事件。事件分为三种:空闲(TelephonyManager.CALL_STATE_IDLE)、响铃(TelephonyManager.CALL_STATE_RINGING)、接通(TelephonyManager.CALL_STATE_OFFHOOK)。

另外,当电话到来时我们需要用到MediaRecorder类去记录音频数据。分别在空闲、响铃、接通状态中做初始化、配置、准备录音、结束录音等操作。请看代码。

private class MyPhoneListener extends PhoneStateListener {    private MediaRecorder recorder;    @Override    public void onCallStateChanged(int state, String incomingNumber) {        super.onCallStateChanged(state, incomingNumber);        switch (state) {        case TelephonyManager.CALL_STATE_IDLE: // 电话处于空闲状态            System.out.println("电话处于空闲状态");            if (recorder != null) {                // 停止录音并释放资源                recorder.stop();                recorder.reset();                recorder.release();                System.out.println("录音成功");            }            break;        case TelephonyManager.CALL_STATE_RINGING:// 电话处于响铃状态            System.out.println("电话处于响铃状态");            // 拿到录音机            recorder = new MediaRecorder();            // 设置录音的声音来源;MIC是麦克风;VOICE_CALL是双方通话的声音,但是好多手机不支持;            recorder.setAudioSource(MediaRecorder.AudioSource.MIC);            // 音频输出格式为3GP            recorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);            // 音频编码格式            recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);            // 音频的输出目录            recorder.setOutputFile("/mnt/sdcard/" + UUID.randomUUID().toString() + ".3gp");            try {                recorder.prepare();            } catch (Exception e) {                e.printStackTrace();            }            break;        case TelephonyManager.CALL_STATE_OFFHOOK:// 电话接通            System.out.println("电话接通");            // 开始录制            recorder.start(); // Recording is now started            System.out.println("开始录制");            break;        default:            break;        }    }}

此时,当我们执行,会包一个异常,是由于未加权限引起的。

下面是录音所加的权限。

 <!-- 录音和保存录音所需的权限 --><uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /><uses-permission android:name="android.permission.RECORD_AUDIO" />

最后一步,在应用启动时启动服务,退出时销毁服务。这里的代码就更简单了。

@Overrideprotected void onCreate(Bundle savedInstanceState) {    super.onCreate(savedInstanceState);    setContentView(R.layout.activity_main);    // 开启窃听服务    Intent service = new Intent(this, PhoneService.class);    startService(service);}@Overrideprotected void onDestroy() {    super.onDestroy();    Intent service = new Intent(this, PhoneService.class);    // 销毁服务    stopService(service);}

至此,整个小案例就结束了。通过这个案例,练习了服务的基本使用方法,和电话管理者对电话状态的监听,以及如何录音。

Service和Activity通信

上面我们学习了Service的基本用法,启动Service之后,就可以在onCreate()onStartCommand()方法里去执行一些具体的逻辑了。

不过这样的话Service和Activity的关系并不大,只是Activity通知了Service一下:“你可以启动了。”然后Service就去忙自己的事情了。那么有没有什么办法能让它们俩的关联更多一些呢?比如说在Activity中可以调用Service中的方法。

当然可以,只需要让Activity和Service建立关联就好了,这其中就用到了ContextWrapper.bindService()方法和IBinder接口了。

通过查看Api可以发现IBinder接口描述了抽象的协议如何与远程对象进行交互的。而Google又为我们提供了默认的实现Binder类。帮助我们在Activity和Service之间进行通信。

我们再来看一下ContextWrapper.bindService()的API注释Connect to an application service, creating it if needed.,简单来说就是,通过这个方法去连接到Service,如果Service还没有创建,并且需要去创建,那么就创建它。

此外,有了绑定服务的方法,那么也会有解除绑定的unbindService()方法。

这样说来说去可能大家都晕了,还是通过一个工程来讲解吧。

创建一个工程,界面如下,每个Button都对应着自己的方法。
界面布局非常简单,这里为了节省篇幅就不贴了。

那么我们先看一下MainActivity里面都做了些什么吧。

public class MainActivity extends Activity {    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        // 实例化Activity与Service的连接对象        conn = new MyServiceConnection();    }    ServiceConnection conn;    // 启动服务    public void start(View v) {        Intent service = new Intent(this, MyService.class);        startService(service);    }    // 绑定服务    public void bind(View v) {        Intent service = new Intent(this, MyService.class);        // 参数1:Activity要绑定的Service        // 参数2:Activity与Service连接所需的连接对象        // 参数3:连接时,如果服务还没有创建就自动创建        bindService(service, conn, Context.BIND_AUTO_CREATE);    }    // 解绑服务    public void unbind(View v) {        // 解除连接        unbindService(conn);    }    // 停止服务    public void stop(View v) {        Intent service = new Intent(this, MyService.class);        stopService(service);    }    // 监视服务的状态    private class MyServiceConnection implements ServiceConnection {        @Override        public void onServiceConnected(ComponentName name, IBinder service) {            System.out.println("MainActivity和MyService已经连接成功");            MyBinder myBinder = (MyBinder) service;            myBinder.callMethod1();            myBinder.callMethod2();        }        @Override        public void onServiceDisconnected(ComponentName name) {        }    }}

除了Binder,Activity与Service建立连接还需要一个实现ServiceConnection接口的对象。

可以看到,这里我们创建了一个实现ServiecConnection接口的内部类,在里面重写了onServiceConnected()方法和onServiceDisconnected()方法,这两个方法分别会在Activity与Service建立关联和解除关联的时候调用。

onServiceConnected()方法中,我们又通过向下转型得到了MyBinder的实例,有了这个实例,Activity和Service之间的关系就变得非常紧密了。

现在我们可以在Activity中根据具体的场景,并借助MyBinder中的任何public方法去调用服务中的任何方法了,即实现了Activity指挥Service干什么Service就去干什么的功能。

再看一下MyService中的方法。

public class MyService extends Service {    @Override    public IBinder onBind(Intent intent) {        // 返回自定的Binder,帮助Activity与Service之间的通信        return new MyBinder();    }    @Override    public void onCreate() {        super.onCreate();        System.out.println("onCreate()");    }    @Override    public int onStartCommand(Intent intent, int flags, int startId) {        System.out.println("onStartCommand()");        return super.onStartCommand(intent, flags, startId);    }    @Override    public void onDestroy() {        System.out.println("onDestroy()");        super.onDestroy();    }    public void method1() {        System.out.println("我是服务中的方法1");    }    public void method2() {        System.out.println("我是服务中的方法2");    }    class MyBinder extends Binder {        public void callMethod1() {            method1();        }        public void callMethod2() {            method2();        }    }}

MyService中的逻辑也比较简单,其中Activity与Service建立关联的核心方法就是onBind()方法,在其中我们返回了继承了Binder类MyBinder的实例。这样在MainActivity的MyServiceConnection内部类中拿到IBinder并向下转型后,就可以随意的操纵服务中的方法了。

另外非常值得注意的一点
bindService(service, conn, Context.BIND_AUTO_CREATE);中可以看到,当去连接时,如果服务还没有创建,便会通过Context.BIND_AUTO_CREATE标识位来自动帮助我们创建服务。

但是这样你会发现startService()bindService()unbindService()stopService()它们之间如果乱序的调用,会非常容易出现异常。

而Google也给出了建议的调用顺序,我们只需要遵守就可以了:

  1. 如果需要长期运行的服务,那么就使用startService()来开启。
  2. 如果需要Activity与Service进行交互,那么先使用startService()来开启服务,再使用bindService()方法绑定服务、建立连接。
  3. 如果需要终止Activity与Service之间的交互,那么调用unbindService()方法解除它们之间的绑定和连接。
  4. 如果需要之中服务,那么首先看是否有Activity与Service建立了连接,如果有就先调用unbindService再去调用stopService()去停止服务。如果没有,那么直接调用stopService()方法去停止服务。

Service和Thread的关系

这里再说下Service和Thread的关系,如果不说清楚,可能会弄混。

Service和Thread到底有什么关系呢?什么时候应该用Service,什么时候又应该用Thread?答案可能会有点让你吃惊,因为Service和Thread之间没有任何关系!

之所以有不少人会把它们联系起来,主要就是因为Service的后台概念。Thread我们大家都知道,是用于开启一个子线程,在这里去执行一些耗时操作就不会阻塞主线程的运行。

而Service我们最初理解的时候,总会觉得它是用来处理一些后台任务的,一些比较耗时的操作也可以放在这里运行,这就会让人产生混淆了。但是,如果我告诉你Service其实是运行在主线程里的,你还会觉得它和Thread有什么关系吗?

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

Log.i("MyService", "MainActivity thread id is " + Thread.currentThread().getId());

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

Log.i("MyService", "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() {              // 开始执行后台任务          }      }).start();      return super.onStartCommand(intent, flags, startId);  }  class MyBinder extends Binder {      public void startDownload() {          new Thread(new Runnable() {              @Override              public void run() {                  // 执行具体的下载任务              }          }).start();      }  }  

  相关解决方案