基于AIDL编程实现Android远程Service服务

Posted Tr0e

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了基于AIDL编程实现Android远程Service服务相关的知识,希望对你有一定的参考价值。

前言

Service 作为 android 四大组件之一,应用非常广泛。

远程服务与本地服务最大的区别是:远程 Service 与调用者不在同一个进程里(即远程 Service 是运行在另外一个进程);而本地服务则是与调用者运行在同一个进程里。二者区别的详细区别如下图:

本地Service

先来看看 Android 普通 Service 的使用。

异步消息处理机制

郭霖解释得很清晰:




服务与活动的通信

直接看看《Android第一行代码》里面的示例:


【圈重点】service 服务的 onBind() 方法通过绑定自定义的 Binder 继承类对象,结合服务与活动绑定后将自动调用执行的 onServiceConnected() 函数,可定义活动与服务的交互动作。

远程Service

Android 系统中的进程之间不能共享内存,因此,需要提供一些机制在不同进程之间进行数据通信。为了使其他的应用程序也可以访问本应用程序提供的服务,Android 系统采用了远程过程调用(Remote Procedure Call,RPC)方式来实现。与很多其他的基于 RPC 的解决方案一样,Android 使用一种接口定义语言(Interface Definition Language,IDL)来公开服务的接口。为了克服 Linux 中 IPC (Inter-Process Communication,即跨进程通信)各种方式的缺点,在 Android 中引入了 Binder 机制,而 AIDL 即是 Binder 机制实现 Android IPC 时使用的工具。

【More】关于 Android 跨进程通信:Android跨进程通信Binder机制与AIDL实例

Binder与AIDL基础

Binder 与 AIDL 的作用范围:

  1. Binder:如果是在一个应用里实现远程调用,使用Binder即可,没必要使用AIDL(比如上面演示的本地Service代码);
  2. AIDL:如果涉及到在多个应用程序之间使用IPC通信,并且在服务又有多线程业务处理,这时可以使用AIDL。

Binder 跨进程通信机制 模型 基于 Client - Server 模式:

Client-ServiceManager-Server时序图如下:

Binder 从代码的角度来说,是 Android 系统源码中的一个类,它实现了 IBinder 接口;从 IPC 角度来说,Binder 是 Android 中的一种跨进程通信方式;从 Android Framework 角度来讲,Binder 是 ServiceManager 连接各种 Manager(ActivityManager、WindowManager 等等)和相应 ManagerService 的桥梁;从 Android 应用层来说,Binder 是客户端和服务端进行通信的媒介,当 bindService 的时候,服务端会返回一个包含了服务端业务调用的 Binder 对象,通过这个 Binder 对象,客户端就可以和服务端进行通信,这里的服务包括普通服务和基于 AIDL 的服务

AIDL Service服务端

在多进程通信中,存在两个进程角色(以最简单的为例):服务器端和客户端。以下是两个进程角色的具体使用步骤:

服务器端(Service)

  1. 新建定义AIDL文件,并声明该服务需要向客户端提供的接口;
  2. 在 Service 子类中定义内部类 A 实现 AIDL 中定义的接口方法,并定义 Service 生命周期的方法(onCreat()、onBind()等),重点是在 onBind() 方法中 return 返回 A 类的对象;
  3. 在 AndroidMainfest.xml 中注册服务且声明为远程服务。

客户端(Client)

  1. 拷贝服务端的 AIDL 文件到 aidl 目录下;
  2. 通过Intent指定服务端的服务名称和所在包,绑定远程Service;
  3. 使用 Stub.asInterface 接口获取服务器的 Binder,根据需要调用服务提供的接口方法。

接下来,通过一个具体实例来介绍远程 Service 的使用。主要实现的是通过 AIDL 自定义的远程服务,给客户端 APP 提供登录接口和图书价格查询的接口。

(1)新建一个带了空 MainActivity 的 Android 项目 com.bwshen.aidlservice,先来看下最终的项目结构(需要编写的文件有 aidl 文件 IMyBookManager.aidl 、图书类 book.java、服务类 AIDLService.java):

(2)先来创建 aidl 接口文件 IMyBookManager.aidl ,在 app 上点击右键->新建->AIDL->AIDL文件(将自动创建 aidl 文件夹),定义两个服务接口分别为用于登录的 login() 和用于图书查询的 queryByName(),具体代码如下:

// IMyBookManager.aidl
package com.bwshen.aidlservice;

// Declare any non-default types here with import statements
import com.bwshen.aidlservice.Book;

//需要说明我们引入的book 是个序列化的类。
parcelable Book;

interface IMyBookManager 
    /**
     * Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */
    void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,double aDouble, String aString);

    String login(String userName, String pwd);

    Book queryByName(String bookName);

(3)创建图书类 book.java 文件,由于需要通过 AIDL 接口返回给调用者,所以这个类需要进行序列化,故需要实现 Parcelable 接口:

package com.bwshen.aidlservice;

import android.os.Parcel;
import android.os.Parcelable;

public class Book implements Parcelable 
    private int price;
    private String name = "";

    public Book()

    protected Book(Parcel in) 
        price = in.readInt();
        name = in.readString();
    

    public static final Creator<Book> CREATOR = new Creator<Book>() 
        @Override
        public Book createFromParcel(Parcel in) 
            return new Book(in);
        

        @Override
        public Book[] newArray(int size) 
            return new Book[size];
        
    ;

    public int getPrice() 
        return price;
    

    public void setPrice(int price) 
        this.price = price;
    

    public String getName() 
        return name;
    

    public void setName(String name) 
        this.name = name;
    

    @Override
    public String toString() 
        return "\\n图书名称:" + name + "\\n图书价格:" + price;
    

    @Override
    public int describeContents() 
        return 0;
    

    @Override
    public void writeToParcel(Parcel dest, int flags) 
        dest.writeInt(price);
        dest.writeString(name);
    

(4)此时对项目进行编译 Build->Make Project,系统便会帮我们自动生成步骤 1 的图示所标出来的 com/bwshen/aidlservice/IMyBookManager.java 文件,该文件的内容及作用此处暂时先忽略,本文文末会单独重点分析。

(5)创建 AIDLService 类,并使用内部类 MyBookManager 继承 IMyBookManager.Stub、实现 IMyBookManager.aidl 对外提供的接口,并通过 service 的 onbind() 函数返回 MyBookManager 类的对象,如下:

package com.bwshen.aidlservice.service;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;

import com.bwshen.aidlservice.Book;
import com.bwshen.aidlservice.IMyBookManager;

public class AIDLService extends Service 
    MyBookManager bookManager = new MyBookManager();

    public AIDLService() 
    

    @Override
    public IBinder onBind(Intent intent) 
        return bookManager;
    

    public class MyBookManager extends IMyBookManager.Stub 
        @Override
        public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException 

        

        @Override
        public String login(String userName, String pwd) throws RemoteException 
            if(userName.equalsIgnoreCase("admin") && pwd.equalsIgnoreCase("123456"))
                return "success";
            
            return "error";
        

        @Override
        public Book queryByName(String bookName) throws RemoteException 
            Book book = new Book();
            book.setName(bookName);
            book.setPrice(100);
            return book;
        
    

(6)最后在 AndroidManifest.xml 中将服务配置为远程服务:

<service
   android:name=".service.AIDLService"
   android:enabled="true"
   android:exported="true"
   android:process=":remote">
   <intent-filter>
       <action android:name="com.bwshen.aidlservice.bookService"/>
       <category android:name="android.intent.category.DEFAULT" />
   </intent-filter>
</service>

至此,提供远程 Service 服务的 APP 准备完毕。可以修改下主活动的页面,展示如下:

AIDL Service客户端

搭建完远程 Service 服务端 APP,下面来搭建一个连接远程 Service 并调用服务接口实现登录、图书价格查询功能的客户端 APP。

(1)同样新建一个空 Activity 的 Android 项目 com.bwshen.aidlclient,先来看看最终项目完整的结构:

(2)由上可知整个项目中需要自行编写的三个文件(MainActivity.java、IMyBookManager.aidl、Book.java)有两个直接从服务端 APP 完整拷贝过来即可,而 IMyBookManager.java 文件则由编译项目时自动生成、无需理会,故这里主要看下 MainActivity 的代码(需注意下面代码会导入 import com.bwshen.aidlservice.IMyBookManager,故需要拷贝完 IMyBookManager.aidl 文件后先编译下项目):

package com.bwshen.aidlclient;

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.text.method.ScrollingMovementMethod;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;

import androidx.appcompat.app.AppCompatActivity;

import com.bwshen.aidlservice.Book;
import com.bwshen.aidlservice.IMyBookManager;

public class MainActivity extends AppCompatActivity 

    private Button btnConnection;
    private EditText etName;
    private EditText etPwd;
    private Button btnLogin;
    private TextView tvBookInfo;
    private Button btnQuery;
    private EditText etBookName;
    private boolean islogin=false;
    /**
     * 该类的导入,需要先编译项目,将aidl文件进行编译后映射,才能成功识别并导出com.bwshen.aidlservice.IMyBookManager
     */
    private IMyBookManager bookManager = null;

    private ServiceConnection conn = new ServiceConnection() 
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) 
            bookManager = IMyBookManager.Stub.asInterface(service);
            Toast.makeText(MainActivity.this, "服务连接成功!", Toast.LENGTH_SHORT).show();
        

        @Override
        public void onServiceDisconnected(ComponentName name) 
            bookManager = null;
        
    ;

    @Override
    protected void onCreate(Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        etBookName = findViewById(R.id.etBookName);
        etName = findViewById(R.id.etName);
        etPwd = findViewById(R.id.etPwd);
        btnLogin = findViewById(R.id.btnLogin);
        btnConnection = findViewById(R.id.btnConnection);
        btnQuery = findViewById(R.id.btnQuery);
        tvBookInfo = findViewById(R.id.tvBookInfo);
        tvBookInfo.setMovementMethod(ScrollingMovementMethod.getInstance());
        connect();
    

    private void connect()
        btnConnection.setOnClickListener(new View.OnClickListener() 
            @Override
            public void onClick(View v) 
                if(bookManager != null)
                    Toast.makeText(MainActivity.this, "服务连接成功!", Toast.LENGTH_SHORT).show();
                else
                    Intent intent = new Intent();
                    intent.setAction("com.bwshen.aidlservice.bookService");
                    intent.setPackage("com.bwshen.aidlservice");
                    bindService(intent, conn, Context.BIND_AUTO_CREATE);
                
            
        );

        btnLogin.setOnClickListener(new View.OnClickListener() 
            @Override
            public void onClick(View v) 
                if(bookManager == null)
                
                    Toast.makeText(MainActivity.this, "还没有绑定服务!", Toast.LENGTH_SHORT).show();
                    return;
                
                if(etName.getText().toString().isEmpty() || etPwd.getText().toString().isEmpty())
                    Toast.makeText(MainActivity.this, "用户名或密码不能为空!", Toast.LENGTH_SHORT).show();
                    return;
                
                try 
                    String resStr = bookManager.login(etName.getText().toString(), etPwd.getText().toString());
                    if(resStr.compareToIgnoreCase("success") == 0)
                        islogin=true;
                        Toast.makeText(MainActivity.this, "登录成功!", Toast.LENGTH_SHORT).show();
                     else
                        Toast.makeText(MainActivity.this, "登录失败!", Toast.LENGTH_SHORT).show();
                        throw new RemoteException("登录失败");
                    
                 catch (RemoteException e) 
                    e.printStackTrace();
                    Toast.makeText(MainActivity.this, "登录失败!", Toast.LENGTH_SHORT).show();
                
            
        );

        btnQuery.setOnClickListener(new View.OnClickListener() 
            @Override
            public void onClick(View v) 
                if(bookManager == null || !islogin)
                
                    Toast.makeText(MainActivity.this, "请先绑定服务并登录!", Toast.LENGTH_LONG).show();
                    return;
                
                if(etBookName.getText().toString().isEmpty())
                    Toast.makeText(MainActivity.this, "请输入您要查找的图书!", Toast.LENGTH_LONG).show();
                    return;
                
                try 
                    Book book = bookManager.queryByName(etBookName.getText().toString());
                    if(以上是关于基于AIDL编程实现Android远程Service服务的主要内容,如果未能解决你的问题,请参考以下文章

基于AIDL编程实现Android远程Service服务

Android AIDL浅析及异步使用

Android AIDL的用法

Android系统服务自动化信息收集与Fuzz测试

Android系统服务自动化信息收集与Fuzz测试

在 Eclipse 中调试基于 AIDL 的远程服务