Android AIDL 使用教程

AIDL(Android Interface Definition Language)是一种 IDL 语言,用于生成可以在 Android 设备上两个进程之间进行进程间通信(IPC)的代码。 通过 AIDL,可以在一个进程中获取另一个进程的数据和调用其暴露出来的方法,从而满足进程间通信的需求。通常,暴露方法给其他应用进行调用的应用称为服务端,调用其他应用的方法的应用称为客户端,客户端通过绑定服务端的 Service 来进行交互。

官方文档中对 AIDL 有这样一段介绍:

Using AIDL is necessary only if you allow clients from different
applications to access your service for IPC and want to handle
multithreading in your service. If you do not need to perform
concurrent IPC across different applications, you should create your
interface by implementing a Binder or, if you want to perform IPC, but
do not need to handle multithreading, implement your interface using a
Messenger. Regardless, be sure that you understand Bound Services
before implementing an AIDL.

第一句很重要,“只有当你允许来自不同的客户端访问你的服务并且需要处理多线程问题时你才必须使用AIDL”,其他情况下你都可以选择其他方法,如使用 Messenger,也能跨进程通信。可见 AIDL 是处理多线程、多客户端并发访问的,而 Messenger 是单线程处理。
下面介绍 AIDL 的使用方法。

1 创建 AIDL 文件

AIDL 文件可以分为两类。一类用来声明实现了 Parcelable 接口的数据类型,以供其他 AIDL 文件使用那些非默认支持的数据类型。还有一类是用来定义接口方法,声明要暴露哪些接口给客户端调用。在 AIDL 文件中需要明确标明引用到的数据类型所在的包名,即使两个文件处在同个包名下。

默认情况下,AIDL 支持下列数据类型:

  • 八种基本数据类型:byte、char、short、int、long、float、double、boolean
  • String,CharSequence
  • List类型。List承载的数据必须是AIDL支持的类型,或者是其它声明的AIDL对象
  • Map类型。Map承载的数据必须是AIDL支持的类型,或者是其它声明的AIDL对象

客户端和服务端都需要创建,我们先在服务端中创建,然后复制到客户端即可。在 Android Studio 中右键点击新建一个 AIDL 文件,如图所示:

新建AIDL文件
创建完成后,系统就会默认创建一个 aidl 文件夹,文件夹下的目录结构即是工程的包名,AIDL 文件就在其中。如图所示:

aidl文件夹
文件中会有一个默认方法,可以删除掉,也可以新增其他方法。

2 实现接口

创建或修改过 AIDL 文件后需要 build 下工程,Android SDK 工具会生成以 .aidl 文件命名的 .java 接口文件(例如,IRemoteService.aidl 生成的文件名是 IRemoteService.java),在进程间通信中真正起作用的就是该文件。生成的接口包含一个名为 Stub 的子类(例如,IRemoteService.Stub),该子类是其父接口的抽象实现,并且会声明 AIDL 文件中的所有方法。
如要实现 AIDL 生成的接口,请实例化生成的 Binder 子类(例如,IRemoteService.Stub),并实现继承自 AIDL 文件的方法。
以下是使用匿名内部类实现 IRemoteService 接口的示例:

private final IRemoteService.Stub binder = new IRemoteService.Stub() {
    public int getPid(){
        return Process.myPid();
    }
    public void basicTypes(int anInt, long aLong, boolean aBoolean,
        float aFloat, double aDouble, String aString) {
        // Does nothing
    }
};

现在,binder 是 Stub 类的一个实例(一个 Binder),其定义了服务端的 RPC 接口。

3 服务端公开接口

在为服务端实现接口后,需要向客户端公开该接口,以便客户端进行绑定。创建 Service 并实现 onBind(),从而返回生成的 Stub 的类实例。以下是服务端的示例代码:

public class RemoteService extends Service {
    private final String TAG = "RemoteService";

    @Override
    public void onCreate() {
        super.onCreate();
    }

    @Override
    public IBinder onBind(Intent intent) {
        // Return the interface
        Log.d(TAG, "onBind");
        return binder;
    }

    private final IRemoteService.Stub binder = new IRemoteService.Stub() {
        public int getPid() {
            return Process.myPid();
        }

        public void basicTypes(int anInt, long aLong, boolean aBoolean,
                               float aFloat, double aDouble, String aString) {
            Log.d(TAG, "basicTypes anInt:" + anInt + ";aLong:" + aLong + ";aBoolean:" + aBoolean + ";aFloat:" + aFloat + ";aDouble:" + aDouble + ";aString:" + aString);
        }
    };
}

我们还需要在 Manefest 文件中注册我们创建的这个 Service,否则客户端无法绑定服务。

        <service
            android:name=".RemoteService"
            android:enabled="true"
            android:exported="true">
            <intent-filter>
                <action android:name="com.example.aidl"/>
                <category android:name="android.intent.category.DEFAULT"/>
            </intent-filter>
        </service>

4 客户端调用 IPC 方法

当客户端(如 Activity)调用 bindService() 以连接此服务时,客户端的 onServiceConnected() 回调会接收服务端的 onBind() 方法所返回的 binder 实例。

客户端还必须拥有接口类的访问权限,因此如果客户端和服务端在不同应用内,则客户端应用的 src/ 目录内必须包含 .aidl 文件(该文件会生成 android.os.Binder 接口,进而为客户端提供 AIDL 方法的访问权限)的副本。所以我们需要把服务端的 aidl 文件夹整个复制到客户端的 java 文件夹同个层级下,不需要改动任何代码。

当客户端在 onServiceConnected() 回调中收到 IBinder 时,它必须调用 IRemoteService.Stub.asInterface(service),以将返回的参数转换成 IRemoteService 类型。例如:

IRemoteService iRemoteService;
private ServiceConnection mConnection = new ServiceConnection() {
    // Called when the connection with the service is established
    public void onServiceConnected(ComponentName className, IBinder service) {
        // Following the example above for an AIDL interface,
        // this gets an instance of the IRemoteInterface, which we can use to call on the service
        iRemoteService = IRemoteService.Stub.asInterface(service);
    }

    // Called when the connection with the service disconnects unexpectedly
    public void onServiceDisconnected(ComponentName className) {
        Log.e(TAG, "Service has unexpectedly disconnected");
        iRemoteService = null;
    }
};

获得了 iRemoteService 对象,我们就可以调用 AIDL 中定义的方法了。如要断开连接,可以调用unbindService() 方法。以下是客户端的示例代码:

public class MainActivity extends AppCompatActivity {
    private final String TAG = "ClientActivity";
    private IRemoteService iRemoteService;
    private Button mBindServiceButton;

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

        mBindServiceButton = findViewById(R.id.btn_bind_service);
        mBindServiceButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                String text = mBindServiceButton.getText().toString();
                if ("Bind Service".equals(text)) {
                    Intent intent = new Intent();
                    intent.setAction("com.example.aidl");
                    intent.setPackage("com.example.aidl.server");
                    bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
                } else {
                    unbindService(mConnection);
                    mBindServiceButton.setText("Bind Service");
                }
            }
        });
    }

    ServiceConnection mConnection = new ServiceConnection() {

        @Override
        public void onServiceDisconnected(ComponentName name) {
            Log.d(TAG, "onServiceDisconnected");
            iRemoteService = null;
        }

        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Log.d(TAG, "onServiceConnected");
            iRemoteService = IRemoteService.Stub.asInterface(service);
            try {
                int pid = iRemoteService.getPid();
                int currentPid = Process.myPid();
                Log.d(TAG, "currentPID: " + currentPid + ", remotePID: " + pid);
                iRemoteService.basicTypes(12, 123, true, 123.4f, 123.45,
                        "服务端你好,我是客户端");
            } catch (RemoteException e) {
                e.printStackTrace();
            }
            mBindServiceButton.setText("Unbind Service");
        }
    };
}

5 通过 IPC 传递对象

除了上面默认支持的数据类型,AIDL 还可以传递对象,但是该类必须实现 Parcelable 接口。而该类是两个应用间都需要使用到的,所以也需要在 AIDL 文件中声明该类,为了避免出现类名重复导致无法创建 AIDL 文件的错误,这里需要先创建 AIDL 文件,之后再创建类。
先在服务端新建一个 AIDL 文件,比如 Rect.aidl,示例如下:

// Rect.aidl
package com.example.aidl.server;

// Declare Rect so AIDL can find it and knows that it implements
// the parcelable protocol.
parcelable Rect;

然后就可以创建 Rect 类了,并使之实现 Parcelable 接口。示例代码如下:

public class Rect implements Parcelable {
    private int left;
    private int top;
    private int right;
    private int bottom;

    public Rect(int left, int top, int right, int bottom) {
        this.left = left;
        this.top = top;
        this.right = right;
        this.bottom = bottom;
    }

    public static final Parcelable.Creator<Rect> CREATOR = new Parcelable.Creator<Rect>() {
        public Rect createFromParcel(Parcel in) {
            return new Rect(in);
        }

        public Rect[] newArray(int size) {
            return new Rect[size];
        }
    };

    private Rect(Parcel in) {
        readFromParcel(in);
    }

    @Override
    public void writeToParcel(Parcel out, int flags) {
        out.writeInt(left);
        out.writeInt(top);
        out.writeInt(right);
        out.writeInt(bottom);
    }

    public void readFromParcel(Parcel in) {
        left = in.readInt();
        top = in.readInt();
        right = in.readInt();
        bottom = in.readInt();
    }

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

    @NonNull
    @Override
    public String toString() {
        return "Rect[left:" + left + ",top:" + top + ",right:" + right + ",bottom:" + bottom + "]";
    }
}

这样我们就可以在之前创建的 IRemoteService.aidl 中新增一个方法来传递 Rect 对象了,示例代码如下:

// IRemoteService.aidl
package com.example.aidl.server;
import com.example.aidl.server.Rect;

// Declare any non-default types here with import statements

interface IRemoteService {
    /**
     * 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);

    int getPid();

    void addRectInOut(inout Rect rect);
}

注意这里需要明确导包:

import com.example.aidl.server.Rect;

然后将新增的 Rect.aidl 文件和 Rect.java 文件还有修改的 IRemoteService.aidl 文件同步到客户端相同路径下,如图所示:

同步文件
build 下工程,就可以在客户端调用到该 addRectInOut 方法了。示例代码如下:

    ServiceConnection mConnection = new ServiceConnection() {

        @Override
        public void onServiceDisconnected(ComponentName name) {
        }

        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            iRemoteService = IRemoteService.Stub.asInterface(service);
            try {
                iRemoteService.addRectInOut(new Rect(1, 2, 3, 4));
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
    };

6 示例代码下载

aidl-sample

  • 34
    点赞
  • 151
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 9
    评论
Android开发案例驱动教程》 配套代码。 注: 由于第12,13,14章代码太大,无法上传到一个包中。 这三节代码会放到其他压缩包中。 作者:关东升,赵志荣 Java或C++程序员转变成为Android程序员 采用案例驱动模式展开讲解知识点,即介绍案例->案例涉及技术->展开知识点->总结的方式 本书作者从事多年一线开发和培训,讲解知识点力求细致,深入浅出 目 录 前言 第1章 Android操作系统概述 1 1.1 Android历史介绍 1 1.2 Android架构 1 1.3 Android平台介绍 2 1.4 现有智能手机操作系统比较 4 第2章 Android开发环境搭建 5 2.1 Eclipse和ADT插件 5 2.1.1 Eclipse安装 5 2.1.2 ADT插件 6 2.2 Android SDK 8 2.2.1 Android SDK的获得 8 2.2.2 Android SDK版本说明 10 2.2.3 ADT配置 10 2.3 Android开发模拟器 11 2.3.1 创建模拟器 11 2.3.2 启动模拟器 13 2.3.3 键盘映射与模拟器控制 13 2.3.4 横屏与竖屏切换 14 第3章 第一个Android程序 15 3.1 HelloAndroid 15 3.1.1 在Eclipse中创建项目 15 3.1.2 编写程序项目代码 17 3.1.3 运行HelloAndroid 18 3.1.4 Android工程目录 19 3.1.5 AndroidManifest.xml文件 21 3.2 Android中的组件介绍 22 3.3 使用Android SDK帮助 23 3.3.1 Android SDK API文档 23 3.3.2 Android SDK开发指南 24 3.3.3 Android SDK samples 24 3.4 使用DDMS帮助调试程序 26 3.4.1 启动DDMS 26 3.4.2 Device 28 3.4.3 Emulator Control 29 3.4.4 File Explorer 30 3.4.5 LogCat 31 3.5 使用ADB帮助调试程序 33 3.5.1 查询模拟器实例和设备 34 3.5.2 进入shell 34 3.5.3 导入导出文件 35 3.6 应用程序的打包、安装和卸载 37 3.6.1 应用程序打包 37 3.6.2 应用程序安装 40 3.6.3 应用程序卸载 40 本章小结 42 第4章 UI基础知识 43 4.1 Android UI组件概述 43 4.1.1 View 43 4.1.2 ViewGroup 44 4.1.3 布局管理器 44 4.2 UI设计工具 44 4.2.1 DroidDraw工具 44 4.2.2 ADT插件UI设计工具 46 4.3 事件处理模型 47 4.3.1 接口实现事件处理模型 47 4.3.2 内部类事件处理模型 49 4.3.3 匿名内部类事件处理模型 51 4.4 Activity中的常用事件 53 4.4.1 触摸事件 53 4.4.2 键盘事件 55 4.5 菜单 57 4.5.1 文本菜单 57 4.5.2 图片文本菜单 59 本章小结 60 第5章 UI基础控件 61 5.1 按钮 61 5.1.1 Button 62 5.1.2 ImageButton 63 5.1.3 ToggleButton 64 5.2 TextView 64 5.3 EditText 65 5.4 RadioButton和RadioGroup 66 5.4.1 RadioButton 66 5.4.2 RadioGroup 67 5.5 CheckBox 68 5.6 ImageView 70 5.7 Progress Bar 70 5.7.1 条状进度条 71 5.7.2 圆形进度条 73 5.7.3 对话框进度条 74 5.7.4 标题栏中进度条 75 5.8 SeekBar 76 5.9 RatingBar 78 本章小结 82 第6章 UI高级控件 83 6.1 列表类控件 83 6.1.1 Adapter概念 83 6.1.2 AutoComplete 84 6.1.3 Spinner 87 6.1.4 ListView 90 6.1.5 GridView 96 6.1.6 Gallery 99 6.2 Toast 103 6.2.1 文本类型 103 6.2.2 图片类型 104 6.2.3 复合类型 105 6.2.4 自定义显示位置Toast 106 6.3 对话框 107 6.3.1 文本信息对话框 107 6.3.2 简单列表项对话框 109 6.3.3 单选项列表项对话框 111 6.3.4 复选框列表项对话框 113 6.3.5 复杂布局列表项对话框 115 6.4 Android国际化和本地化 118 本章小结 121 第7章 UI布局 122 7.1 FrameLayout 122 7.1.1 TextSwitcher 124 7.1.2 ImageSwitcher 126 7.1.3 DatePicker 129 7.1.4 TimePicker 131 7.1.5 ScrollView 133 7.1.6 选项卡 134 7.2 LinearLayout 138 7.3 RelativeLayout 139 7.4 AbsoluteLayout 141 7.5 TableLayout 143 7.6 布局嵌套 146 7.7 屏幕旋转 152 本章小结 154 第8章 多线程 155 8.1 多线程案例--计时器 155 8.2 线程概念 156 8.2.1 进程概念 156 8.2.2 线程概念 156 8.3 Java中的线程 157 8.3.1 Java中的实现线程体方式1 157 8.3.2 Java中的实现线程体方式2 160 8.3.3 Java中的实现线程体方式3 162 8.4 Android中的线程 163 8.4.1 Android线程应用中的问题与分析 164 8.4.2 Message和MessageQueue 169 8.4.3 Handler 169 8.4.4 Looper和HandlerThread 172 本章小结 178 第9章 Activity和Intent 179 9.1 Activity 179 9.1.1 创建Activity 179 9.1.2 Activity生命周期 180 9.2 Intent 183 9.2.1 显式Intent 184 9.2.2 隐式Intent 186 9.2.3 匹配组件 186 9.3 多Activity之间跳转 188 9.3.1 多个Activity之间数据传递 189 9.3.2 跳转与返回 192 9.3.3 任务与标志 196 9.4 Android系统内置Intent 199 本章小结 201 第10章 数据存储 203 10.1 健康助手案例 203 10.2 Android数据存储概述 205 10.3 本地文件 205 10.3.1 访问SD卡 207 10.3.2 访问应用文件目录 212 10.4 SQLite数据库 216 10.4.1 SQLite数据类型 216 10.4.2 Android平台下管理SQLite数据库 216 10.5 编写访问SQLite数据库组件 220 10.5.1 DBHelper类 220 10.5.2 数据插入 222 10.5.3 数据删除 224 10.5.4 数据修改 224 10.5.5 数据查询 227 10.6 案例重构 229 10.6.1 系统架构设计 229 10.6.2 重构数据访问层 230 10.7 为案例增加参数设置功能 238 10.7.1 Shared Preferences 240 10.7.2 Preferences控件介绍 243 10.7.3 使用Preferences控件的案例 248 本章小结 250 第11章 Content Provider 251 11.1 Content Provider概述 251 11.2 Content URI 252 11.2.1 Content URI含义 252 11.2.2 内置的Content URI 253 11.3 通过Content Provider访问联系人 253 11.3.1 查询联系人 255 11.3.2 通过联系人ID查询联系人的Email 258 11.3.3 按照过滤条件查询Email 259 11.3.4 查询联系人的电话 261 11.4 通过Content Provider访问通话记录 262 11.4.1 查询通话记录 262 11.4.2 按照过滤条件查询通话记录 264 11.5 通过Content Provider访问短信 266 11.6 自定义Content Provider实现数据访问 269 11.6.1 编写Content Provider 269 11.6.2 在不同的应用中调用Content Provider 277 11.6.3 重构Content Provider调用 278 本章小结 281 第12章 多媒体 282 12.1 多媒体文件介绍 282 12.1.1 音频多媒体文件介绍 282 12.1.2 视频多媒体文件介绍 283 12.2 Android音频播放 284 12.2.1 Android音频/视频播放状态 284 12.2.2 音频播放案例介绍 286 12.2.3 资源音频文件播放 287 12.2.4 本地音频文件播放 291 12.2.5 网络音频文件播放 292 12.2.6 完善案例其他功能 293 12.3 Android音频录制 303 12.3.1 Android音频/视频录制状态 303 12.3.2 音频录制案例介绍 303 12.3.3 音频录制案例实现 305 12.4 Android视频播放 309 12.4.1 视频播放案例 309 12.4.2 采用MediaPlayer类播放视频 310 12.4.3 使用VideoView控件重构案例 315 本章小结 316 第13章 Service 317 13.1 Service概述 317 13.1.1 本地Service生命周期 317 13.1.2 远程Service生命周期 318 13.2 本地Service 319 13.2.1 本地Service案例 319 13.2.2 编写AudioService 320 13.2.3 调用Service 322 13.2.4 重构案例 323 13.3 远程Service 325 13.3.1 远程Service调用原理 325 13.3.2 远程Service案例 326 13.3.3 设计AIDL文件 327 13.3.4 编写AudioService 331 13.3.5 调用远程Service 336 13.3.6 组件间参数传递 343 本章小结 347 第14章 Broadcast Receiver和Notification 348 14.1 Broadcast Receiver 348 14.1.1 音频播放案例 349 14.1.2 编写音频播放Broadcast Receiver 350 14.1.3 注册音频播放Broadcast Receiver 351 14.1.4 接收系统的广播 353 14.1.5 MP3下载服务案例 353 14.2 Notification 358 14.2.1 完善MP3下载服务案例 358 14.2.2 完善音频播放案例 363 14.2.3 其他形式的Notification 369 本章小结 371 第15章 云端应用 372 15.1 典型云端应用--城市天气信息服务 372 15.2 网络通信技术与实现 374 15.2.1 网络通信技术介绍 376 15.2.2 Java URL类实现方式 377 15.2.3 Apache HttpClient实现方式 378 15.3 数据交换格式 380 15.3.1 纯文本格式 381 15.3.2 XML格式 381 15.3.3 JSON格式 385 15.4 自定义服务器端程序实例 387 15.4.1 Java Servlet概述 387 15.4.2 编写城市信息服务的Servlet 388 15.4.3 编写城市天气服务的Servlet 393 15.4.4 再次探讨HttpClient的POST请求 395 15.5 云端应用案例优化 400 本章小结 404 第16章 Google Map和定位服务 405 16.1 MyMap服务系统案例 405 16.2 Android Google Map 406 16.2.1 申请Google Map Android API Key 407 16.2.2 编写Android Google Map骨架程序 409 16.2.3 控制地图 412 16.2.4 地图的显示模式 416 16.2.5 地图的图层 419 16.2.6 查询与定位 422 16.3 Android定位服务 430 16.3.1 开启定位服务 431 16.3.2 模拟测试 433 16.3.3 GPS与Google Map结合 435 16.4 案例重构 437 16.4.1 重构"定位查询"方法 438 16.4.2 重构"查询周围"方法 440 本章小结 443 第17章 Android通信应用 444 17.1 电话应用开发 444 17.1.1 拨打电话功能 444 17.1.2 呼入电话状态 446 17.2 短信和彩信应用开发 450 17.2.1 Android内置的发送短信/彩信功能 450 17.2.2 自己编写发送文本内容的短信 452 17.2.3 自己编写接收文本内容的短信 458 17.2.4 自己编写发送二进制内容的短信 459 17.2.5 自己编写接收二进制内容的短信 461 17.3 蓝牙通信 463 17.3.1 Android 2 BluetoothChat案例 464 17.3.2 Android 2 蓝牙API介绍 464 17.3.3 TCP Socket与蓝牙Socket的区别 465 17.3.4 BluetoothChat中的类 466 17.3.5 初始化本地蓝牙设备 467 17.3.6 查找蓝牙设备 471 17.3.7 管理连接 476 17.3.8 互相之间的通信 480 17.4 WiFi通信 484 17.4.1 管理WiFi 484 17.4.2 扫描热点 487 17.4.3 Socket通信 489
Android AIDLAndroid Interface Definition Language)是一种用于定义客户端和服务之间通信接口的语言。AIDL 是一个 Android 特有的 RPC(远程过程调用)机制。 下面是使用 AIDL 的基本步骤: 1. 定义 AIDL 接口 在服务端创建一个 AIDL 文件,定义服务的接口方法。例如,创建一个名为 IMyService.aidl 的文件,其中包含以下内容: ``` interface IMyService { void sayHello(); } ``` 2. 实现 AIDL 接口 在服务端实现 AIDL 接口,例如: ``` public class MyService extends Service { private final IMyService.Stub binder = new IMyService.Stub() { @Override public void sayHello() throws RemoteException { Log.i("MyService", "Hello World"); } }; @Nullable @Override public IBinder onBind(Intent intent) { return binder; } } ``` 3. 绑定服务 在客户端绑定服务,例如: ``` private ServiceConnection connection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { IMyService myService = IMyService.Stub.asInterface(service); try { myService.sayHello(); } catch (RemoteException e) { e.printStackTrace(); } } @Override public void onServiceDisconnected(ComponentName name) { } }; private void bindService() { Intent intent = new Intent(this, MyService.class); bindService(intent, connection, Context.BIND_AUTO_CREATE); } ``` 通过上述步骤,就可以实现客户端与服务端之间的通信。需要注意的是,AIDL 接口中定义的方法必须是可序列化的。如果方法参数或返回值类型不支持序列化,可以通过 Parcelable 接口实现序列化。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ByteSaid

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值