关于Service和Activity的关系,以及通信方式,你知道吗?

文章出处:http://linkinmama-gmail-com.iteye.com/blog/1569039

http://www.poemcode.net/2010/05/aidl-ipc/

昨天被问到Android进程间的通信方式,把我给问傻了,后来我查了下资料,感觉基本清晰了,下一步就是用实践来锻炼了,其中文章是整理前辈整理的,供大家学习。


首先先来下知识预热吧。

Activity与Service是否处于同一进程? 

  一般来说:同一个包内的activity和service,如果service没有设定属性android:process=":remote"的话,service会和activity跑在同一个进程中,由于一个进程只有一个UI线程,所以,service和acitivity

就是在同一个线程里面的。android:process=":remote"值得注意他的用法!!!如果Activity想访问service中的对象或方法,如果service设定属性android:process=":remote",那么就是跨进程访问,跨

进程访问容易出现意想不到的问题,还是慎重给service设定属性android:process=":remote"

Service 的两大功能是什么?怎样实现?

android系统中的Service主要有两个作用:后台运行和跨进程通讯。

情况1:当Acitivity和Service处于同一个Application和进程时,通过继承Binder类来实现。

Service和Activity的连接可以用ServiceConnection来实现,需要实现一个新的ServiceConnection,重写onServiceConnected和onServiceDisconnected方法。执行绑定,调用bindService方法,传入一

个选择了要绑定的Service的Intent(显式或隐式)和一个你实现了的ServiceConnection实例。一旦连接建立,你就能通Service的接口onBind()得到serviceBinder实例进而得到Service的实例引用。一旦

Service对象找到,就能得到它的公共方法和属性。但这种方式,一定要在同一个进程和同一个Application里。

情况2:跨进程通讯,使用AIDL;

默认情况下,一个应用不管有多少个 Activity、Service 或其他组件,它们都是运行在一个进程上,但是我们可以安排 Service 运行一个新的进程上,但是不同进程之间应该如何通信呢?当需要在不同的进程之间传递对象时,应该怎么做呢?AIDL(Android Interface Definition Language) 便是解决这一问题的钥匙。

使用 AIDL 并不是难事,但是比较繁琐,并且一不小心容易出错。好在 Android Dev Guide 的 Designing a Remote Interface Using AIDL 对这个问题讲解非常详细,再结合 Android APIDemo 中的 Remote Service Binding 给出了的示例,这都给了开发者提供了非常到位的帮助。以下内容就是我结合二者,整理出来的笔记,力求真理,但能力有限,差错难免,请读者坚持自己的判断力,本文只供参考,且不可尽信。

使用 AIDL 实现 IPC

面对问题,应统揽全局,归纳阶段,划定步骤,共得出四大步,分列如下:

创建.aidl 文件
在这个文件里定义 method 和 field
把 .aidl 文件加入到 makefile 中去
如果使用 Eclipse 则 ADT 会帮你管理
实现接口方法
AIDL 编译器会根据接口生成一个用 Java 语言编写的interface,这个 interface 有一个抽象的内部类,名字为  Stub,你必须创建一个类,继承于它,并且实现 .adil 文件中所声明的方法
公开接口给客户端
如果创建的是 service,则应该继承自  Service,并且重载 Service.onBind() 返回实现接口的类的实例

这四个步骤在 Remote Service Binding 中均有所呈现,以下分开阐述。Remote Service Binding 共包含有两个 .java 文件,三个 .aidl 文件,物理结构比较简单,但是逻辑结构就不那么简单,以下用 Class Diagram 来展示其中的关系。

Remote Service Binding

Remote Service Binding

1、创建.aidl 文件

AIDL 语法简单,用来声明接口,其中的方法接收参数和返回值,但是参数和返回值的类型是有约束的,且有些类型是需要 import,另外一些则无需这样做。

AIDL 支持的数据类型划分为四类,第一类是 Java 编程语言中的基本类型,第二类包括 String、List、Map 和 CharSequence,第三类是其他 AIDL 生成的 interface,第四类是实现了 Parcelable protocol 的自定义类。

其中,除了第一类外,其他三类在使用时均需要特别小心。

使用第二类时,首先需要明白这些类不需要 import,是内嵌的。其次注意在使用 List 和 Map 此二者容器类时,需注意其元素必须得是 AIDL 支持的数据类型,List 可支持泛型,但是 Map 不支持,同时另外一端负责接收的具体的类里则必须是 ArrayList 和 HashMap

使用第三、四类时,需要留意它们都是需要 import 的,但是前者传递时,传递的是 reference,而后者则是 value。

在创建 .aidl 文件的过程中,应该注意一旦 method 有参数,则需注意在前面加上 in, out 或 inout,它们被称为 directional tag,但是对于基本类型的参数,默认就是 in,并且不会是其他值。

Remote Service Binding 共包括了三个 .aidl 文件,分别是IRemoteService.aidl、IRemoteServiceCallback.aidl、ISecondary.aidl,通过它们可以看到如何使用第一类和第三类的数据类型,稀罕的是,看不到第二类、第四类数据类型的使用,也没有看到 directional tag。

2、实现 Interface

AIDL 为你生成一个 interface 文件,文件名称和 .aidl 文件相同。如果使用 Eclipse 插件,则 AIDL 会在构建过程中自动运行,如果不使用插件,则需要先使用 AIDL。

生成的 interface 会包含一个抽象内部类 Stub,它声明了在 .aidl 文件里的所有方法。Stub 也定义了一些帮助方法,比较常用的有 asInterface(),其接收一个 IBinder 作为参数,并且返回一个 interface 的实例用来调用IPC方法。

要实现 interface,需要继承 Stub,实现其方法,这在 RemoteService 和 RemoteServiceBinding 都可以找到相关代码。

这个环节是重中之重,需要特别小心的有两点,其一是抛出的所有异常均不会发给调用者;其二是IPC调用是同步的,这意味IPC服务一旦花费较长时间完成的话,就会引起ANR,应该将这样的操作放在单独的线程里。

3、向客户端公开 Interface

独乐乐不如众乐乐,需要将服务公开出去,要达成这个目的,须得创建一个 Service 的子类,并且实现Service.onBind(Intent),通过这个方法将实现了接口的类的实例返回回来。通过查看 RemoteService 一目了然。

4、使用Parcelables传值

前文中提到 Remote Servcie Binding 没有使用第四类数据类型作为参数,这是示例的不足,要想让一个类变成第四类,需要遵照以下步骤:

  1. 引入 Parcelable 接口
  2. 实现 writeToParcel(Parcel out)
  3. 增加一个静态的field,其实现 Parcelable.Creator 接口
  4. 创建一个 .aidl 文件,用以声明你的 parcelables 类

在 Designing a Remote Interface Using AIDL 中,类 Rect 是一个不错的示例,弥补了 Remote Service Binding 的不足。

二、调用 IPC 方法

万事具备,只欠东风,IPC 备妥,只待调用。在 Remote Service Binding 中,RemoteServiceBinding 正是 IPC 的调用者,

既然要使用接口,那就先声明 interface 类型的变量,

实现 ServiceConnection,在 onServiceConnected(ComponentName className, IBinder service) 中完成对 mService 和 mSecondaryService 的赋值。

接着别忘了调用 Context.bindService(),完成任务以后,调用 Context.unbindService()。如果在 connection 中断的情况下,调用 IPC 方法,你会遇到 DeadObjectException,这是 remote method 能抛出的唯一异常。


可能干着说有点累,上例子:

下面用一个客户端Activity操作服务端Service播放音乐的实例演示AIDL的使用。
开发工具: eclipse 3.7(indigo)+ android sdk 4.1+ adt 20.0.2
服务端代码结构(左)                                 客户端代码结构(右)

  被标记的就是需要动手的。

服务端
  新建一个android application project,命名为PlayerServer。 在res下的raw文件夹里面放入一个音乐文件,我这里放入的是Delta Goodrem的《Lost Without You》片段。如果不存在raw这个文件夹就自己新建一个,命名为raw。这个文件夹在raw文件夹下,与layout文件夹平级。raw中的文件遵守标识符的命名规则,不要出现中文和空格,多个单词可以用下划线连接。
  新建一个IRemoteServiice.aidl 文件,加入如下代码:
	 package pandafang.demo.playerserver;
	  interface IRemoteService {
		  void play();
		  void stop();
	}

可见aidl文件的代码跟java的interface一样,但是aidl中不能加public等修饰符。Ctrl + S 保存后 ADT 会根据这个IRemoteService.aidl文件自动生成IRemoteService.java文件。如同R.java文件一样在“gen/包名”下,代码是自动生成的,不要手动修改。
  接下来就是bound service的知识了。IRemoteService.java 中有一个Stub静态抽象类extends Binder implements IRemoteService。自己动手写一个PlayerService 用来播放音乐,播放音乐需要使用android.media.MediaPlayer类。代码如下:
package pandafang.demo.playerserver;

import java.io.FileDescriptor;
import java.io.IOException;
import android.app.Service;
import android.content.Intent;
import android.media.MediaPlayer;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;

/**
 * 播放音乐的服务
 * @author Panda Fang
 * @date 2012-10-22 10:15:33
 */
public class PlayerService extends Service {
    
    public static final String TAG = "PlayerService";
    
    private MediaPlayer mplayer;
    
    @Override
    public IBinder onBind(Intent intent) {
        Log.i(TAG,"service onbind");
        if(mplayer==null){
            // 方法一说明
            // 此方法实例化播放器的同时指定音乐数据源 ,若用此方法在,mplayer.start() 之前不需再调用mplayer.prepare() 
            // 官方文档有说明 :On success, prepare() will already have been called and must not be called again.
            // 译文:一旦create成功,prepare已被调用,勿再调用 。查看源代码可知create方法内部已经调用prepare方法。
            // 方法一开始
            // mplayer = MediaPlayer.create(this, R.raw.lost);
            // 方法一结束
            
            // 方法二说明
            // 若用此方法,在mplayer.start() 之前需要调用mplayer.prepare() 
            // 方法二开始
            mplayer = new MediaPlayer();
            try {
                FileDescriptor fd = getResources().openRawResourceFd(R.raw.lost).getFileDescriptor(); // 获取音乐数据源
                mplayer.setDataSource(fd); // 设置数据源
                mplayer.setLooping(true); // 设为循环播放
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            // 方法二结束
            Log.i(TAG,"player created");
        }
        return mBinder;
    }
    
    // 实现aidl文件中定义的接口
    private IBinder mBinder = new IRemoteService.Stub() {
        
        @Override
        public void stop() throws RemoteException {
            try {
                if (mplayer.isPlaying()) {
                    mplayer.stop();
                }
            } catch (Exception e) {
                // TODO: handle exception
                e.printStackTrace();
            }
        }
        
        @Override
        public void play() throws RemoteException {
            try {
                if (mplayer.isPlaying()) {
                    return;
                }
                // start之前需要prepare。
                // 如果前面实例化mplayer时使用方法一,则第一次play的时候直接start,不用prepare。
                // 但是stop一次之后,再次play就需要在start之前prepare了。
                // 前面使用方法二 这里就简便了, 不用判断各种状况
                mplayer.prepare();
                mplayer.start();
            } catch (Exception e) {
                // TODO: handle exception
                e.printStackTrace();
            }
        }
    };

    @Override
    public boolean onUnbind(Intent intent) {
        if (mplayer != null) {
            mplayer.release();
        }
        Log.i(TAG,"service onUnbind");
        return super.onUnbind(intent);
    }

}

服务编写好以后,按照惯例在AndroidManifest.xml中加入声明,代码如下:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="pandafang.demo.playerserver"
     android:versionCode="1"
     android:versionName="1.0" >
 
     <uses-sdk
         android:minSdkVersion="8"
         android:targetSdkVersion="15" />
 
     <application
         android:icon="@drawable/ic_launcher"
         android:label="@string/app_name"
         android:theme="@style/AppTheme" >
         <service android:name=".PlayerService" android:process=":remote">
             <intent-filter >
                 <action android:name="com.example.playerserver.PlayerService"/>
             </intent-filter>
          </service>
     </application>
 
 </manifest>

  需要加入的只是<service>...</service>那段,要注意的是 android:process=":remote" 和 intent-filter 。

  运行服务端到设备上,准备给客户端调用

客户端

  新建一个android application project,命名为PlayerClient。将服务端放有aidl文件的包直接copy到客户端src目录下,保留包中的aidl文件,其他删除。

  编辑 layout 下的 activity_main.xml 布局文件,加入两个按钮,代码如下:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:tools="http://schemas.android.com/tools"
     android:layout_width="match_parent"
     android:layout_height="match_parent" 
     android:gravity="center_vertical">
 
     <Button
         android:id="@+id/button1"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:text="play" />
     
      <Button
         android:id="@+id/button2"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:layout_below="@id/button1"
         android:layout_marginTop="20dp"
         android:text="stop" />
 
 </RelativeLayout>

编写MainActivity.java 代码如下:

package pandafang.demo.playerclient;
 
 import pandafang.demo.playerserver.IRemoteService;
 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.util.Log;
 import android.view.Menu;
 import android.view.View;
 import android.view.View.OnClickListener;
 import android.widget.Button;
 
 /**
  * 客户端控制界面
  * @author Panda Fang
  * @date 2012-10-22 10:36:44
  */
 public class MainActivity extends Activity {
     
     public static final String TAG = "MainActivity";
     
     // 服务端 AndroidManifest.xml中的intent-filter action声明的字符串
     public static final String ACTION = "com.example.playerserver.PlayerService";
     
     private Button playbtn, stopbtn;
     
     private IRemoteService mService;
     
     private boolean isBinded = false;
     
     private ServiceConnection conn = new ServiceConnection() {
         
         @Override
         public void onServiceDisconnected(ComponentName name) {
             isBinded = false;
             mService = null;
         }
         
         @Override
         public void onServiceConnected(ComponentName name, IBinder service) {
             mService = IRemoteService.Stub.asInterface(service);
             isBinded = true;
         }
     };
 
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         setContentView(R.layout.activity_main);
         
         doBind();
         initViews();
     }
     
     private void initViews() {
         playbtn = (Button) findViewById(R.id.button1);
         stopbtn = (Button) findViewById(R.id.button2);
         playbtn.setOnClickListener(clickListener);
         stopbtn.setOnClickListener(clickListener);
     }
     @Override
     public boolean onCreateOptionsMenu(Menu menu) {
         getMenuInflater().inflate(R.menu.activity_main, menu);
         return true;
     }
     
     @Override
     protected void onDestroy() {
         doUnbind();
         super.onDestroy();
     }
     
     public void doBind() {
         Intent intent = new Intent(ACTION);
         bindService(intent, conn, Context.BIND_AUTO_CREATE);
     }
     
     public void doUnbind() {
         if (isBinded) {
             unbindService(conn);
             mService = null;
             isBinded = false;
         }
         
     }
     private OnClickListener clickListener = new OnClickListener() {
         
         @Override
         public void onClick(View v) {
             if (v.getId() == playbtn.getId()) {
                 // play
                 Log.i(TAG,"play button clicked");
                 try {
                     mService.play();
                 } catch (RemoteException e) {
                     // TODO Auto-generated catch block
                     e.printStackTrace();
                 }
             } else {
                 // stop
                 Log.i(TAG,"stop button clicked");
                 try {
                     mService.stop();
                 } catch (RemoteException e) {
                     // TODO Auto-generated catch block
                     e.printStackTrace();
                 }
             }
         }
     };
 
 }

MainActivity是根据向导自动生成的,不需要在AndroidManifest.xml中注册声明了

  运行客户端到设备,按下按钮可以播放/停止 效果如图








  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值