第7章 后台服务
本章学习目标 Service简介 本地服务 远程服务
Service简介 Service概念 Service是Android系统的后台服务组件,适用于开发无界面、长时间运行的应用功能 特点 没有用户界面 比处于非活动状态的Activity 优先级高,不会轻易被 Android系统终止。即使Service被系统终止,在系统资源 恢复后Service也将自动恢复运行状态 用于进程间通信(Inter Process Communication,IPC), 解决两个不同Android应用程序进程之间的调用和通讯问 题
Service简介 Service生命周期 Service生命周期包括 全生命周期 活动生命周期 开始:onCreate()事件回调函数,完成Service的初始化工作 结束:onDestroy()事件回调函数,释放Service所有占用的资源 活动生命周期 开始:onStart()事件回调函数 结束:但没有与之对应的(OnStop())函数,因此可以近似认为活动生命周期也是以onDestroy()标志结束
Service简介 Service使用方法 启动方式 绑定方式 混合方式 Activity Service
Service简介 启动方式 通过调用Context.startService()启动Service, 通过调用Context.stopService()或Service.stopSelf()停 止Service Service是由其他的组件启动的,但停止过程可以通过其他组件或自身完成 如果仅以启动方式使用的Service,这个Service需要具备自管理的能力,且不需要通过函数调用向外部组件提供数据或功能
Service简介 绑定方式 通过服务链接(Connection)或直接获取Service中状态和数据信息 服务链接能够获取Service的对象,因此绑定Service的组件可以调用Service中的实现的函数 使用Service的组件通过Context.bindService()建立服务链接,通过Context.unbindService()停止服务链接 如果在绑定过程中Service没有启动,Context.bindService()会自动启动Service 同一个Service可以绑定多个服务链接,这样可以同时为多个不同的组件提供服务
Service简介 启动方式和绑定方式的结合 这两种使用方法并不是完全独立的,在某些情况下可以混合使用 以MP3播放器为例, 在后台的工作的Service通过Context.startService()启动某个特定音乐播放, 但在播放过程中如果用户需要暂停音乐播放,则需要通过Context.bindService()获取服务链接和Service对象,进而通过调用Service的对象中的函数,暂停音乐播放过程,并保存相关信息。 在这种情况下,如果调用Context.stopService()并不能够停止Service,需要在所有的服务链接关闭后,Service才能够真正的停止
本地服务 服务的启动和停止 实现Service的最小代码集 onBind()函数是在Service被绑定后调用的函数,能够返 回Service的对象 import android.app.Service; import android.content.Intent; import android.os.IBinder; public class RandomService extends Service{ @Override public IBinder onBind(Intent intent) { return null; } 引入必要的包 继承Service 重载OnBind()
本地服务 Service会根据实际情况选择需要重载上面的三个函 数 Service的最小代码集并不能完成任何实际的功能,需要重载onCreate()、onStart()和onDestroy(),才使Service具有实际意义 onCreate() :Android系统在创建Service时,自动调用,完成必要的初始化工作 onStart():通过Context.startService(Intent)启动Service时,被系统调用;Intent会传递给Service一些重要的参数 onDestroy():在Service没有必要再存在时,系统自动调用,释放所有占用的资源 Service会根据实际情况选择需要重载上面的三个函 数
本地服务 注册Service 在AndroidManifest.xml文件中注册,否则,这个Service根本无法启动 使用<service>标签声明服务,其中的android:name表示的是Service的类名称,一定要与用户建立的Service类名称一致 <service android:name=".RandomService"/>
本地服务 启动Service 显示启动 隐式启动 显示启动:在Intent中指明Service所在的类,并调用 startService(Intent)函数启动Service,示例代码如下 在Intent中指明启动的Service在RandomSerevice.class中 final Intent serviceIntent = new Intent(this, RandomService.class); startService(serviceIntent);
本地服务 隐式启动:在注册Service时,声明Intent-filter的action属性 设置Intent的action属性,可以在不声明Service所在类的情况下启动服务 隐式启动的代码如下: <service android:name=".RandomService"> <intent-filter> <action android:name="edu.hrbeu.RandomService" /> </intent-filter> </service> 声明action属性 final Intent serviceIntent = new Intent(); serviceIntent.setAction("edu.hrbeu.RandomService"); 隐式启动
本地服务 若Service和调用服务的组件在同一个应用程序中,可以使用显式启动或隐式启动,显式启动更加易于使用,且代码简洁 App1 App2 Service Activity Service Activity 显式 隐式 隐式 Android OS
本地服务 停止Service 将启动Service的Intent传递给stopService(Intent)函数即可,示例代码如下 在调用startService(Intent)函数首次启动Service后,系统会 先后调用onCreate()和onStart() 再次调用startService(Intent)函数,系统则仅调用onStart(), 而不再调用onCreate() 在调用stopService(Intent)函数停止Service时,系统会调用 onDestroy() 无论调用过多少次startService(Intent),在调用stopService (Intent)函数时,系统仅调用onDestroy()一次 stopService(serviceIntent);
本地服务 示例SimpleRandomServiceDemo以显式启动服务 在工程中创建RandomService服务,该服务启动后会产生一个随机数,使用Toast显示在屏幕上 “启动Service”按钮调用startService(Intent)函数,启动RandomService服务 “停止Service”按钮调用stopService(Intent)函数,停止RandomService服务
本地服务 RandomService.java文件的代码如下 package edu.hrbeu.SimpleRandomServiceDemo; import android.app.Service; import android.content.Intent; import android.os.IBinder; import android.widget.Toast; public class RandomService extends Service{ @Override public void onCreate() { super.onCreate(); Toast.makeText(this, "(1) 调用onCreate()", Toast.LENGTH_LONG).show(); }
本地服务 17. @Override 18. public void onStart(Intent intent, int startId) { 19. super.onStart(intent, startId); 20. Toast.makeText(this, "(2) 调用onStart()", Toast.LENGTH_SHORT).show(); 23. double randomDouble = Math.random(); 24. String msg = "随机数:"+ String.valueOf(randomDouble); 25. Toast.makeText(this,msg, Toast.LENGTH_SHORT).show(); 26. } 27. 28. @Override 29. public void onDestroy() { 30. super.onDestroy(); 31. Toast.makeText(this, "(3) 调用onDestroy()", 32. Toast.LENGTH_SHORT).show(); } . 35. @Override 36. public IBinder onBind(Intent intent) { 37. return null; 38. } 39. }
本地服务 AndroidManifest.xml文件的代码如下 <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="edu.hrbeu.SimpleRandomServiceDemo" android:versionCode="1" android:versionName="1.0"> <application android:icon="@drawable/icon" android:label="@string/app_name"> <activity android:name=".SimpleRandomServiceDemo" android:label="@string/app_name"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> 14. <service android:name=".RandomService"/> 15. </application> 16. <uses-sdk android:minSdkVersion="3" /> 17. </manifest>
本地服务 SimpleRandomServiceDemo.java文件的代码如下 package edu.hrbeu.SimpleRandomServiceDemo; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.view.View; import android.widget.Button; public class SimpleRandomServiceDemo extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main);
本地服务 第20行和第25行分别是启动和停止Service的代码 15. Button startButton = (Button)findViewById(R.id.start); 16. Button stopButton = (Button)findViewById(R.id.stop); 17. final Intent serviceIntent = new Intent(this, RandomService.class); 18. startButton.setOnClickListener(new Button.OnClickListener(){ 19. public void onClick(View view){ 20. startService(serviceIntent); 21. } 22. }); 23. stopButton.setOnClickListener(new Button.OnClickListener(){ 24. public void onClick(View view){ 25. stopService(serviceIntent); 26. } 27. }); 28. } 29. }
本地服务 服务绑定 以绑定方式使用Service,能够获取到Service对象, 为了使Service支持绑定,需要在Service类中重载 onBind()方法,并在onBind()方法中返回Service对象, 示例代码如下
本地服务 当Service被绑定时,系统会调用onBind()函数,通过onBind()函数的返回值,将Service对象返回给调用者 public class MathService extends Service{ private final IBinder mBinder = new LocalBinder(); public class LocalBinder extends Binder{ MathService getService() { return MathService.this; } @Override public IBinder onBind(Intent intent) { return mBinder;
本地服务 第11行代码中可以看出,onBind()函数的返回值必须是符 合IBinder接口,因此在代码的第2行声明一个接口变量 mBinder,mBinder符合onBind()函数返回值的要求,因 此将mBinder传递给调用者 IBinder是用于进程内部和进程间过程调用的轻量级接口 ,定义了与远程对象交互的抽象协议,使用时通过继承 Binder的方法实现 第4行代码继承Binder,LocalBinder是继承Binder的一个 内部类 第5行代码实现了getService()函数,当调用者获取到 mBinder后,通过调用getService()即可获取到Service的 对象
onServiceDisConnected 本地服务 绑定服务:bindService()函数 final Intent serviceIntent = new Intent(this, MathService.class); bindService(serviceIntent, mConnection, Context.BIND_AUTO_CREATE); Activity Service ServiceConnection onServiceConnected bindService onServiceDisConnected Android OS
本地服务 单向调用 Acitvity Service Acitvity Acitvity Andriod操作系统 onCreate() onStart() onStart() startService() onResume() stopService() onDestory() startActivity() …… Intent Intent Andriod操作系统
本地服务 双向调用 Service Acitvity Acitvity Acitvity int Add() getService() Service Acitvity Acitvity Acitvity onCreate() onStart() onCreate() onStart() onDestory() bindService() startActivityForResult() onResume() …… unBindService() onBind() setResult() IBinder int Add() onActivityResult() finish() LocalBinder Intent IBinder Intent requestCode Intent ServiceConnection ResultCode LocalBinder onServiceConnected BIND_AUTO_CREATE onServiceDisConnected Andriod操作系统
本地服务 第1个参数中将Intent传递给bindService()函数,声明需要启动的Service 第3个参数Context.BIND_AUTO_CREATE表明只要绑定 存在,就自动建立Service;同时也告知Android系统,这 个Service的重要程度与调用者相同,除非考虑终止调用 者,否则不要关闭这个Service 第2个参数是ServiceConnnection 为了绑定方式使用Service,调用者需要声明一个 ServiceConnnection,并重载内部的onServiceConnected() 方法和onServiceDisconnected方法 当绑定成功后,系统将调用ServiceConnnection的 onServiceConnected()方法 而当绑定意外断开后,系统将调用ServiceConnnection中 的onServiceDisconnected方法
本地服务 在第4行代码中,绑定成功后通过getService()获取 Service对象,这样便可以调用Service中的方法和属性 第8行代码将Service对象设置为null,表示绑定意外失效 ,Service实例不再可用 private ServiceConnection mConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { mathService = ((MathService.LocalBinder)service).getService(); } public void onServiceDisconnected(ComponentName name) { mathService = null; };
本地服务 取消绑定:unbindService()方法,并将ServiceConnnection传递给unbindService()方法 需注意的是,unbindService()方法成功后,系统并不会调 用onServiceDisconnected(),因为 onServiceDisconnected()仅在意外断开绑定时才被调用 unbindService(mConnection);
本地服务 通过bindService()函数绑定Servcie时, 通过unbindService()函数取消绑定Servcie时 onCreate()函数和onBind()函数将先后被调用 通过unbindService()函数取消绑定Servcie时 onUnbind()函数将被调用, 如果onUnbind()函数的返回true,则表示在调用者绑定新服务时,onRebind()函数将被调用 绑定方式的函数调用顺序
本地服务 示例SimpleMathServiceDemo使用绑定方式使用Service 创建了MathService服务,用来完成简单的数学运算但足以说明如何使用绑定方式调用Service实例中的公有方法 在服务绑定后,用户可以点击“加法运算”,将两个随机产生的数值传递给MathService服务,并从MathService对象中获取到加法运算的结果,然后显示在屏幕的上方 “取消绑定”按钮可以解除与MathService的绑定关系,取消绑定后,无法通过“加法运算”按钮获取加法运算结果
本地服务 在SimpleMathServiceDemo示例中,MathService.java文件是描述Service的文件 package edu.hrbeu.SimpleMathServiceDemo; import android.app.Service; import android.content.Intent; import android.os.Binder; import android.os.IBinder; import android.widget.Toast;
本地服务 public class MathService extends Service{ private final IBinder mBinder = new LocalBinder(); 13. public class LocalBinder extends Binder{ 14. MathService getService() { 15. return MathService.this; 16. } } 19. @Override 20. public IBinder onBind(Intent intent) { 21. Toast.makeText(this, "本地绑定:MathService", 22. Toast.LENGTH_SHORT).show(); 23. return mBinder; 24. } 25.
本地服务 26. @Override 27. public boolean onUnbind(Intent intent){ 28. Toast.makeText(this, "取消本地绑定:MathService", 29. Toast.LENGTH_SHORT).show(); 30. return false; 31. } 32. 33. 34. public long Add(long a, long b){ 35. return a+b; 36. } 37. 38. }
本地服务 SimpleMathServiceDemo.java文件是界面的Activity文件,绑定和取消绑定服务的代码在这个文件中 package edu.hrbeu.SimpleMathServiceDemo; import android.app.Activity; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.os.Bundle; import android.os.IBinder; import android.view.View; import android.widget.Button; import android.widget.TextView;
本地服务 14. public class SimpleMathServiceDemo extends Activity { 15. private MathService mathService; 16. private boolean isBound = false; 17. TextView labelView; 18. @Override 19. public void onCreate(Bundle savedInstanceState) { 20. super.onCreate(savedInstanceState); 21. setContentView(R.layout.main); 22. 23. labelView = (TextView)findViewById(R.id.label); 24. Button bindButton = (Button)findViewById(R.id.bind); 25. Button unbindButton = (Button)findViewById(R.id.unbind); 26. Button computButton = (Button)findViewById(R.id.compute); 27. 28. bindButton.setOnClickListener(new View.OnClickListener(){ 29. @Override 30. public void onClick(View v) { 31. if(!isBound){
本地服务 32. final Intent serviceIntent = new Intent(SimpleMathServiceDemo.this,MathService.class); 33. bindService(serviceIntent,mConnection,Context.BIND_AUTO_CREATE); 34. isBound = true; 35. } 36. } }); 39. . unbindButton.setOnClickListener(new View.OnClickListener(){ 40. @Override 41. public void onClick(View v) { 42. if(isBound){ 43. isBound = false; 44. unbindService(mConnection); 45. mathService = null; 46. } 47. } 48. });
本地服务 49. 50. computButton.setOnClickListener(new View.OnClickListener(){ 51. @Override 52. public void onClick(View v) { 53. if (mathService == null){ 54. labelView.setText("未绑定服务"); 55. return; 56. } 57. long a = Math.round(Math.random()*100); 58. long b = Math.round(Math.random()*100); 59. long result = mathService.Add(a, b); 60. String msg = String.valueOf(a)+" + "+String.valueOf(b)+ 61. " = "+String.valueOf(result); 62. labelView.setText(msg); 63. } 64. }); 65. } 66.
本地服务 67. private ServiceConnection mConnection = new ServiceConnection() { 68. @Override 69. public void onServiceConnected(ComponentName name, IBinder service) { 70. mathService = ((MathService.LocalBinder)service).getService(); 71. } 72. 73. @Override 74. public void onServiceDisconnected(ComponentName name) { 75. mathService = null; 76. } 77. }; 78. }
本地服务 多线程 任何耗时的处理过程都会降低用户界面的响应速度,甚至导致用户界面失去响应,当用户界面失去响应超过5秒钟,Android系统会允许用户强行关闭应用程序 较好的解决方法是将耗时的处理过程转移到子线程上,这样可以避免负责界面更新的主线程无法处理界面事件,从而避免用户界面长时间失去响应
本地服务 线程是独立的程序单元,多个线程可以并行工作 在多处理器系统中,每个中央处理器(CPU)单独运行 一个线程,因此线程是并行工作的 在单处理器系统中,处理器会给每个线程一小段时间, 在这个时间内线程是被执行的,然后处理器执行下一个 线程,这样就产生了线程并行运行的假象 无论线程是否真的并行工作,在宏观上可以认为子线程 是独立于主线程,且能与主线程并行工作的程序单元
本地服务 使用线程 实现Java的Runnable接口,并重载run()方法。在run()中 放置代码的主体部分 创建Thread对象,并将上面实现的Runnable对象作为参 数传递给Thread对象 调用start()方法启动线程 主线程 创建 Thread对象 Start() 实现Runnable的对象 创建子线程 Run() Interrupted() 子线程 耗时工作 通知终止 子线程
本地服务 实现Java的Runnable接口,并重载run()方法。在run()中 放置代码的主体部分 private Runnable backgroudWork = new Runnable(){ @Override public void run() { //过程代码 } }; 实现Runable接口 重载run()
本地服务 创建Thread对象,并将上面实现的Runnable对象作为参数传递给Thread对象 调用start()方法启动线程 第3个参数是线程的名称 调用start()方法启动线程 Runnable参数传递 private Thread workThread; workThread = new Thread(null, backgroudWork, "WorkThread"); 创建Thread对象 workThread.start();
本地服务 线程终止: 自动终止:线程在run()方法返回后,线程就自动终止了; 外部终止: Thread::stop():不推荐使用 Thread:: interrupt():通告线程准备终止,线程会释放它正在使用的资源,在完成所有的清理工作后自行关闭;推荐使用 interrupt()方法并不能直接终止线程,仅是改变了线程内部的一个布尔字段,run()方法能够检测到这个布尔字段,从而知道何时应该释放资源和终止线程 在run()方法的代码,一般通过Thread.interrupted()方法查询线程是否被中断 workThread.interrupt();
本地服务 下面的代码是以1秒为间隔循环检测断线程是否被中断 第4行代码使线程休眠1000毫秒 当线程在休眠过程中被中断,则会产生InterruptedException 在中断的线程上调用sleep()方法,同样会产生InterruptedException public void run() { while(!Thread.interrupted()){ //过程代码 Thread.sleep(1000); }
本地服务 Handler用途 创建 Thread对象 主线程 Start() 实现Runnable的对象 创建子线程 Run() Interrupted() 子线程 耗时工作 界面 通知终止 子线程 产生/更新 数据 Handler 返回
本地服务 Handler使用 View 监听器 主线程消息 队列 Handler 子线程 post Runnable 消息响应函数 SendMessage Message HandleMessage 界面事件响应
本地服务 使用Handler将子线程数据更新到用户界面(主线程) Handler允许将Runnable对象发送到线程的消息队列中, 每个Handler对象绑定到一个单独的线程和消息队列上 当用户建立一个新的Handler对象,通过post()方法将 Runnable对象从后台线程发送到GUI线程的消息队列中, 当Runnable对象通过消息队列后,这个Runnable对象将 被运行 创建Handler private static Handler handler = new Handler(); public static void UpdateGUI(double refreshDouble){ handler.post(RefreshLable); } private static Runnable RefreshLable = new Runnable(){ @Override public void run() { //过程代码 }; 公有、静态函数, 将被子线程调用 运行在子线程内部 Handler::post() 实现Runnable接口 Handler::post()参数 运行在主线程,用于更新界面
本地服务 第1行代码建立了一个静态的Handler对象,但这个对象是 私有的,因此外部代码并不能直接调用这个Handler对象 第3行UpdateGUI()是公有的界面更新函数,后台线程通 过调用该函数,将后台产生的数据refreshDouble传递到 UpdateGUI()函数内部,然后并直接调用post()方法,将 第6行的创建的Runnable对象传递给界面线程(主线程) 的消息队列中 第7行到第10行代码是Runnable对象中需要重载的run()函 数,一般将界面更新代码放置在run()函数中
本地服务 示例ThreadRandomServiceDemo使用线程持续产生随机数 点击“启动Service”后,将启动后台线程 后台线程每1秒钟产生一个0到1之间的随机数,并通过Handler将产生的随机数显示在用户界面上
本地服务 在ThreadRandomServiceDemo示例中,RandomService.java文件是描述Service的文件,用来创建线程、产生随机数和调用界面更新函数 RandomService.java文件的完整代码如下 package edu.hrbeu.ThreadRandomServiceDemo; import android.app.Service; import android.content.Intent; import android.os.IBinder; import android.widget.Toast; public class RandomService extends Service{ private Thread workThread;
本地服务 12. @Override 13. public void onCreate() { 14. super.onCreate(); 15. Toast.makeText(this, "(1) 调用onCreate()", 16. Toast.LENGTH_LONG).show(); 17. workThread = new Thread (null, backgroudWork, "WorkThread"); 18. } 19. 20. @Override 21. public void onStart(Intent intent, int startId) { 22. super.onStart(intent, startId); 23. Toast.makeText(this, "(2) 调用onStart()", 24. Toast.LENGTH_SHORT).show(); 25. if (!workThread.isAlive()){ 26. workThread.start(); 27. } 28. } 29.
本地服务 30. @Override 31. public void onDestroy() { 32. super.onDestroy(); 33. Toast.makeText(this, "(3) 调用onDestroy()", 34. Toast.LENGTH_SHORT).show(); 35. workThread.interrupt(); 36. } 37. 38. @Override 39. public IBinder onBind(Intent intent) { 40. return null; 41. } 42.
本地服务 43. private Runnable backgroudWork = new Runnable(){ 44. @Override 45. public void run() { 46. try { 47. while(!Thread.interrupted()){ 48. double randomDouble = Math.random(); 49. ThreadRandomServiceDemo.UpdateGUI(randomDouble); 50. Thread.sleep(1000); 51. } 52. } catch (InterruptedException e) { 53. e.printStackTrace(); 54. } 55. } 56. }; 57. }
本地服务 ThreadRandomServiceDemo.java文件是界面的Activity文件,封装Handler的界面更新函数就在这个文件中 ThreadRandomServiceDemo.java文件的完整代码 package edu.hrbeu.ThreadRandomServiceDemo; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.os.Handler; import android.view.View; import android.widget.Button; import android.widget.TextView;
本地服务 11. public class ThreadRandomServiceDemo extends Activity { 12. 13. private static Handler handler = new Handler(); 14. private static TextView labelView = null; private static double randomDouble ; 16. 17. public static void UpdateGUI(double refreshDouble){ randomDouble = refreshDouble; 19. handler.post(RefreshLable); 20. } 21. 22. private static Runnable RefreshLable = new Runnable(){ 23. @Override 24. public void run() { 25. labelView.setText(String.valueOf(randomDouble)); 26. } 27. }; 28.
本地服务 29. @Override 30. public void onCreate(Bundle savedInstanceState) { 31. super.onCreate(savedInstanceState); 32. setContentView(R.layout.main); 33. labelView = (TextView)findViewById(R.id.label); 34. Button startButton = (Button)findViewById(R.id.start); 35. Button stopButton = (Button)findViewById(R.id.stop); 36. final Intent serviceIntent = new Intent(this, RandomService.class); 37. 38. startButton.setOnClickListener(new Button.OnClickListener(){ 39. public void onClick(View view){ 40. startService(serviceIntent); 41. } 42. }); 43. 44. stopButton.setOnClickListener(new Button.OnClickListener(){ 45. public void onClick(View view){ 46. stopService(serviceIntent); 47. } 48. }); 49. } 50. }
远程服务 进程间通信(IPC) 在Android系统中,每个应用程序在各自的进程中运行 ,而且出于安全原因的考虑,这些进程之间彼此是隔离 的 Intent:承载数据,是一种简单、高效、且易于使用的IPC机制 远程服务:服务和调用者在不同的两个进程中,调用过程需要跨越进程才能实现
远程服务 进程一 进程二 调用者 服务提供者 Proxy IPC Parcel func( ) func( ) Binder::Transact() onTransact()
远程服务 实现远程服务的步骤 接口定义:使用AIDL语言定义跨进程服务的接口 接口实现:根据AIDL语言定义的接口,在具体的Service 类中实现接口中定义的方法和属性 接口使用:在需要调用跨进程服务的组件中,通过相同的 AIDL接口文件,调用跨进程服务
远程服务 服务创建与调用 AIDL(Android Interface Definition Language)是Android系统自定义的接口描述语言,可以简化进程间数据格式转换和数据交换的代码,通过定义Service内部的公共方法,允许调用者和Service在不同进程间相互传递数据 AIDL的IPC机制:基于接口的轻量级进程通信机制 AIDL语言的语法:与Java语言的接口定义非常相似, 唯一不同之处是:AIDL允许定义函数参数的传递方向 package edu.hrbeu.RemoteMathServiceDemo; interface IMathService { long Add(long a, long b); }
远程服务 AIDL支持三种方向:in、out和inout 在不标识参数的传递方向时,缺省认定所有函数的传递 方向为in 出于性能方面的考虑,不要在参数中标识不需要的传递 方向
远程服务 远程访问的创建和调用需要使用AIDL语言,一般分为以下几个过程 AIDL语言定义跨进程服务的接口 继承Service类,实现接口 绑定和使用跨进程服务
onServiceDisConnected 远程服务 使用接口 实现接口 定义接口 IMathService.aidl asInterface() MathService int Add() Acitvity onCreate() onStart() IMathService.java onDestory() bindService() Stub unBindService() onBind() asInterface() IMathService.Stub int Add() proxy RemoteBinder int Add() int Add() IMathService.Stub Intent ServiceConnection RemoteBinder onServiceConnected int Add() BIND_AUTO_CREATE onServiceDisConnected int Add() Andriod操作系统
远程服务 RemoteMathServiceDemo示例 在这个示例中,仅定义了MathService服务,可以为远 程调用者提供加法服务 使用AIDL语言定义跨进程服务的接口 首先使用AIDL语言定义的MathService的服务接口,文件名为IMathService.aidl IMathService接口仅包含一个add()方法,传入的参数是两个长型整数,返回值也是长型整数 package edu.hrbeu.RemoteMathServiceDemo; interface IMathService { long Add(long a, long b); }
远程服务 使用AIDL语言定义跨进程服务的接口 如果使用Eclipse编辑IMathService.aidl文件,当用户保存文件后,ADT会自动在/gen目录下生成IMathService.java文件 右图是IMathService.java文件结构
远程服务 使用AIDL语言定义跨进程服务的接口 IMathService.java文件根据IMathService.aidl的定义 生成了一个内部静态抽象类Stub,Stub继承了Binder类,并 实现ImathService接口 在Stub类中,还包含一个重要的静态类Proxy。 如果认为Stub类实现进程内服务调用, 那么Proxy类则是用来实现跨进程服务调用的,将Proxy作为 Stub的内部类完全是出于使用方便的目的
远程服务 使用AIDL语言定义跨进程服务的接口 Stub类和Proxy类关系图
远程服务 IMathService.java的完整代码 package edu.hrbeu.RemoteMathServiceDemo; import java.lang.String; import android.os.RemoteException; import android.os.IBinder; import android.os.IInterface; import android.os.Binder; import android.os.Parcel; public interface IMathService extends android.os.IInterface{ /** Local-side IPC implementation stub class. */ public static abstract class Stub extends android.os.Binder implements edu.hrbeu.RemoteMathServiceDemo.IMathService{ private static final java.lang.String DESCRIPTOR = "edu.hrbeu.RemoteMathServiceDemo.IMathService"; /** Construct the stub at attach it to the interface. */ public Stub(){ this.attachInterface(this, DESCRIPTOR); }
远程服务 16. /** 17. * Cast an IBinder object into an IMathService interface, 18. * generating a proxy if needed. 19. */ 20. public static edu.hrbeu.RemoteMathServiceDemo.IMathService asInterface(android.os.IBinder obj){ 21. if ((obj==null)) { 22. return null; 23. } 24. android.os.IInterface iin = (android.os.IInterface) obj.queryLocalInterface(DESCRIPTOR); 25. if (((iin!=null)&&(iin instanceof edu.hrbeu.RemoteMathServiceDemo.IMathService))) { 26. return ((edu.hrbeu.RemoteMathServiceDemo.IMathService)iin); 27. } 28. return new edu.hrbeu.RemoteMathServiceDemo.IMathService.Stub.Proxy(obj); 29. } 30. public android.os.IBinder asBinder(){ 31. return this; 32. }
远程服务 33. public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException{ 34. switch (code) { 35. case INTERFACE_TRANSACTION: 36. { 37. reply.writeString(DESCRIPTOR); 38. return true; 39. } 40. case TRANSACTION_Add: 41. { 42. data.enforceInterface(DESCRIPTOR); 43. long _arg0; 44. _arg0 = data.readLong(); 45. long _arg1; 46. _arg1 = data.readLong(); 47. long _result = this.Add(_arg0, _arg1); 48. reply.writeNoException(); reply.writeLong(_result); 50. return true;
远程服务 51. } 52. } 53. return super.onTransact(code, data, reply, flags); 54. } 55. private static class Proxy implements edu.hrbeu.RemoteMathServiceDemo.IMathService{ 56. private android.os.IBinder mRemote; 57. Proxy(android.os.IBinder remote){ 58. mRemote = remote; 59. } 60. public android.os.IBinder asBinder(){ 61. return mRemote; 62. } 63. public java.lang.String getInterfaceDescriptor(){ 64. return DESCRIPTOR; 65. } 66. public long Add(long a, long b) throws android.os.RemoteException{ android.os.Parcel _data = android.os.Parcel.obtain(); 68. android.os.Parcel _reply = android.os.Parcel.obtain(); 69. long _result;
远程服务 70. try { 71. _data.writeInterfaceToken(DESCRIPTOR); 72. _data.writeLong(a); 73. _data.writeLong(b); 74. mRemote.transact(Stub.TRANSACTION_Add, _data, _reply, 0); 75. _reply.readException(); 76. _result = _reply.readLong(); 77. } 78. finally { 79. _reply.recycle(); 80. _data.recycle(); 81. } 82. return _result; 83. } 84. } 85. static final int TRANSACTION_Add = (IBinder.FIRST_CALL_TRANSACTION + 0); 86. } 87. public long Add(long a, long b) throws android.os.RemoteException; 88. }
远程服务 第8行代码是IMathService继承了android.os.IInterface,这是所有使用AIDL建立的接口都必须继承的基类接口,这个基类接口中定义了asBinder()方法,用来获取Binder对象 在代码的第30行到第32行,实现了android.os.IInterface接口所定义的asBinder()方法 在IMathService中,绝大多数的代码是用来实现Stub这个抽象类的。每个远程接口都包Stub类,因为是内部类,所有并不会产生命名的冲突 asInterface(IBinder)是Stub内部的跨进程服务接口,调用者可以通过该方法获取到跨进程服务的对象
远程服务 仔细观察asInterface(IBinder)实现方法,首先判断IBinder对象obj是否为null(第21行),如果是则立即返回 第24行代码是使用DESCRIPTOR构造android.os.IInterface对象,并判断android.os.IInterface对象是否为进程内服务 如果是进程内服务,则无需进程间通信,返回android.os.IInterface对象(第26行) 如果不是进程内服务,则构造并返回Proxy对象(第28行) 第66行代码是Proxy内部包含与IMathService.aidl相同签名的函数 第71~76行代码是在该函数中以一定的顺序将所有参数写入Parcel对象,以供Stub内部的onTransact()方法能够正确获取到参数
远程服务 当数据以Parcel对象的形式传递到跨进程服务的内部时,onTransact()方法(第33行)将从Parcel对象中逐一的读取每个参数,然后调用Service内部制定的方法,并再将结果写入另一个Parcel对象,准备将这个Parcel对象返回给远程的调用者 Parcel是Android系统中应用程序进程间数据传递的容器 ,能够在两个进程中完成数据的打包和拆包的工作,但 Parcel不同于通用意义上的序列化,Parcel的设计目的是 用于高性能IPC传输,因此不能够将Parcel对象保存在任 何持久存储设备上
远程服务 通过继承Service类实现跨进程服务 实现跨进程服务需要建立一个继承android.app.Service的 类,并在该类中通过onBind()方法返回IBinder对象,调用 者使用返回的IBinder对象就可以访问跨进程服务 IBinder对象的建立通过使用IMathService.java内部的 Stub类实现,并逐一实现在IMathService.aidl接口文件定 义的函数
远程服务 在RemoteMathServiceDemo示例中,跨进程服务的实现类是MathService.java package edu.hrbeu.RemoteMathServiceDemo; import android.app.Service; import android.content.Intent; import android.os.IBinder; import android.widget.Toast; public class MathService extends Service{ private final IMathService.Stub mBinder = new IMathService.Stub() { public long Add(long a, long b) { return a + b; 12. } 13. };
远程服务 通过继承Service类实现跨进程服务 14. @Override 15. public IBinder onBind(Intent intent) { 16. Toast.makeText(this, "远程绑定:MathService", 17. Toast.LENGTH_SHORT).show(); 18. return mBinder; 19. } 20. @Override 21. public boolean onUnbind (Intent intent){ 22. Toast.makeText(this, "取消远程绑定:MathService", 23. Toast.LENGTH_SHORT).show(); 24. return false; 25. } 26. }
远程服务 第8行代码表明MathService继承于android.app.Service 第9行代码建立IMathService.Stub的对象mBinder 第10行代码实现了AIDL文件定义的跨进程服务接口 第18行代码在onBind()方法中,将mBinder返回给远程调用者 第16行和第22行代码分别是在绑定和取消绑定时,为用户产生的提示信息
远程服务 RemoteMathServiceDemo示例的文件结构如图 示例中只有跨进程服务的类文件MathService.java和接口文件IMathService.aidl,没有任何用于启动时显示用户界面的Activity文件 服务 AIDL接口 JAVA接口
远程服务 通过继承Service类实现跨进程服务 在调试RemoteMathServiceDemo示例时,模拟器的屏幕 上不会出现用户界面,但在控制台会有“没有找到用于启 动的Activity,仅将应用程序同步到设备上”的提示信息 ,这些信息表明apk文件已经上传到模拟器中 提示信息如图
远程服务 使用File Explorer查看模拟器的文件系统,可以进一步确认编译好的apk文件是否正确上传到模拟器中 如果能在/data/app/下找到edu.hrbeu.RemoteMathServiceDemo.apk文件,说明提供跨进程服务的apk文件已经正确上传 如果RemoteMathServiceDemo示例无法在Android模拟器的程序启动栏中找到,只能够通过其他应用程序调用该示例中的跨进程服务
远程服务 下图表示edu.hrbeu.RemoteMathServiceDemo.apk文件的保存位置
远程服务 RemoteMathServiceDemo是一个没有Activity的示例,在AndroidManifest.xml文件中,在<application>标签下只有一个<service>标签 AndroidManifest.xml文件的完整代码如下 <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="edu.hrbeu.RemoteMathServiceDemo" android:versionCode="1" android:versionName="1.0"> 6. <application android:icon="@drawable/icon" android:label= "@string/app_name"> 7. <service android:name=".MathService" 8. android:process=":remote"> 9. <intent-filter> 10. <action android:name= "edu.hrbeu.RemoteMathServiceDemo.MathService" /> 11. </intent-filter> 12. </service> 13. </application> 14. <uses-sdk android:minSdkVersion="3" /> 15. </manifest>
远程服务 注意第10行代码,edu.hrbeu.RemoteMathServiceDemo.MathService是远程调用MathService的标识,在调用者段使用Intent.setAction()函数将标识加入Intent中,然后隐式启动或绑定服务
远程服务 绑定和使用跨进程服务 下图是RemoteMathCallerDemo的界面 用户可以绑定跨进程服务,也可以取消服务绑定 在绑定跨进程服务后,可以调用RemoteMathServiceDemo中的MathService服务进行加法运算,运算的输入由RemoteMathCallerDemo随机产生,运算的输入和结果显示在屏幕的上方
远程服务 应用程序在调用跨进程服务时,应用程序与跨进程服务应 具有相同的Proxy类和签名函数,这样才能够使数据在调 用者处打包后,可以在远程访问端正确拆包,反之亦然 从实践角度来讲,调用者需要使用与跨进程服务端相同的 AIDL文件 在RemoteMathCallerDemo示例,在 edu.hrbeu.RemoteMathServiceDemo包下,引入与 RemoteMathServiceDemo相同的AIDL文件 IMathService.aidl,同时在/gen目录下会自动产生相同的 IMathService.java文件
远程服务 下图是RemoteMathServiceDemo的文件结构
远程服务 RemoteMathCallerDemo.java是Activity的文件,跨进程服务的绑定和使用方法与上节的进程内服务绑定示例SimpleMathServiceDemo相似 不同之处主要包括以下两个方面 第1行代码使用IMathService声明跨进程服务对象 第6行代码通过IMathService.Stub的asInterface()方法实现获取服务对象
远程服务 private IMathService mathService; private ServiceConnection mConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { mathService = IMathService.Stub.asInterface(service); } public void onServiceDisconnected(ComponentName name){ mathService = null; };
远程服务 绑定服务时,首先通过setAction()方法声明服务标识,然 后调用bindService()绑定服务 服务标识必须与跨进程服务在AndroidManifest.xml文件中 声明的服务标识完全相同。因此本示例的服务标识为 edu.hrbeu.RemoteMathServiceDemo.MathService,与 跨进程服务示例RemoteMathServiceDemo在 AndroidManifest.xml文件声明的服务标识一致 final Intent serviceIntent = new Intent(); serviceIntent.setAction("edu.hrbeu.RemoteMathServiceDemo.MathService"); bindService(serviceIntent,mConnection,Context.BIND_AUTO_CREATE);
远程服务 下面是RemoteMathCallerDemo.java文件的完整代码 package edu.hrbeu.RemoteMathCallerDemo; import edu.hrbeu.RemoteMathServiceDemo.IMathService; import android.app.Activity; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.os.Bundle; import android.os.IBinder; import android.os.RemoteException; import android.view.View; import android.widget.Button; import android.widget.TextView;
远程服务 16. 17. public class RemoteMathCallerDemo extends Activity { 18. private IMathService mathService; 18. 20. private ServiceConnection mConnection = new ServiceConnection() { 21. @Override 22. public void onServiceConnected(ComponentName name, IBinder service) { 23. mathService = IMathService.Stub.asInterface(service); } @Override public void onServiceDisconnected(ComponentName name) { mathService = null; }; 30.
远程服务 private boolean isBound = false; TextView labelView; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); labelView = (TextView)findViewById(R.id.label); Button bindButton = (Button)findViewById(R.id.bind); Button unbindButton = (Button)findViewById(R.id.unbind); Button computButton = (Button)findViewById(R.id.compute_add); bindButton.setOnClickListener(new View.OnClickListener(){
远程服务 public void onClick(View v) { if(!isBound){ final Intent serviceIntent = new Intent(); serviceIntent.setAction("edu.hrbeu.RemoteMathServiceDemo.MathService"); bindService(serviceIntent,mConnection,Context.BIND_AUTO_CREATE); isBound = true; } }); unbindButton.setOnClickListener(new View.OnClickListener(){ @Override if(isBound){ isBound = false;
远程服务 60. unbindService(mConnection); 61. mathService = null; } }); computButton.setOnClickListener(new View.OnClickListener(){ @Override public void onClick(View v) { if (mathService == null){ labelView.setText("未绑定跨进程服务"); return; long a = Math.round(Math.random()*100); long b = Math.round(Math.random()*100); long result = 0;
远程服务 try { result = mathService.Add(a, b); } catch (RemoteException e) { e.printStackTrace(); } String msg = String.valueOf(a)+" + "+String.valueOf(b)+ " = "+String.valueOf(result); labelView.setText(msg); });
远程服务 数据传递 在Android系统中,进程间传递的数据包括: 所有数据都必须是“可打包”的,才能够穿越进程边界 Java语言支持的基本数据类型 用户自定义的数据类型 所有数据都必须是“可打包”的,才能够穿越进程边界 Java语言的基本数据类型的打包过程是自动完成 对于自定义的数据类型,则需要实现Parcelable接口, 使自定义的数据类型能够转换为系统级原语保存在 Parcel对象中,穿越进程边界后可再转换为初始格式
远程服务 AIDL支持的数据类型表 类型 说明 需要引入 Java语言的基本类型 包括boolean、byte、short、int、float和double等 否 String java.lang.String CharSequence java.lang.CharSequence List 其中所有的元素都必须是AIDL支持的数据类型 Map 其中所有的键和元素都必须是AIDL支持的数据类型 其他AIDL接口 任何其他使用AIDL语言生成的接口类型 是 Parcelable对象 实现Parcelable接口的对象
远程服务 AllResult.aidl Acitvity MathService AllResult.java IPC 获取结果 计算结果 parcelable AllResult Acitvity MathService Proxy AllResult.java IPC int AddResult int SubResult int AddResult int SubResult ComputeAll() 获取结果 计算结果 AllResult(…) 构造对象 AllResult(Parcel parcel) AllResult writetoPracel( ) CREATOR Andriod操作系统
远程服务 下面以ParcelMathServiceDemo示例为参考,说明如何在跨进程服务中使用自定义数据类型 这个示例是RemoteMathServiceDemo示例的延续 定义MathService服务,为远程调用者提供加法服务,没有启动界面,因此在模拟器的调试过程与RemoteMathServiceDemo示例相同 不同之处在于MathService服务增加了“全运算”功能 在接收到输入参数后,将向调用者返回一个包含“加、减、乘、除”全部运算结果的对象。这个对象是一个自定义的类,为了能够使其他AIDL文件可使用这个自定义类,需要使用AIDL语言声明这个类
远程服务 首先建立AllResult.aidl文件,声明AllResult类 在第2行代码中使用parcelable声明自定义类,这样其他的AIDL文件就可以使用这个自定义的类 package edu.hrbeu.ParcelMathServiceDemo; parcelable AllResult;
远程服务 下图是ParcelMathServiceDemo的文件结构
远程服务 下面是IMathService.aidl文件的代码 第2行代码中引入了edu.hrbeu.ParcelMathServiceDemo.AllResult,才能够使用自定义数据结构AllResult 第6行代码为全运算增加了新的函数ComputeAll(),该函数的返回值就是在AllResult.aidl文件中定义AllResult package edu.hrbeu.ParcelMathServiceDemo; import edu.hrbeu.ParcelMathServiceDemo.AllResult; interface IMathService { long Add(long a, long b); AllResult ComputeAll(long a, long b); }
远程服务 构造AllResult类。AllResult类除了基本的构造函数以外,还需要有以Parcel对象为输入的构造函数,并且需要重载打包函数writeToParcel() AllResult.java文件的完整代码如下 package edu.hrbeu.ParcelMathServiceDemo; import android.os.Parcel; import android.os.Parcelable; public class AllResult implements Parcelable { public long AddResult; public long SubResult; public long MulResult; public double DivResult;
远程服务 12. public AllResult(long addRusult, long subResult, long mulResult, double divResult){ AddResult = addRusult; SubResult = subResult; MulResult = mulResult; DivResult = divResult; } public AllResult(Parcel parcel) { AddResult = parcel.readLong(); SubResult = parcel.readLong(); MulResult = parcel.readLong(); DivResult = parcel.readDouble(); @Override public int describeContents() { return 0;
远程服务 30. 31. @Override 32. public void writeToParcel(Parcel dest, int flags) { dest.writeLong(AddResult);34. dest.writeLong(SubResult); dest.writeLong(MulResult); dest.writeDouble(DivResult); } public static final Parcelable.Creator<AllResult> CREATOR = new Parcelable.Creator<AllResult>(){ public AllResult createFromParcel(Parcel parcel){ return new AllResult(parcel); 44. public AllResult[] newArray(int size){ 45. return new AllResult[size]; 46. } 47. }; 48. }
远程服务 第6行代码说明了AllResult类继承于Parcelable 第7行到第10行代码用来保存全运算的运算结果 第19行是类的构造函数,支持通过Parcel对象实例化AllResult 第20行到第23行代码是构造函数的读取顺序 第32行代码的writeToParcel()是“打包”函数,将AllResult类内部的数据,按照特定的顺序写入Parcel对象,写入的顺序必须与构造函数的读取顺序一致 第39行实现了静态公共字段Creator,用来使用Parcel对象构造AllResult对象
远程服务 在MathService.java文件中,增加了用来进行全运算的ComputAll()函数,并将运算结果保存在AllResult对象中 ComputAll()函数实现代码如下 @Override public AllResult ComputeAll(long a, long b) throws RemoteException { long addRusult = a + b; long subResult = a - b; long mulResult = a * b; double divResult = (double) a / (double)b; AllResult allResult = new AllResult(addRusult, subResult, mulResult, divResult); return allResult; }
远程服务 ParcelMathCallerDemo示例是ParcelMathServiceDemo示例中MathService服务的调用者 其中AllResult.aidl、AllResult.java和IMathService.aidl文件务必与ParcelMathServiceDemo示例的三个文件完全一致,否则会出现错误
远程服务 下图是ParcelMathCallerDemo用户界面 原来的“加法运算”按钮改为了“全运算”按钮,运算结果显示在界面的上方
远程服务 下面是ParcelMathCallerDemo.java文件与RemoteMathCallerDemo示例中RemoteMathCallerDemo.java文件不同的一段代码 定义了“全运算”按钮的监听函数 随机产生输入值 调用跨进程服务 获取运算结果,并将运算结果显示在用户界面上
远程服务 computAllButton.setOnClickListener(new View.OnClickListener(){ @Override public void onClick(View v) { if (mathService == null){ labelView.setText("未绑定跨进程服务"); return; } long a = Math.round(Math.random()*100); long b = Math.round(Math.random()*100); AllResult result = null; try { result = mathService.ComputeAll(a, b); } catch (RemoteException e) { e.printStackTrace();
远程服务 String msg = ""; if (result != null){ msg += String.valueOf(a)+" + "+String.valueOf(b)+" = "+String.valueOf(result.AddResult)+"\n"; msg += String.valueOf(a)+" - "+String.valueOf(b)+" = "+String.valueOf(result.SubResult)+"\n"; msg += String.valueOf(a)+" * "+String.valueOf(b)+" = "+String.valueOf(result.MulResult)+"\n"; msg += String.valueOf(a)+" / "+String.valueOf(b)+" = "+String.valueOf(result.DivResult); } labelView.setText(msg); });
习题: 1.简述Service的基本原理和用途; 2.编程建立一个简单的进程内服务,实现比较两个整数大小的功能。服务提供Int Compare(Int, Int)函数,输入两个整数,输出较大的整数。 3.使用AIDL语言实现功能与第2题相同的跨进程服务。