Android四大组件之Service

1. Android中进程和线程概述

1.1. Android中的进程

当一个程序第一次启动的时候,Android会启动一个Linux进程和一个主线程。默认的状况下,全部该程序的组件都将在该进程和线程中运行。Android会尽可能保留一个正在运行进程,只在内存资源出现不足时,Android会尝试中止一些进程从而释放足够的资源给其余新的进程使用,也能保证用户正在访问的当前进程有足够的资源去及时地响应用户的事件。 
咱们能够将一些组件运行在其余进程中,而且能够为任意的进程添加线程。组件运行在哪一个进程中是在manifest文件里设置的,其中<activity><service><receiver><provider>都有一个process属性来指定该组件运行在哪一个进程之中。咱们能够设置这个属性,使得每一个组件运行在它们本身的进程中,或是几个组件共同享用一个进程,或是不共同享用。<application>元素也有一个process属性,用来指定全部的组件的默认属性。 
Android会根据进程中运行的组件类别以及组件的状态来判断该进程的重要性,Android会首先中止那些不重要的进程。按照重要性从高到低一共有五个级别:java

1.前台进程:android

前台进程是用户当前正在使用的进程。只有一些前台进程能够在任什么时候候都存在。他们是最后一个被结束的,当内存低到根本连他们都不能运行的时候。通常来讲,在这种状况下,设备会进行内存调度,停止一些前台进程来保持对用户交互的响应。 
若是有如下的情形的那么就是前台进程: 
(a)这个进程运行着一个正在和用户交互的Activity(这个Activity的onResume()方法被调用)。 
(b)这个进程里有绑定到当前正在和用户交互的Activity的一个service。 
(c)这个进程里有一个service对象,这个service对象正在执行一个它的生命周期的回调函数(onCreate(), onStart(), onDestroy()) 
(d)这个进程里有一个正在运行onReceive()方法的BroadCastReiver对象。数据库

2.可见进程api

  可见进程是不包含前台的组件可是仍会影响用户在屏幕上所见内容的进程,除非前台进程须要获取它的资源,否则不会被停止。 
  若是有以下的一种情形就是可见进程: 
(a)这个进程中含有一个不位于前台的Activity,可是仍然对用户是可见的(这个Activity的onPause()方法被调用),这是极可能发生的,例如,若是前台Activity是一个对话框的话,就会容许在它后面看到前一个Activity。 
(b)这个进程里有一个绑定到一个可见的Activity的Service。缓存

3.服务进程安全

  运行着一个经过startService() 方法启动的service,它不会升级为上面两种级别。service所在的进程虽然对用户不是直接可见的,可是他们执行了用户很是关注的任务(好比播放mp3,从网络下载数据)。只要前台进程和可见进程有足够的内存,系统不会回收他们。服务器

4.后台进程网络

运行着一个对用户不可见的activity(调用过 onStop() 方法)。这些进程对用户体验没有直接的影响,能够在服务进程、可见进程、前台进程须要内存的时候回收。一般,系统中会有不少不可见进程在运行,他们被保存在LRU (least recently used) 列表中,以便内存不足的时候被第一时间回收。若是一个activity正确的执行了它的生命周期,关闭这个进程对于用户体验没有太大的影响。app

5.空进程ide

  未运行任何程序组件。运行这些进程的惟一缘由是做为一个缓存,缩短下次程序须要从新使用的启动时间。系统常常停止这些进程,这样能够调节程序缓存和系统缓存的平衡。 
Android 对进程的重要性评级的时候,选取它最高的级别。例如,若是一个进程含有一个service和一个可视activity,进程将被纳入一个可视进程而不是service进程。

1.2. Android中的线程

应用程序启动时,系统会为它建立一个名为“main”的主线程。主线程很是重要,由于它负责把事件分发给相应的用户界面widget——包括屏幕绘图事件。它也是应用程序与Android UI组件包(来自android.widget和android.view包)进行交互的线程。所以,主线程有时也被叫作UI线程。

系统并不会为每一个组件的实例都建立单独的线程。运行于同一个进程中的全部组件都是在UI线程中实例化的,对每一个组件的系统调用也都是由UI线程分发的。所以,对系统回调进行响应的方法(好比报告用户操做的onKeyDown()或生命周期回调方法)老是运行在UI线程中。

若是UI线程须要处理每一件事情,那些耗时很长的操做——诸如访问网络或查询数据库等将会阻塞整个UI(线程)。一旦线程被阻塞,全部事件都不能被分发,包括屏幕绘图事件。从用户的角度看来,应用程序看上去像是挂起了。更糟糕的是,若是UI线程被阻塞超过必定时间(目前大约是5秒钟),用户就会被提示那个可恶的“应用程序没有响应”(ANR)对话框。

此外,Andoid的UI组件包并非线程安全的。所以不容许从工做线程中操做UI——只能从UI线程中操做用户界面。因而,Andoid的单线程模式必须遵照两个规则: 
(a)不要阻塞UI线程。 
(b)不要在UI线程以外访问Andoid的UI组件包。

2. Service

2.1. Service概述

Service是Android中四大组件之一,在Android开发中有很是重要的做用。 
Service(服务)是一个没有用户界面的在后台运行执行耗时操做的应用组件。其余应用组件可以启动Service,而且当用户切换到另外的应用场景,Service将持续在后台运行。另外,一个组件可以绑定到一个service与之交互(IPC机制),例如,一个service可能会处理网络操做,播放音乐,操做文件 I/O或者与内容提供者(content provider)交互,全部这些活动都是在后台进行。

2.2. Service的两种状态

Service有两种状态,“启动的”和“绑定”:

Started

经过startService() 启动的服务处于“启动的”状态,一旦启动,Service就在后台运行,即便启动它的应用组件已经被销毁了。一般started状态的Service执行单任务而且不返回任何结果给启动者。好比当下载或上传一个文件,当这项操做完成时,Service应该中止它自己。

Bound

还有一种“绑定”状态的Service,经过调用bindService()来启动,一个绑定的Service提供一个容许组件与Service交互的接口,能够发送请求、获取返回结果,还能够经过跨进程通讯来交互(IPC)。绑定的Service只有当应用组件绑定后才能运行,多个组件能够绑定一个Service,当调用unbind()方法时,这个Service就会被销毁了。

另外,在官方的说明文档中还有一个警告: 
Service 与Activity同样都存在与当前进程的主线程中,因此,一些阻塞UI的操做,好比耗时操做不能放在Service里进行,好比另外开启一个线程来处理 诸如网络请求的耗时操做。若是在Service里进行一些耗CPU和耗时操做,可能会引起ANR警告,这时应用会弹出强制关闭仍是等待的对话框。因此,对Service的理解就是和Activity平级的,只不过是看不见的,在后台运行的一个组件,这也是为何和Activity同被说为Android的基本组件。

2.3. Service的简单使用

1) 建立一个Service,须要继承Service类。

2) 覆盖一些回调函数来处理服务生命周期的一些关键要素,而且若是合适的话,须要提供一个机制让组件绑定到服务。这些最重要的须要覆盖的函数以下:

onStartCommand():

onStartCommand()方法当另外一个组件(如Activity)经过调用startService()请求Service启动时调用。一旦这个方法执行,这个服务就被开启而且在后台无限期地运行。若是你实现了这个方法,你就有责任在服务工做完毕后经过调用stopSelf()或者stopService()关闭它(若是仅仅用来提供绑定,就不须要实现这个方法)。

onBind():

onBind()方法当另外一个组件(如执行RPC)经过bindService()和这个服务绑定的时候调用。在这个方法的实现中,须要经过返回一个IBinder对象提供客户端用来和Service通信的接口,你必须一致实现该方法,除非不想绑定服务,这时候须要返回null。

onCreate()

Service第一次建立的时候调用该方法来执行一次性安装程序(以前调用要么onStartCommand()要么onBind())。若是Service已经运行了,这个方法不会被调用。

onDestory()

当Service再也不使用而且被销毁的时候调用。服务须要实现这个方法来清理资源如线程,注册的监听器,接受者等。这个方法是Service最后一个调用的。

3) 在清单文件中注册服务

<manifest ... >
  ...
  <application ... >
    <service android:name=".ExampleService" />
    ...
  </application>
</manifest>

3. 服务的第一种启动方式

服务的第一种启动方式是调用startService()方法。 
建立一个服务,必须实现onBind()方法,实现onCreate(),onStartCommand()和onDestory()方法:

public class DemoService extends Service {
    @Override
    public IBinder onBind(Intent intent) {      
        return null;
    }
    @Override
    public void onCreate() {
        System.out.println("onCreate");
        super.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();
    }   
}

在清单文件中注册

<service android:name="com.itheima.servicedemo.DemoService"></service>

MainActivity中实现开启服务 
这里写图片描述 
点击按钮开启服务

public void click(View v){
    Intent intent = new Intent(MainActivity.this,DemoService.class);
    startService(intent);   
}

查看Logcat日志发现开启服务调用onCreate()和onStartCommand()方法: 
这里写图片描述

在此点击按钮,查看日志,发现onStartCommand()又执行了一次: 
这里写图片描述

那么如何查看服务真的被启动了呢?咱们能够在模拟器设置界面中的应用界面查看,以下图: 
这里写图片描述

那么如何中止服务呢?点开上图标出的条目,跳转到以下图的另外一个界面,点击中止按钮。以下图: 
这里写图片描述

这时候,服务就中止了。咱们能够查看Logcat日志,发现调用了onDestory()方法: 
这里写图片描述

经过上面的操做能够得出调用startService()方法时Service的生命周期以下图: 
这里写图片描述

4. 电话窃听器案例

本案例实如今后台监听电话状态,当手机来电而且接通后,开始录音,而且保存到sdcard中。 
为何须要在服务中监听电话状态呢?由于服务开启后能够在后台一直运行,若是放在Activity中监听电话状态,当Activity销毁后就不能监听到电话状态了。 
使用Thread(){}.start()方法也能够在后台实现监听,那么为何不使用子线程而使用Service呢?由于以前咱们已经了解了进程的等级,当应用程序退出时,当前应用的进程就变成一个空进程,最容易被系统回收。开启服务是在服务进程中运行,服务进程的优先级比后台进程高。

4.1. 监听电话状态

监听电话状态须要使用TelephonyManager类。TelephonyManager类主要提供了一系列用于访问与手机通信相关的状态和信息的get方法。其中包括手机SIM的状态和信息、电信网络的状态及手机用户的信息。

Context.getSystemService(Context.TELEPHONY_SERVICE)方法能够用来获取到TelephonyManager类的对象。须要注意的是有些通信信息的获取,对应用程序的权限有必定的限制,在开发的时候须要为其添加相应的权限。

查看api文档,找到以下图方法,能够用来监听电话状态: 
这里写图片描述

listen()方法,注册一个监听对象用来接收指定电话状态变化的通知。 
在服务中利用TelephonyManager监听电话状态:

public class PhoneService extends Service {
    private TelephonyManager tManager;
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }
    @Override
    public void onCreate() {
        tManager = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);
        //调用TelephonyManager的listen()方法监听电话状态。参数1表示电话状态监听器,参数2表示须要监听的电话事件
        tManager.listen(new MyPhoneStateListener(),PhoneStateListener.LISTEN_CALL_STATE);
        super.onCreate();
    }
    @Override
    public void onDestroy() {
        super.onDestroy();
    }
}

建立自定义电话状态监听器:

private class MyPhoneStateListener extends PhoneStateListener {
    @Override
    public void onCallStateChanged(int state, String incomingNumber) {
        super.onCallStateChanged(state, incomingNumber);
        //判断state状态
        switch (state) {
        case TelephonyManager.CALL_STATE_IDLE: // CALL_STATE_IDLE表示空闲状态
            System.out.println("判断用户是否已经开启录音机,若是开启,上传");
            break;
        case TelephonyManager.CALL_STATE_OFFHOOK: // CALL_STATE_OFFHOOK表示接通状态
            System.out.println("开始录");
            break;
        case TelephonyManager.CALL_STATE_RINGING: // CALL_STATE_RINGING表示电话响铃状态
            System.out.println("电话响铃的时候  我就准备一个录音机 ");
            break;
        default:
            break;
        }
    }
}

清单文件加入读取手机状态权限:

<uses-permission android:name="android.permission.READ_PHONE_STATE" />

开启服务

public void click(View view){
    Intent intent = new Intent(this,PhoneService.class);
    startService(intent);
}

运行结果 
当开启服务后,没有来电,Logcat打印以下日志: 
这里写图片描述

当有来电时Logcat打印以下日志: 
这里写图片描述 
这里写图片描述

当挂断电话后,打印以下日志: 
这里写图片描述 
这里写图片描述

4.2. 录音

上面已经实现了电话各类状态的监听,如今能够在不一样的状态下操做录音,将通话录音记录到sdcard,这里须要用到MediaRecorder对象。具体的介绍咱们能够参考api。 
这里写图片描述

在监听方法中实现录音功能:

//建立MediaRecorder
private MediaRecorder recorder;
......
switch (state) {
    case TelephonyManager.CALL_STATE_IDLE: 
        //关闭Recorder,释放资源
        if (recorder!=null) {
            recorder.stop();    
            recorder.reset();  
            recorder.release(); 
        }           
        break;
    case TelephonyManager.CALL_STATE_OFFHOOK: 
        System.out.println("开始录");
        //开启录音
        recorder.start(); 
        break;      
    case TelephonyManager.CALL_STATE_RINGING:   
        //建立MediaRecorder对象
        recorder = new MediaRecorder(); 
        //设置音频来源
        recorder.setAudioSource(MediaRecorder.AudioSource.MIC);  
        //设置影音的输出格式
        recorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
        //设置音频文件的编码格式
        recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
        //设置音频文件保存的路径
        recorder.setOutputFile("/mnt/sdcard/luyin.3gp");
        try {
            //准备录音
            recorder.prepare();
        } catch (IllegalStateException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        break;
    default:
        break;
}

加入权限

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />

运行结果,能够在sdcard中看到保存的录音luyin.3gp。 
这里写图片描述

4.3. 自动开启服务

如何自动开启服务呢?能够在系统重启时,利用广播接受者接收系统重启广播,在广播中开启监听电话状态的服务。 
定义广播接受者

public class BootReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        Intent intent2 = new Intent(context,PhoneService.class);
        context.startService(intent2);
    }
}

须要加入权限

<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />

5. 服务的第二种启动方式

开启服务的第二种方式是bindService()。第一种方式是startService()方法开启,与之对应的中止服务的方法是stopService(),bindService()对应的方法是unbindService()。 
首先编写Service类:

public class TestService extends Service {
    //当服务被成功绑定的时候调用
    @Override
    public IBinder onBind(Intent intent) {
        System.out.println("onBind");
        return null;
    }
    @Override
    public void onCreate() {
        System.out.println("onCreate");
        super.onCreate();
    }
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        System.out.println("onStartCommand");
        return super.onStartCommand(intent, flags, startId);
    }
    @Override
    public boolean onUnbind(Intent intent) {
        System.out.println("onUnbind");
        return super.onUnbind(intent);
    }
    @Override
    public void onDestroy() {
        System.out.println("onDestroy");
        super.onDestroy();
    }
}

而后实现Activity中按钮点击事件开启服务: 
这里写图片描述 
MainActivity中实现两种方式开启服务:

public class MainActivity extends Activity {
    private MyConn conn;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
    // 采用start-service方式开启服务
    public void click1(View v) {
        Intent intent = new Intent(this, TestService.class);
        startService(intent);
    }
    // 采用stop-service方式开启服务
    public void click2(View v) {
        Intent intent = new Intent(this, TestService.class);
        stopService(intent);
    }
    // 采用bind-service方式开启服务
    public void click3(View v) {
        Intent intent = new Intent(this, TestService.class);
        // conn 是ServiceConnection接口用来监视服务的状态 flags 为了绑定服务
        conn = new MyConn();
        //bindService()方法,用来绑定服务,当这个方法一调用,Servie中的onBind()方法就会执行。参数1表示须要传入的服务的intent,参数2即是一个ServiceConnection的实现类,用来监视服务的状态,参数3是绑定的操做选项
        bindService(intent, conn, BIND_AUTO_CREATE);
    }
    // 采用unbind-service方式开启服务
    public void click4(View v) {
        unbindService(conn);
    }

    //MyConn类实现ServiceConnection接口,用来监视服务的状态。其中有两个回调方法,onServiceConnected()方法表示当服务器链接的时候调用,onServiceDisconnected()方法表示当服务失去链接时调用
    private class MyConn implements ServiceConnection {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service)
         {
        }
        @Override
        public void onServiceDisconnected(ComponentName name) {
        }
    }
    @Override
    protected void onDestroy() {
        //unbindService()方法,用来解绑服务
        //unbindService(conn);
        super.onDestroy();
    }
}

首先点击bindService(),Service会调用onCreate()和onBind()方法,查看Logcat日志: 
这里写图片描述

咱们点击返回键,这样Activity就会销毁,这时候查看日志,发现Service调用了onDestory()方法,同时Logcat日志会报错,以下图: 
这里写图片描述 
这里写图片描述

出现这个错误的缘由是因为当Activity销毁的时候会调用onDestory()方法,在Activity须要调用unbindService()方法将服务解绑。因此咱们还须要在Activity的onDestory()方法中加入如下代码:

@Override
protected void onDestroy() {
    unbindService(conn);
    super.onDestroy();
}

接下来,咱们从新部署项目,而后点击bind-service按钮,Service会调用onCreate()和onBind()方法,Logcat日志会打印以下结果: 
这里写图片描述

这时候咱们查看模拟器设置里的应用中查找咱们的服务,发现找不到咱们开启的服务,以下图: 
这里写图片描述

而后点击unbind-service按钮,Service会调用onUnbind()和onDestory()方法,Logcat日志会打印如下结果: 
这里写图片描述

接着,咱们从新部署项目,连续点击两次bind-service按钮,而后咱们点击unbind-service按钮,再次点击unbind-service按钮,这时候应用会崩溃,而且Logcat会输出如下错误日志: 
这里写图片描述

总结: 
(a)使用bindService()绑定服务,不会使进程变成服务进程; 
(b)使用bindService()绑定服务,只能够绑定一次,不能屡次绑定。 
使用bindService()方法开启服务,Service的生命周期以下图: 
这里写图片描述

6. 调用服务中的方法

6.1. 普通方式调用服务中方法

建立一个Service,在Service中定义一个内部方法:

public class TestDemoService extends Service {
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }
    @Override
    public void onCreate() {
        super.onCreate();
    }
    @Override
    public void onDestroy() {
        super.onDestroy();
    }
    public void serviceMethod(){
        Toast.makeText(this, "我是服务里面的方法", 1).show();
    }
}

在MainActivity中点击按钮调用服务中的方法:

public class MainActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
     public void click(View v){
         //建立TestDemoService对象
        TestDemoService testDemoService = new TestDemoService();
        //调用serviceMethod()方法
        testDemoService.serviceMethod();
     }
}

运行结果: 
这里写图片描述

应用程序会崩溃,而且Logcat会有以下日志输出: 
这里写图片描述 
定位到代码中,是Service中自定义的方法中弹出吐司出现了错误。因为这边调用Service服务中的方法是经过Service的一个实例来调用的,那么这个实例对象就是一个普通的类,普通的类中没有上下文,因此弹出吐司须要传递的上下文就为null,因此程序会报空指针异常。

6.2. 经过IBinder调用服务中的方法

当使用bindService()绑定服务的时候,须要传入一个参数,这个参数是ServiceConnection的一个实现类,这个实现类代码以下:

private class MyConn implements ServiceConnection{
    @Override
    //onServiceConnected()方法当绑定成功后调用,其中有一个IBinder对象
    public void onServiceConnected(ComponentName name, IBinder service) {
    }
    @Override
    public void onServiceDisconnected(ComponentName name) {
    }
}

而后分析Service中的onBind()方法代码:

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

经过onBind()方法发现这个方法返回值是IBinder类型,和咱们上面ServiceConnection中绑定成功回调中的一个参数类型一致。其实,这边的两个对象是同一个对象,当服务绑定成功后,会返回IBinder对象给ServiceConnection中的回调函数。

查看IBinder类,发现这个类是一个接口,因此咱们要么实现IBinder接口,要么继承它已知的子类,这里咱们继承IBinder的一个子类Binder类。

public class TestDemoService extends Service {
    @Override
    public IBinder onBind(Intent intent) {
        //返回自定义MyBinder对象
        return new MyBinder();
    }
    @Override
    public void onCreate() {
        super.onCreate();
    }
    @Override
    public void onDestroy() {
        super.onDestroy();
    }
    public void serviceMethod(){
        Toast.makeText(this, "我是服务里面的方法", 1).show();
    }   

    //建立MyBinder类继承Binder类
    public class MyBinder extends Binder{
        //建立callServiceMethod()方法
        void callServiceMethod(){
            //调用Service中的serviceMethod()方法
            serviceMethod();
        }
    }
}

这时候当服务成功绑定后,ServiceConnection中的回调函数就能接收到这个对象,经过这个对象来调用服务中的方法。在MainActivity中绑定服务,而且经过IBinder调用服务中的方法:

public class MainActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
    public void click(View v){
        bindService(new Intent(this,TestDemoService.class), new MyConn(), BIND_AUTO_CREATE);
    }   
    class MyConn implements ServiceConnection{
        @Override
        public void onServiceConnected(ComponentName name, IBinder service)
         {
             //接收Service中onBind()返回的IBinder对象,这个IBinder对象就是咱们自定义的MyBinder对象
            MyBinder binder = (MyBinder) service;
            //经过IBinder对象调用服务中的方法
            binder.callServiceMethod();
        }
        @Override
        public void onServiceDisconnected(ComponentName name) { 
        }
    }   
}

下图是简易的Activity中调用Service中的方法图: 
这里写图片描述

这时候,问题又来了,若是Service中有不少方法,那么中间人IBinder就能够调用Service中的方法,那么如何控制IBinder中的方法呢?咱们能够定义一个接口,将Service中的方法定义到接口中,让咱们的自定义的Binder实现这个接口。

定义的接口:

public interface Iservice {
    public void callBanZheng(int money);
    public void callPlayMaJiang();
    public void callXiSangNa();
}

自定义的MyBinder实现该接口:

private class Mybinder extends Binder implements IService {
    @Override
    public void callBanZheng(int money) {
        banZheng(money);
    }
    @Override
    public void callPlayMaJiang() { 
         playMaJiang(); 
    }
    @Override
    public void callXiSangNa() {
         xiSangna();        
    }
}

这样,自定义的MyBinder类就能够调用Service中的方法。若是Service不但愿MyBinder具备其中某个方法,那么能够在IService接口中不提供该方法。

7. 混合方式开启Service

7.1. 音乐播放器案例

音乐播放器须要使用到服务,由于当音乐播放器须要在后台也能运行,当手机按Home键后,音乐播放器也须要可以播放,本案例实模拟音乐播放器的实现。在后台服务中,既要保证服务可以在后台一直运行,又要保证Activity中可以调用服务中的方法,这就须要利用两种启动方法混合使用。

主界面以下图: 
这里写图片描述

定义一个服务MusicService用来在服务中播放音乐,暂停播放,继续播放等:

public class MusicService extends Service {
    @Override
    public IBinder onBind(Intent intent) {
        return new MyBinder();
    }
    @Override
    public void onCreate() {
        super.onCreate();
    }
    @Override
    public void onDestroy() {
        super.onDestroy();
    }
    //定义开始播放方法
    public void play(){
        System.out.println("音乐开始播放了 ");
    }
    //定义音乐暂停方法
    public void pause(){
        System.out.println("音乐暂停了 ");
    }   
    //定义音乐继续播放方法
    public void rePlay(){
        System.out.println("音乐继续播放 ");
    }
    private class MyBinder extends Binder implements IService{
        @Override
        public void callPlay() {
            play();
        }
        @Override
        public void callPause() {
            pause();
        }
        @Override
        public void callReplay() {
            rePlay();
        }
    }
}

IService接口中定义方法:

public interface Iservice {
    public void callPlay();
    public void callPause();
    public void callReplay();
}

在Activity中点击按钮,实现音乐播放器的功能:

public class MainActivity extends Activity {
    private Myconn myconn;
    private Iservice iservice; 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Intent intent = new Intent(this,MusicService.class);
        //调用startService()方法开启服务,保证服务在后台长期运行
        startService(intent);
        myconn = new Myconn();
        //调用bindService()方法,目的是为了调用服务中的方法
        bindService(intent, myconn, BIND_AUTO_CREATE);
    }
    public void click1(View v) {
        iservice.callPlay();
    }
    public void click2(View v) {
        iservice.callPause();
    }
    public void click3(View v) {
        iservice.callReplay();
    }
    private class Myconn implements ServiceConnection{
        @Override
        public void onServiceConnected(ComponentName name, IBinder service)
         {
            iservice = (Iservice) service;
        }
        @Override
        public void onServiceDisconnected(ComponentName name) { 
        }
    }
    @Override
    protected void onDestroy() {
        unbindService(myconn);
        super.onDestroy();
    }
}

运行结果: 
当应用程序一块儿动,就开启服务,而且一直运行。以下图: 
这里写图片描述

首先点击播放,这时候音乐开始播放,Logcat输出以下日志: 
这里写图片描述

而后点击暂停,这时候音乐暂停播放,Logcat输出以下日志: 
这里写图片描述

接下来点击继续播放,这时候音乐继续播放,Logcat输出以下日志: 
这里写图片描述

7.2. 混合方式开启服务流程

(a)采用startServie()方法开启服务,保证服务可以在后台长期运行; 
(b)调用bindServie()方法,目的是可以调用服务中的方法; 
(c)Activity销毁时调用unbindService()方法解绑; 
(d)最后调用stopService()方法中止服务。

8. 使用服务注册特殊广播接收者

安卓中,有些广播须要广播接受者动态注册(采用代码的方式注册),好比电池电量低、解锁屏等特殊的广播。

8.1. 接收解锁屏广播

定义广播接受者类ScreenReceiver:

public class ScreenReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        if ("android.intent.action.SCREEN_OFF".equals(action)) {
            System.out.println("屏幕off了");
        }else if("android.intent.action.SCREEN_ON".equals(action)){
            System.out.println("屏幕on了");
        }
    }
}

在清单文件中注册广播:

<receiver android:name="com.itheima.register.ScreenReceiver" >
    <intent-filter>
        <action android:name="android.intent.action.SCREEN_ON" />
        <action android:name="android.intent.action.SCREEN_OFF" />
    </intent-filter>
</receiver>

咱们在模拟器上,解屏或者锁屏,发现Logcat没有日志输出,说明静态注册广播接受者不能接收到解锁屏的广播。

为何不能接收到屏幕解锁屏的广播呢?由于手机一天会解锁屏不少次,因此广播接受者会不断的注册,这样就比较耗电,因此谷歌就对发送这种特殊的广播作了特殊处理,只有动态注册,才能监听到这类广播。

咱们在Activity中注册广播:

public class MainActivity extends Activity {
    private ScreenReceiver screenReceiver;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        screenReceiver = new ScreenReceiver();
        //建立IntentFilter对象,给该对象设置Action
        IntentFilter filter = new IntentFilter();
        filter.addAction("android.intent.action.SCREEN_OFF");
        filter.addAction("android.intent.action.SCREEN_ON");
        //调用Context对象的registerReceiver()注册广播。参数1表示广播接受者,参数2表示意图过滤器
        registerReceiver(screenReceiver, filter);
    }
    @Override
    protected void onDestroy() {
        super.onDestroy();
    }
}

运行结果: 
当点击锁屏按钮,屏幕黑屏,Logcat会输出以下日志: 
这里写图片描述 
这里写图片描述

点击解屏按钮,屏幕亮起,Logcat会输出以下日志: 
这里写图片描述

这时候,若是点击返回按钮,应用程序退出,Logcat会报以下错误,提示咱们须要调用unregisterReceiver()方法: 
这里写图片描述

在Activity的onDestory()中调用unregisterReceiver()方法:

protected void onDestroy() {
    unregisterReceiver(screenReceiver);
    super.onDestroy();
}

8.2. 保持广播监听

由于在Activity中注册广播,当Activity被销毁后,就不能监听到解锁屏的广播,因此,能够在Service中后台一直监听广播。

public class ScreenService extends Service {
    private ScreenReceiver screenReceiver;
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }
    @Override
    public void onCreate() {
        screenReceiver = new ScreenReceiver();
        IntentFilter filter = new IntentFilter();
        filter.addAction("android.intent.action.SCREEN_OFF");
        filter.addAction("android.intent.action.SCREEN_ON");
        registerReceiver(screenReceiver, filter);
        super.onCreate();
    }
    @Override
    public void onDestroy() {
        unregisterReceiver(screenReceiver);
        super.onDestroy();
    }
}

9. 进程间通讯

IPC(Inter-Process Communication):进程间通讯。 
Android系统中,每一个应用程序都会有一个进程,因为应用程序之间不能共享内存,那么就须要进程间通讯。

9.1. 本地服务和远程服务

本地服务:服务和启动它的组件在同一个进程。 
远程服务:服务和启动它的组件不在同一个进程。 
远程服务只能隐式启动,相似隐式启动Activity,在清单文件中配置Service标签时,必须配置intent-filter子节点,并指定action子节点。

9.2. AIDL

AIDL(Android Interface Definition Language):安卓接口定义语言。AIDL是安卓中用来跨进程通讯的一种接口定义语言。 
应用场景:远程服务中的中间人对象,其余应用是拿不到的,那么在经过绑定服务获取中间人对象时,就没法强制转换,使用aidl技术,就能够在其余应用中拿到中间人类所实现的接口。

9.3. AIDL的使用

建立一个远程应用项目,在项目中建立一个远程服务类RemoteService:

public class RemoteService extends Service {
    @Override
    public IBinder onBind(Intent intent) {
        return new MyBinder();
    }
    @Override
    public void onCreate() {
        super.onCreate();
    }
    public void remoteMethod(){
        System.out.println("我是远程服务里面的方法");
    }
    private class MyBinder extends Binder implements Iservice{
        @Override
        public void callRemoteMethod() {
            remoteMethod();
        }
    }
}

IService代码:

public interface Iservice {
     public void callRemoteMethod();
}

配置远程服务:

<service android:name="com.itheima.remoteservice.RemoteService"></service>

建立本地应用项目,在MainActivity中实现点击按钮事件:

public class MainActivity extends Activity {
    private Iservice iservice;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Intent intent = new Intent();
        bindService(intent, new MyConn(), BIND_AUTO_CREATE);
    }
    public void click(View v){  
    }
     private class MyConn implements ServiceConnection{
        //当服务被连接成功的时候调用 
        @Override
        public void onServiceConnected(ComponentName name, IBinder service)
        {   
        }
        @Override
        public void onServiceDisconnected(ComponentName name) {     
        }
    }
}

以上是调用本地服务的操做步骤,下面利用aidl实现本地应用访问远程服务。 
首先,将定义方法的接口类Iservice文件的后缀名改为.aidl,这时候刷新下工程,Iservice文件会报错,以下图: 
这里写图片描述 
这里写图片描述

从上图能够看出,须要将public关键字去掉,这样咱们的Iservice就不会报错。为何不能使用public呢?既然Iservice这个aidl是用来解决进程间通讯的,那么它确定是public的,因此不须要加上public。 
这时候到工程目录的gen目录下能够看到Iservice.java这个文件,打开看一下,以下图: 
这里写图片描述

这里写图片描述

从上图能够看到生成的文件中,有一个Stub类,继承Binder类而且实现Iservice接口。这时候咱们发现,自定义的中间人能够继承这个Stub类。 
接下来咱们要在本地应用中经过中间人来访问远程服务,那么如何保证本地应用的中间人和远程服务的中间人是同一个呢?谷歌规定,只要在本地应用中,包名和aidl文件名和远程服务一致就能够了。 
远程服务的包名: 
这里写图片描述

本地应用中须要建立和远程服务同样的包名,而后将Iservice.aidl文件拷贝到本地应用中建立的包中: 
这里写图片描述

作完以上操做后,在本地应用的gen目录下就能够看到自动生成的Iservice.java文件,有了这个文件,就能够调用远程服务中的方法,以下图: 
这里写图片描述

在本地应用的MainActivity中完善代码:

public class MainActivity extends Activity {
    private Iservice iservice;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Intent intent = new Intent();
        //经过设置intent的action来指定打开的服务,这边须要在远程服务的项目的清单文件中配置这个action
        intent.setAction("com.itheima.remoteservice");
        bindService(intent, new MyConn(), BIND_AUTO_CREATE);
    }
    public void click(View v){
        try {
            //调用中间人对象调用远程服务中的方法
            iservice.callRemoteMethod();
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }
    private class MyConn implements ServiceConnection{
        @Override
        public void onServiceConnected(ComponentName name, IBinder service)
         {
             //经过Iservice.Stub.asInterface(service)方法获取IBinder对象
             iservice = Iservice.Stub.asInterface(service);
        }
        @Override
        public void onServiceDisconnected(ComponentName name) {
        }
    }
}

在远程服务的项目的清单文件中配置这个action

<service android:name="com.itheima.remoteservice.RemoteService">
     <intent-filter >
          <action android:name="com.itheima.remoteservice"/>
     </intent-filter>
</service>

运行效果: 
首先部署远程服务到手机中,而后部署本地服务,点击按钮后,查看日志,发现调用了远程服务中的方法。以下图: 
这里写图片描述 
这里写图片描述

10. 远程服务的应用场景

10.1. 支付宝服务

远程服务最多见的应用场景就是支付宝,本案例模拟支付宝支付过程。 
建立Iservice接口,在接口中定义中间人对象须要实现的方法:

public interface Iservice {
     public boolean callPay(String name,String pwd,int money);
}

将Iservice文件后缀名改成.aidl,取消访问修饰符public,以下图: 
这里写图片描述

定义支付宝的服务,在服务中定义pay方法:

public class ALiPayService extends Service {
    @Override
    public IBinder onBind(Intent intent) {
        return new MyBinder();
    }
    @Override
    public void onCreate() {
        super.onCreate();
    }
    @Override
    public void onDestroy() {
        super.onDestroy();
    }
    //定义pay方法,返回值为boolean类型,用来判断支付结果(成功或失败)
    public boolean pay(String name,String pwd,int money){
        System.out.println("检查用户名和密码是否正确....");
        System.out.println("检查手机是否有病毒....");
        System.out.println("检查余额是否够用....");     
        if ("abc".equals(name)&&"123".equals(pwd)&&money>5000) {
            return true;
        }else {
            return false;
        }
    }
}

在服务中定义中间人对象MyBinder类,直接继承Stub类,实现方法:

private class MyBinder extends Stub{
    @Override
    public boolean callPay(String name, String pwd, int money) {
         //调用Service中的pay方法
         return pay(name, pwd, money);
    }
}

注册支付宝服务,须要在AndroidManifest.xml文件中配置,而且加上意图过滤器:

<service android:name="com.itheima.alipay.ALiPayService">
    <intent-filter >
         <action android:name="com.itheima.alipay"/>
    </intent-filter>
</service>

10.2. 其余应用调用支付宝服务

接下来模拟“欢乐斗地主”应用调用支付宝支付,在欢乐斗地主项目工程目录下建立与远程服务同名的包,将远程服务的aidl文件拷贝到新建的包下:

这里写图片描述

绑定到远程服务,点击按钮调用远程服务的支付方法支付:

public class MainActivity extends Activity {
    private MyConn myConn;
    private Iservice iservice
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Intent intent = new Intent();
        intent.setAction("com.itheima.alipay");
        myConn = new MyConn();
        bindService(intent, myConn, BIND_AUTO_CREATE);
    }
    public void click(View v){  
        try {
            boolean result = iservice.callPay("abc", "123", 501);       
            if (result) {
                Toast.makeText(getApplicationContext(), "够买欢乐豆成功", 0).show();
            }else {
                Toast.makeText(getApplicationContext(), "够买失败", 0).show();
            }
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }   
    private class MyConn implements ServiceConnection{
        @Override
        public void onServiceConnected(ComponentName name, IBinder service)
        {       
             iservice = Iservice.Stub.asInterface(service); 
        @Override
        public void onServiceDisconnected(ComponentName name) {     
        }   
    }
}

咱们调用支付功能,支付501,运行结果: 
这里写图片描述

咱们将支付金额改为5001,从新部署项目,再次调用支付功能,运行结果: 
这里写图片描述

相关文章
相关标签/搜索