专栏名称: 安卓开发精选
伯乐在线旗下账号,分享安卓应用相关内容,包括:安卓应用开发、设计和动态等。
目录
相关文章推荐
开发者全社区  ·  WC!阿里P7总包超150w ·  昨天  
开发者全社区  ·  英区金融妲己 ·  2 天前  
开发者全社区  ·  离谱瓜!约会8个女同事的大厂男 ·  2 天前  
开发者全社区  ·  UCL色魔博士被抓 ·  3 天前  
开发者全社区  ·  人大发TT了 ·  3 天前  
51好读  ›  专栏  ›  安卓开发精选

学习 AIDL,这一篇文章就够了(一)下

安卓开发精选  · 公众号  · android  · 2016-09-26 08:18

正文

(点击 上方公众号 ,可快速关注)


来源:伯乐在线专栏作者 - lypeer

链接:http://android.jobbole.com/84580/

点击 → 了解如何加入专栏作者


接上文


4.1.2,插件生成


我不是很清楚 Eclipse 或者较低版本的 as 上会不会像 as 2.1.2 这样帮我们在实现 Parcelable 接口的过程中做如此多的操作,但是就算不会,我们还有其他的招数——通过插件来帮我们实现 Parcelable 接口。


具体的实现方式和实现过程大家可以参见这篇文章:告别手写parcelable


4.2,书写AIDL文件


首先我们需要一个 Book.aidl 文件来将 Book 类引入使得其他的 AIDL 文件其中可以使用 Book 对象。那么第一步,如何新建一个 AIDL 文件呢?Android Studio已经帮我们把这个集成进去了:



鼠标移到app上面去,点击右键,然后 new->AIDL->AIDL File,按下鼠标左键就会弹出一个框提示生成AIDL文件了。生成AIDL文件之后,项目的目录会变成这样的:



比起以前多了一个叫做 aidl 的包,而且他的层级是和 java 包相同的,并且 aidl 包里默认有着和 java 包里默认的包结构。那么如果你用的是 Eclipse 或者较低版本的 as ,编译器没有这个选项怎么办呢?没关系,我们也可以自己写。打开项目文件夹,依次进入 app->src->main,在 main 包下新建一个和 java 文件夹平级的 aidl 文件夹,然后我们手动在这个文件夹里面新建和 java 文件夹里面的默认结构一样的文件夹结构,再在最里层新建 .aidl 文件就可以了:



注意看图中的文件目录。


Ok,如何新建AIDL文件说的差不多了,接下来就该写AIDL文件的内容了。内容的话如果上一节有认真看的话基本上是没什么问题的。在这里,我们需要两个AIDL文件,我是这样写的:


// Book.aidl

//第一类AIDL文件

//这个文件的作用是引入了一个序列化对象 Book 供其他的AIDL文件使用

//注意:Book.aidl与Book.java的包名应当是一样的

package com . lypeer . ipcclient ;

//注意parcelable是小写

parcelable Book ;


// BookManager.aidl

//第二类AIDL文件

//作用是定义方法接口

package com . lypeer . ipcclient ;

//导入所需要使用的非默认支持数据类型的包

import com . lypeer . ipcclient . Book ;

interface BookManager {

//所有的返回值前都不需要加任何东西,不管是什么数据类型

List getBooks ();

//传参时除了Java基本类型以及String,CharSequence之外的类型

//都需要在前面加上定向tag,具体加什么量需而定

void addBook ( in Book book );

}


注意: 这里又有一个坑! 大家可能注意到了,在 Book.aidl 文件中,我一直在强调:Book.aidl与Book.java的包名应当是一样的。这似乎理所当然的意味着这两个文件应当是在同一个包里面的——事实上,很多比较老的文章里就是这样说的,他们说最好都在 aidl 包里同一个包下,方便移植——然而在 Android Studio 里并不是这样。如果这样做的话,系统根本就找不到 Book.java 文件,从而在其他的AIDL文件里面使用 Book 对象的时候会报 Symbol not found 的错误。


为什么会这样呢?因为 Gradle 。大家都知道,Android Studio 是默认使用 Gradle 来构建 Android 项目的,而 Gradle 在构建项目的时候会通过 sourceSets 来配置不同文件的访问路径,从而加快查找速度——问题就出在这里。Gradle 默认是将 java 代码的访问路径设置在 java 包下的,这样一来,如果 java 文件是放在 aidl 包下的话那么理所当然系统是找不到这个 java 文件的。那应该怎么办呢?


又要 java文件和 aidl 文件的包名是一样的,又要能找到这个 java 文件——那么仔细想一下的话,其实解决方法是很显而易见的。首先我们可以把问题转化成:如何在保证两个文件包名一样的情况下,让系统能够找到我们的 java 文件?这样一来思路就很明确了:要么让系统来 aidl 包里面来找 java 文件,要么把 java 文件放到系统能找到的地方去,也即放到 java 包里面去。接下来我详细的讲一下这两种方式具体应该怎么做:


  • 修改 build.gradle 文件:在 android{} 中间加上下面的内容:


sourceSets {

main {

java . srcDirs = [ 'src/main/java' , 'src/main/aidl' ]

}

}


也就是把 java 代码的访问路径设置成了 java 包和 aidl 包,这样一来系统就会到 aidl 包里面去查找 java 文件,也就达到了我们的目的。只是有一点,这样设置后 Android Studio 中的项目目录会有一些改变,我感觉改得挺难看的。


  • 把 java 文件放到 java 包下去:把 Book.java 放到 java 包里任意一个包下,保持其包名不变,与 Book.aidl 一致。只要它的包名不变,Book.aidl 就能找到 Book.java ,而只要 Book.java 在 java 包下,那么系统也是能找到它的。但是这样做的话也有一个问题,就是在移植相关 .aidl 文件和 .java 文件的时候没那么方便,不能直接把整个 aidl 文件夹拿过去完事儿了,还要单独将 .java 文件放到 java 文件夹里去。


我们可以用上面两个方法之一来解决找不到 .java 文件的坑,具体用哪个就看大家怎么选了,反正都挺简单的。


到这里我们就已经将AIDL文件新建并且书写完毕了,clean 一下项目,如果没有报错,这一块就算是大功告成了。


4.3,移植相关文件


我们需要保证,在客户端和服务端中都有我们需要用到的 .aidl 文件和其中涉及到的 .java 文件,因此不管在哪一端写的这些东西,写完之后我们都要把这些文件复制到另一端去。如果是用的上面两个方法中的第一个解决的找不到 .java 文件的问题,那么直接将 aidl 包复制到另一端的 main 目录下就可以了;如果是使用第二个方法的话,就除了把把整个 aidl 文件夹拿过去,还要单独将 .java 文件放到 java 文件夹里去。


4.4,编写服务端代码


通过上面几步,我们已经完成了AIDL及其相关文件的全部内容,那么我们究竟应该如何利用这些东西来进行跨进程通信呢?其实,在我们写完AIDL文件并 clean 或者 rebuild 项目之后,编译器会根据AIDL文件为我们生成一个与AIDL文件同名的 .java 文件,这个 .java 文件才是与我们的跨进程通信密切相关的东西。事实上,基本的操作流程就是:在服务端实现AIDL中定义的方法接口的具体逻辑,然后在客户端调用这些方法接口,从而达到跨进程通信的目的。


接下来我直接贴上我写的服务端代码:


/**

* 服务端的AIDLService.java

*

* Created by lypeer on 2016/7/17.

*/

public class AIDLService extends Service {

public final String TAG = this . getClass (). getSimpleName ();

//包含Book对象的list

private List mBooks = new ArrayList ();

//由AIDL文件生成的BookManager

private final BookManager . Stub mBookManager = new BookManager . Stub () {

@Override

public List getBooks () throws RemoteException {

synchronized ( this ) {

Log . e ( TAG , "invoking getBooks() method , now the list is : " + mBooks . toString ());

if ( mBooks != null ) {

return mBooks ;

}

return new ArrayList ();

}

}

@Override

public void addBook ( Book book ) throws RemoteException {

synchronized ( this ) {

if ( mBooks == null ) {

mBooks = new ArrayList ();

}

if ( book == null ) {

Log . e ( TAG , "Book is null in In" );

book = new Book ();

}

//尝试修改book的参数,主要是为了观察其到客户端的反馈

book . setPrice ( 2333 );

if ( ! mBooks . contains ( book )) {

mBooks . add ( book );

}

//打印mBooks列表,观察客户端传过来的值

Log . e ( TAG , "invoking addBooks() method , now the list is : " + mBooks . toString ());

}

}

};

@Override

public void onCreate () {

super . onCreate ();

Book book = new Book ();

book . setName ( "Android开发艺术探索" );

book . setPrice ( 28 );

mBooks . add ( book );

}

@Nullable

@Override

public IBinder onBind ( Intent intent ) {

Log . e ( getClass (). getSimpleName (), String . format ( "on bind,intent = %s" , intent . toString ()));

return mBookManager ;

}

}


整体的代码结构很清晰,大致可以分为三块: 第一块是初始化。 在 onCreate() 方法里面我进行了一些数据的初始化操作。 第二块是重写 BookManager.Stub 中的方法。 在这里面提供AIDL里面定义的方法接口的具体实现逻辑。 第三块是重写 onBind() 方法。 在里面返回写好的 BookManager.Stub 。


接下来在 Manefest 文件里面注册这个我们写好的 Service ,这个不写的话我们前面做的工作都是无用功:


service

android : name = ".service.AIDLService"

android : exported = "true" >

intent - filter >

action android : name = "com.lypeer.aidl" />

category android : name = "android.intent.category.DEFAULT" />

intent - filter >

service >


到这里我们的服务端代码就编写完毕了,如果你对里面的一些地方感觉有些陌生或者根本不知所云的话,说明你对 Service 相关的知识已经有些遗忘了,建议再去看看这两篇博文:Android中的Service:默默的奉献者 (1),Android中的Service:Binder,Messenger,AIDL(2) 。


4.5,编写客户端代码


前面说过,在客户端我们要完成的工作主要是调用服务端的方法,但是在那之前,我们首先要连接上服务端,完整的客户端代码是这样的:


/**

* 客户端的AIDLActivity.java

* 由于测试机的无用debug信息太多,故log都是用的e

*

* Created by lypeer on 2016/7/17.

*/

public class AIDLActivity extends AppCompatActivity {

//由AIDL文件生成的Java类

private BookManager mBookManager = null ;

//标志当前与服务端连接状况的布尔值,false为未连接,true为连接中

private boolean mBound = false ;

//包含Book对象的list

private List mBooks ;

@Override

protected void onCreate ( Bundle savedInstanceState ) {

super . onCreate ( savedInstanceState );

setContentView ( R . layout . activity_aidl );

}

/**

* 按钮的点击事件,点击之后调用服务端的addBookIn方法

*

* @param view

*/

public void addBook ( View view ) {

//如果与服务端的连接处于未连接状态,则尝试连接

if ( ! mBound ) {

attemptToBindService ();

Toast . makeText ( this , "当前与服务端处于未连接状态,正在尝试重连,请稍后再试" , Toast . LENGTH_SHORT ). show ();

return ;

}

if ( mBookManager == null ) return ;

Book book = new Book ();

book . setName ( "APP研发录In" );

book . setPrice ( 30 );

try {

mBookManager . addBook ( book );

Log . e ( getLocalClassName (), book . toString ());

} catch ( RemoteException e ) {

e . printStackTrace ();

}

}

/**

* 尝试与服务端建立连接

*/

private void attemptToBindService () {

Intent intent = new Intent ();

intent . setAction ( "com.lypeer.aidl" );

intent . setPackage ( "com.lypeer.ipcserver" );

bindService ( intent , mServiceConnection , Context . BIND_AUTO_CREATE );

}

@Override

protected void onStart () {

super . onStart ();

if ( ! mBound ) {

attemptToBindService ();

}

}

@Override

protected void onStop () {

super . onStop ();

if ( mBound )







请到「今天看啥」查看全文


推荐文章
开发者全社区  ·  WC!阿里P7总包超150w
昨天
开发者全社区  ·  英区金融妲己
2 天前
开发者全社区  ·  离谱瓜!约会8个女同事的大厂男
2 天前
开发者全社区  ·  UCL色魔博士被抓
3 天前
开发者全社区  ·  人大发TT了
3 天前
孤读先生  ·  你的容貌出卖了你的生活品质
8 年前
摄脉  ·  「摄脉交流群」入群需知
7 年前
金融行业网  ·  震惊!网贷之家居然对天猫做这种事!
7 年前