AIDL解析(二):AIDL项目开发

一、序言

        该系列文章旨在让AIDL初学者入门,不一定全,但通过自己在Android Studio上的实际编写运行,尽可能会把相关的知识点和编译运行过程中遇到的问题列出来并给出解决方案。

        本文主要介绍使用Android Studio进行AIDL项目代码的编写,并针对过程中遇到的问题,提供相关解决方法,学习本文之前,建议大家先学习下面这篇文章,将AIDL的原理了解一下,对于后续的AIDL的开发可以起到事半功倍的作用:
https://blog.csdn.net/qq_41739313/article/details/123322808

二、服务器端代码

        建议在学习本文前,先将代码成功运行起来,本文源码链接如下:AIDL开发-文章源码-Android文档类资源

2.1 MainActivity.java

        单个Service组件无法运行,要么通过BroadcastReceiver来启动,要么依托于Activity来启动;MainActivity位于com.example.ipservcer包;

        MainActivity.java使用默认的代码就行,不过要在AndroidManifest.xml中配置一下android:exported属性:

        <activity android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>

2.2 activity_main.xml

        使用默认的代码即可;

2.3 Book.java

        位于com.example.ipcserver包,定义一个继承了Parcelable类的可序列化的Book类;

public class Book implements Parcelable {
    public String getName() {
        return name;
    }

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

    public int getPrice() {
        return price;
    }

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

    private String name;

    private int price;

    public Book(){}

    public Book(Parcel in) {
        name = in.readString();
        price = in.readInt();
    }

    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];
        }
    };

    @Override
    public int describeContents() {
        return 0;
    }

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

    public void readFromParcel(Parcel dest) {
        name = dest.readString();
        price = dest.readInt();
    }

    @Override
    public String toString() {
        return "name : " + name + " , price : " + price;
    }
}

2.4 Book.aidl

        位于com.example.ipcserver包,定义parcelable对象,以供其他AIDL文件使用AIDL中非默认支持的数据类型。

        直接在包名上单击鼠标右键,选择new -> AIDL,可能会出现不能创建同名文件的提示,因为前面我们创建了Book.java文件,这里随意命名,创建后在另行改名为Book.aidl;

package com.example.ipcserver;

parcelable Book;

2.5  BookManager.aidl

        位于com.example.ipcserver包,用于声明服务器端相关方法的接口;

package com.example.ipcserver;

import com.example.ipcserver.Book;

interface BookManager {

    List<Book> getBooks();
    void addBook(inout Book book);
}

2.6 AIDLService.java

        (1)位于com.example.ipcserver包,用于处理和客户端的连接,以及继承并定义Stub中声明的方法;

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

import androidx.annotation.Nullable;

import java.util.ArrayList;
import java.util.List;

public class AIDLService extends Service {

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

    //包含Book对象的list
    private List<Book> mBooks = new ArrayList<>();

    //由AIDL文件生成的BookManager
    private final IBinder mBookManager = new BookManager.Stub() {
        @Override
        public List<Book> 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() {
        Book book = new Book();
        book.setName("Android开发艺术探索");
        book.setPrice(28);
        mBooks.add(book);
        super.onCreate();
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        Log.e(getClass().getSimpleName(), String.format("on bind,intent = %s", intent.toString()));
        return mBookManager;
    }

    public void onDestroy() {
        Log.e("onDestroy", "service on destroy");
        super.onDestroy();
    }

}

        (2)在AndroidManifest.xml中定义该service组件;

        <service android:name=".AIDLService"
            android:exported="true"
            android:enabled="true">
            <intent-filter>
                <action android:name="android.intent.action.AIDLService"/>
            </intent-filter>
        </service>

2.7 AndroidManifest.xml

        其中的内容在前面步骤中定义相关组件的时候已经列出了;

        唯一要注意的就是,看一下manifest标签中的package属性值,是否service类的包名对应上了,这个属性值在客户端连接服务器的时候需要使用到。

2.8 BookManager.java

        编写完前面的代码后,运行该项目,将项目结构切换为Project,在app -> build -> generated -> aidl_source_output_dir -> debug -> out -> com.example.ipcserver目录下,可以找到生成的BookManager.java接口文件。

package com.example.ipcserver;
public interface BookManager extends android.os.IInterface
{
    public static abstract class Stub extends android.os.Binder implements com.example.ipcserver.BookManager
  {
    ...
    private static class Proxy implements com.example.ipcserver.BookManager
    {
        ...
    }
  }
  ...
}

    

三、客户端代码

3.1 AIDLActivity.java

        (1)位于com.example.ipclient包下,服务器端的主要代码,用于建立与服务器端的连接,调用服务器端的方法;

public class AIDLActivity extends AppCompatActivity implements View.OnClickListener{

    //由AIDL文件生成的Java类
    private BookManager mBookManager = null;
    //标志当前与服务端连接状况的布尔值,false为未连接,true为连接中
    private boolean mBound = false;
    //包含Book对象的list
    private List<Book> mBooks;
    private Button addBookBtn, ServiceBtn;
    private MyService.DownloadBinder downloadBinder;
    private ServiceConnection mServiceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Log.e(getLocalClassName(), "service connected");
            mBookManager = BookManager.Stub.asInterface(service);
            mBound = true;

            if (mBookManager != null) {
                try {
                    mBooks = mBookManager.getBooks();
                    Log.e(getLocalClassName(), mBooks.toString());
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            Log.e(getLocalClassName(), "service disconnected");
            mBound = false;
        }
    };
    private ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
            downloadBinder = (MyService.DownloadBinder) iBinder;
            downloadBinder.startDownload();
            downloadBinder.getProgress();
        }

        @Override
        public void onServiceDisconnected(ComponentName componentName) {

        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_aidl);
        initView();
    }

    private void initView() {
        addBookBtn = findViewById(R.id.addBookIn);
        addBookBtn.setOnClickListener(this);
    }

    @Override
    public void onClick(View view) {
        switch (view.getId()) {
            case R.id.addBookIn:
                addBook(view);
                break;
            default:
                break;
        }
    }

    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.setPackage("com.example.ipcserver");
        intent.setAction("android.intent.action.AIDLService");
        boolean b = bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
    }

    @Override
    protected void onStop() {
        super.onStop();
        if (mBound) {
            unbindService(mServiceConnection);
            mBound = false;
        }
    }
}

        (2)在AndroidManifest.xml中声明activity组件;

    <activity android:name="com.example.ipcclient.AIDLActivity"
        android:exported="true">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>

3.2 AndroidManifest.xml

        其中的相关内容已在定义相关activity的时候进行了说明;

        注意将 "需要让其他进程访问的组件的android:exported属性" 设置为true;

3.3 buid.gradle(app)

        因为Activity文件和aidl文件位于两个不同的包下,由于Android Studio的特殊编译机制,需要在build.gradle(Module:app)中配置java编译路径,才能正确编译到所有文件,否则,编译系统找不到aidl文件;

android {
    sourceSets {
        main {
            java.srcDirs = ['src/main/java', 'src/main/aidl']
        }
    }
}

四、注意事项

4.1 aidl编译配置

        编写AIDL的目的是生成对应的java文件,而在Android Studio中,默认情况下只会编译java目录下的文件,aidl文件和java文件在两个不同的目录下,所以需要在build.gradle(app)进行如下配置:

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

4.2 AndroidManifest配置

4.2.1 配置方法

        服务器端的service需要配置andrdoid:export属性为true,才能被客户端访问,export="true"的作用就是:支持其它应用调用当前组件,具体配置如下:

        <service android:name=".AIDLService"
            android:exported="true"
            android:enabled="true">
            <intent-filter>
                <action android:name="android.intent.action.AIDLService"/>
            </intent-filter>
        </service>

4.2.2 android:exported属性

        参考:android:exported 属性详解 - 天之骄子19 - 博客园

4.3 package一致性

        (1)client客户端是通过如下方式与服务器端连接的,所以要保证客户端的setPackage(" ")中的参数和服务器端的build.gradle(app)中android的defaultConfig的applicationId属性的值保持一致,最好也与AndroidManifest.xml中的manifest标签的package属性保持一致。

        (2)客户端和服务器端都有相同的aidl通信接口文件,要保证两端aidl文件所在目录的包名一致;

4.4 软件可见性

        Android 11之后,在默认情况下,系统会自动让部分应用对您的应用可见,但会隐藏其他应用,也影响到了AIDL通信;需要加如下权限:

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

        参考:Android 11 适配 软件包可见性_Lucky_William的博客-CSDN博客

4.5 无法连接service 

        报错内容:Unable to start service Intent { cmp=com.glad.android.action/com.glad.android.services.bg.DataProcessService }: not found 

        如果你的service和启动的activity不在同一个包内,需要把service标签中的android:name配置成service类的完全名(全路径)(AIDL跨进程通信暂未出现该问题)

        无法连接的可能原因:

        参考:https://www.jb51.net/article/121679.htm

        参考:Android bindService失败,解决方法。_VNanyesheshou的博客

        参考:使用AIDL时bindService失败---packageName_胡飞洋的博客-CSDN博客

        特定手机:进程间bindService失败(魅族手机) - 简书

        参考:bindService失败的解决办法 - it610.com

参考文章:

AIDL | Android:学习AIDL,这一篇文章就够了(上)_lypeer的博客-CSDN博客_aidl

AIDL | Android中AIDL的使用详解 - 简书

AIDL开发 | Android进程间通信(一):AIDL使用详解 - 帅气陈吃苹果 - 博客园

软件可见性 | Android 11 适配 软件包可见性_Lucky_William的博客-CSDN博客

AIDL在安卓系统中的应用 | AIDL在android系统中的作用_园荐_博客园

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值