C# 互操作性入门系列(三):平台调用中的数据封送处理

好文章搬用工模式启动ing 。。。。。

{

  文章中已经包含了原文链接 就不再次粘贴了

  言明 改文章是一个系列,但只收录了2篇,原因是 够用了

}

---------------------------------------------------------------------------------------

C#互操作系列文章:

  1. C#互操作性入门系列(一):C#中互操作性介绍
  2. C#互操作性入门系列(二):使用平台调用调用Win32 函数
  3. C#互操作性入门系列(三):平台调用中的数据封送处理
  4. C#互操作性入门系列(四):在C# 中调用COM组件

本专题概要

  • 数据封送介绍
  • 封送Win32数据类型
  • 封送字符串的处理
  • 封送结构体的处理
  • 封送类的处理
  • 小结

一、数据封送介绍

看到这个专题时,大家的第一个疑问肯定是——什么是数据封送呢?(这系列专题中采用假设朋友的提问方式来解说概念,就是希望大家带着问题去学习本专题内容,以及大家在平时的学习过程中也可以采用这个方式,个人觉得这个方式可以使自己学习效率有所提高,即使这样在学习的过程可能会显得慢了,但是这种方式会对你所看过的知识点会有一个更深的印象。远比看的很快,最后却发现记住的没多少强,在这里分享下这个学习方式,认为可以接受的朋友可以在平时的学习中可以尝试下的,如果觉得不好的话,相信大家肯定也会有自己更好的学习方式的。)对于这个问题的解释是,数据封送是——在托管代码中对非托管函数进行互操作时,需要通过方法的参数和返回值在托管内存和非托管内存之间传递数据的过程,数据封送处理的过程是由CLR(公共语言运行时)的封送处理服务(即封送拆送器)完成的

封送拆送器主要进行3项任务:

        1. 将数据从托管类型转换为非托管类型,或从非托管类型转换为托管类型
        2. 将经过类型转换的数据从托管代码内存复制到非托管内存,或从非托管内存复制到托管内存
        3. 调用完成后,释放封送处理过程中分配的内存

二、封送Win32数据类型

对非托管代码进行互操作时,一定会有数据的封送处理。然而封送时需要处理的数据类型分为两种——可直接复制到本机结构中的类型(blittable)和非直接复制到本机结构中的类型(non-bittable)。下面就这两种数据类型分别做一个介绍。

2.1 可直接复制到本机结构中的类型

由于在托管代码和非托管代码中,数据类型在托管内存和非托管内存的表示形式不一样,因为这样的原因,所以我们需要对数据进行封送处理,以至于在托管代码中调用非托管函数时,把正确的传入参数传递给非托管函数和把正确的返回值返回给托管代码中。然而,并不是所有数据类型在两者内存的表现形式不一样的,这时候我们把在托管内存和非托管内存中有相同表现形式的数据类型称为——可直接复制到本机结构中的类型,这些数据类型不需要封送拆送器进行任何特殊的处理就可以在托管和非托管代码之间传递, 下面列出一些课直接复制到本机结构中的简单数据类型:


Windows 数据类型


非托管数据类型


托管数据类型


托管数据类型解释


BYTE/Uchar/UInt8


unsigned char


System.Byte


无符号8位整型


Sbyte/Char/Int8


char


System.SByte


有符号8位整型


Short/Int16


short


System.Int16


有符号16位整型


USHORT/WORD/UInt16/WCHAR


unsigned short


System.UInt16


无符号16位整型


Bool/HResult/Int/Long


long/int


System.Int32


有符号32位整型


DWORD/ULONG/UINT


unsigned long/unsigned int


System.UInt32


无符号32位整型


INT64/LONGLONG


_int64


System.Int64


有符号64位整型


UINT64/DWORDLONG/ULONGLONG


_uint64


System.UInt64


无符号64位整型


INT_PTR/hANDLE/wPARAM


void*/int或_int64


System.IntPtr


有符号指针类型


HANDLE


void*


System.UIntPtr


无符号指针类型


FLOAT


float


System.Single


单精度浮点数


DOUBLE


double


System.Double


双精度浮点数

除了上表列出来的简单类型之外,还有一些复制类型也属于可直接复制到本机结构中的数据类型:

(1) 数据元素都是可直接复制到本机结构中的一元数组,如整数数组,浮点数组等

(2)只包含可直接复制到本机结构中的格式化值类型

(3)成员变量全部都是可复制到本机结构中的类型且作为格式化类型封送的类

上面提到的格式化指的是——在类型定义时,成员的内存布局在声明时就明确指定的类型。在代码中用StructLayout属性修饰被指定的类型,并将StructLayout的LayoutKind属性设置为Sequential或Explicit,例如:

using System.Runtime.InteropServices;

// 下面的结构体也属于可直接复制到本机结构中的类型
[StructLayout(LayoutKind.Sequential)]
public struct Point {
   public int x;
   public int y;
}   

2.2 非直接复制到本机结构中的类型

如果一个类型不是可直接复制到本机结构中的类型,那么它就是非直接复制到本机结构中的类型。由于一些类型在托管内存和非托管内存的表现形式不一样,所以对于这种类型,封送器需要对它们进行相应的类型转换之后再复制到被调用的函数中,下面列出一些非直接复制到本机结构中的数据类型:


Windows 数据类型


非托管数据类型


托管数据类型


托管数据类型解释


Bool


bool


System.Boolean


布尔类型


WCHAR/TCHAR


char/ wchar_t


System.Char


ANSI字符/Unicode字符


LPCSTR/LPCWSTR/LPCTSTR/LPSTR/LPWSTR/LPTSTR


const char*/const wchar_t*/char*/wchar_t*


System.String


ANSI字符串/Unicode字符串,如果非托管代码不需要更新此字符串时,此时用String类型在托管代码中声明字符串类型


LPSTR/LPWSTR/LPTSTR


Char*/wchar_t*


System.StringBuilder


ANSI字符串/Unicode字符串,如果非托管代码需要更新此字符串,然后把更新的字符串传回托管代码中,此时用StringBuilder类型在托管代码中声明字符串

除了上表中列出的类型之外,还有很多其他类型属于非直接复制到本机结构中的类型,例如其他指针类型和句柄类型等。理解了blittable和non-blittable类型的区别之后,就可以在互操作过程更好地处理数据的封送,下面就具体的一些数据类型的封送问题做一个简单介绍

三、封送字符串的处理

在上一个专题中,我们已经涉及到字符串的封送问题了(上一个专题中使用了将字符串作为In参数传递给Win32 MessageBox 函数,具体可以查看上一个专题) 。所以在这部分将介绍——封送作为返回值的字符串,下面是一段演示代码,代码中主要是调用Win32 GetTempPath函数来获得返回返回临时路径,此时拆送器就需要把返回的字符串封送回托管代码中。

// 托管函数中的返回值封送回托管函数的例子
    class Program
    {

        // Win32 GetTempPath函数的定义如下:
//DWORD WINAPI GetTempPath(
//  _In_   DWORD nBufferLength,
//  _Out_  LPTSTR lpBuffer
//);    // 主要是注意如何在托管代码中定义该函数原型               [DllImport("Kernel32.dll", CharSet = CharSet.Unicode, SetLastError=true)]
        public static extern uint GetTempPath(int bufferLength, StringBuilder buffer);
        static void Main(string[] args)
        {
            StringBuilder buffer = new StringBuilder(300);
            uint tempPath=GetTempPath(300, buffer);
            string path = buffer.ToString();
            if (tempPath == 0)
            {
                int errorcode =Marshal.GetLastWin32Error();
                Win32Exception win32expection = new Win32Exception(errorcode);
                Console.WriteLine("调用非托管函数发生异常,异常信息为:" +win32expection.Message);
            }

            Console.WriteLine("调用非托管函数成功。");
            Console.WriteLine("Temp 路径为:" + buffer);
            Console.Read();
        }
    }

运行结果为:

四、封送结构体的处理

在我们实际调用Win32 API函数时,经常需要封送结构体和类等复制类型,下面就以Win32
函数GetVersionEx为例子来演示如何对作为参数的结构体进行封送处理。为了在托管代码中调用非托管代码,首先我们就要知道非托管函数的定义,下面是GetVersionEx非托管定义(更多关于该函数的信息可以参看MSDN链接:http://msdn.microsoft.com/en-us/library/ms885648.aspx ):

BOOL GetVersionEx(
  LPOSVERSIONINFO lpVersionInformation
); 

参数lpVersionInformation是一个指向 OSVERSIONINFO结构体的指针类型,所以我们在托管代码中为函数GetVersionEx函数之前,必须知道 OSVERSIONINFO结构体的非托管定义,然后再在托管代码中定义一个等价的结构体类型作为参数。以下是OSVERSIONINFO结构体的非托管定义:

typedef struct  _OSVERSIONINFO{
    DWORD dwOSVersionInfoSize;       //在使用GetVersionEx之前要将此初始化为结构的大小
    DWORD dwMajorVersion;               //系统主版本号
    DWORD dwMinorVersion;               //系统次版本号
    DWORD dwBuildNumber;               //系统构建号
    DWORD dwPlatformId;                  //系统支持的平台
    TCHAR szCSDVersion[128];          //系统补丁包的名称
    WORD wServicePackMajor;            //系统补丁包的主版本
    WORD wServicePackMinor;            //系统补丁包的次版本
    WORD wSuiteMask;                      //标识系统上的程序组
    BYTE wProductType;                    //标识系统类型
    BYTE wReserved;                         //保留,未使用
} OSVERSIONINFO;

知道了OSVERSIONINFO结构体在非托管代码中的定义之后, 现在我们就需要在托管代码中定义一个等价的结构,并且要保证两个结构体在内存中的布局相同。托管代码中的结构体定义如下:

  // 因为Win32 GetVersionEx函数参数lpVersionInformation是一个指向 OSVERSIONINFO的数据结构
        // 所以托管代码中定义个结构体,把结构体对象作为非托管函数参数
        [StructLayout(LayoutKind.Sequential,CharSet=CharSet.Unicode)]
        public struct OSVersionInfo
        {
            public UInt32 OSVersionInfoSize; // 结构的大小,在调用方法前要初始化该字段
            public UInt32 MajorVersion; // 系统主版本号
            public UInt32 MinorVersion; // 系统此版本号
            public UInt32 BuildNumber;  // 系统构建号
            public UInt32 PlatformId;  // 系统支持的平台

            // 此属性用于表示将其封送成内联数组
            [MarshalAs(UnmanagedType.ByValTStr,SizeConst=128)]
            public string CSDVersion; // 系统补丁包的名称
            public UInt16 ServicePackMajor; // 系统补丁包的主版本
            public UInt16 ServicePackMinor;  // 系统补丁包的次版本
            public UInt16 SuiteMask;   //标识系统上的程序组
            public Byte ProductType;    //标识系统类型
            public Byte Reserved;  //保留,未使用
        }

从上面的定义可以看出, 托管代码中定义的结构体有以下三个方面与非托管代码中的结构体是相同的:

  • 字段声明的顺序
  • 字段的类型
  • 字段在内存中的大小

并且在上面结构体的定义中,我们使用到了 StructLayout 属性,该属性属于System.Runtime.InteropServices命名空间(所以在使用平台调用技术必须添加这个额外的命名空间)。这个类的作用就是允许开发人员显式指定结构体或类中数据字段的内存布局,为了保证结构体中的数据字段在内存中的顺序与定义时一致,所以指定为 LayoutKind.Sequential(该枚举也是默认值)。 下面就具体看看在托管代码中调用的代码:

using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
namespace 封送结构体的处理
{
    class Program
    {
        // 对GetVersionEx进行托管定义
        // 为了传递指向结构体的指针并将初始化的信息传递给非托管代码,需要用ref关键字修饰参数        // 这里不能使用out关键字,如果使用了out关键字,CLR就不会对参数进行初始化操作,这样就会导致调用失败        [DllImport("Kernel32",CharSet=CharSet.Unicode,EntryPoint="GetVersionEx")]
        private static extern Boolean GetVersionEx_Struct(ref  OSVersionInfo osVersionInfo);

        // 因为Win32 GetVersionEx函数参数lpVersionInformation是一个指向 OSVERSIONINFO的数据结构
        // 所以托管代码中定义个结构体,把结构体对象作为非托管函数参数
        [StructLayout(LayoutKind.Sequential,CharSet=CharSet.Unicode)]
        public struct OSVersionInfo
        {
            public UInt32 OSVersionInfoSize; // 结构的大小,在调用方法前要初始化该字段
            public UInt32 MajorVersion; // 系统主版本号
            public UInt32 MinorVersion; // 系统此版本号
            public UInt32 BuildNumber;  // 系统构建号
            public UInt32 PlatformId;  // 系统支持的平台

            // 此属性用于表示将其封送成内联数组
            [MarshalAs(UnmanagedType.ByValTStr,SizeConst=128)]
            public string CSDVersion; // 系统补丁包的名称
            public UInt16 ServicePackMajor; // 系统补丁包的主版本
            public UInt16 ServicePackMinor;  // 系统补丁包的次版本
            public UInt16 SuiteMask;   //标识系统上的程序组
            public Byte ProductType;    //标识系统类型
            public Byte Reserved;  //保留,未使用
        }

        // 获得操作系统信息
        private static string GetOSVersion()
        {
            // 定义一个字符串存储版本信息
            string versionName = string.Empty;

            // 初始化一个结构体对象
            OSVersionInfo osVersionInformation = new OSVersionInfo();

            // 调用GetVersionEx 方法前,必须用SizeOf方法设置结构体中OSVersionInfoSize 成员
            osVersionInformation.OSVersionInfoSize = (UInt32)Marshal.SizeOf(typeof(OSVersionInfo));

            // 调用Win32函数
            Boolean result = GetVersionEx_Struct(ref osVersionInformation);

            if (!result)
            {
                // 如果调用失败,获得最后的错误码
                int errorcode = Marshal.GetLastWin32Error();
                Win32Exception win32Exc = new Win32Exception(errorcode);
                Console.WriteLine("调用失败的错误信息为: " + win32Exc.Message);

                // 调用失败时返回为空字符串
                return string.Empty;
            }
            else
            {
                Console.WriteLine("调用成功");
                switch (osVersionInformation.MajorVersion)
                {
                    // 这里仅仅讨论 主版本号为6的情况,其他情况是一样讨论的
                    case 6:
                        switch (osVersionInformation.MinorVersion)
                        {
                            case 0:
                                if (osVersionInformation.ProductType == (Byte)0)
                                {
                                    versionName = " Microsoft Windows Vista";
                                }
                                else
                                {
                                    versionName = "Microsoft Windows Server 2008"; // 服务器版本
                                }
                                break;
                            case 1:
                                if (osVersionInformation.ProductType == (Byte)0)
                                {
                                    versionName = " Microsoft Windows 7";
                                }
                                else
                                {
                                    versionName = "Microsoft Windows Server 2008 R2";
                                }
                                break;
                            case 2:
                                versionName = "Microsoft Windows 8";
                                break;
                        }
                        break;
                    default:
                        versionName = "未知的操作系统";
                        break;
                }
                return versionName;
            }
        }

        static void Main(string[] args)
        {
            string OS=GetOSVersion();
            Console.WriteLine("当前电脑安装的操作系统为:{0}", OS);
            Console.Read();
        }
    }
}

运行结果为:

附上微软操作系统名和版本号的对应关系,大家可以参考下面的表对上面代码进行其他的讨论:


操作系统


版本号


Windows 8


6.2


Windows 7


6.1


Windows Server   2008 R2


6.1


Windows Server   2008


6.0


Windows Vista


6.0


Windows Server   2003 R2


5.2


Windows Server   2003


5.2


Windows XP


5.1


Windows 2000


5.0

五、封送类的处理

下面直接通过GetVersionEx函数进行封送类的处理的例子,具体代码如下:

using System;
using System.ComponentModel;
using System.Runtime.InteropServices;

namespace 封送类的处理
{
    class Program
    {
        // 对GetVersionEx进行托管定义
        // 由于类的定义中CSDVersion为String类型,String是非直接复制到本机结构类型,
        // 所以封送拆送器需要进行复制操作。
        // 为了是非托管代码能够获得在托管代码中对象设置的初始值(指的是OSVersionInfoSize字段,调用函数前首先初始化该值),
        // 所以必须加上[In]属性;函数返回时,为了将结果复制到托管对象中,必须同时加上 [Out]属性
        // 这里不能是用ref关键字,因为 OsVersionInfo是类类型,本来就是引用类型,如果加ref 关键字就是传入的为指针的指针了,这样就会导致调用失败        [DllImport("Kernel32", CharSet = CharSet.Unicode, EntryPoint = "GetVersionEx")]
        private static extern Boolean GetVersionEx_Struct([In, Out]  OSVersionInfo osVersionInfo);

        // 获得操作系统信息
        private static string GetOSVersion()
        {
            // 定义一个字符串存储操作系统信息
            string versionName = string.Empty;

            // 初始化一个类对象
            OSVersionInfo osVersionInformation = new OSVersionInfo();

            // 调用Win32函数
            Boolean result = GetVersionEx_Struct(osVersionInformation);

            if (!result)
            {
                // 如果调用失败,获得最后的错误码
                int errorcode = Marshal.GetLastWin32Error();
                Win32Exception win32Exc = new Win32Exception(errorcode);
                Console.WriteLine("调用失败的错误信息为: " + win32Exc.Message);

                // 调用失败时返回为空字符串
                return string.Empty;
            }
            else
            {
                Console.WriteLine("调用成功");
                switch (osVersionInformation.MajorVersion)
                {
                    // 这里仅仅讨论 主版本号为6的情况,其他情况是一样讨论的
                    case 6:
                        switch (osVersionInformation.MinorVersion)
                        {
                            case 0:
                                if (osVersionInformation.ProductType == (Byte)0)
                                {
                                    versionName = " Microsoft Windows Vista";
                                }
                                else
                                {
                                    versionName = "Microsoft Windows Server 2008"; // 服务器版本
                                }
                                break;
                            case 1:
                                if (osVersionInformation.ProductType == (Byte)0)
                                {
                                    versionName = " Microsoft Windows 7";
                                }
                                else
                                {
                                    versionName = "Microsoft Windows Server 2008 R2";
                                }
                                break;
                            case 2:
                                versionName = "Microsoft Windows 8";
                                break;
                        }
                        break;
                    default:
                        versionName = "未知的操作系统";
                        break;
                }
                return versionName;
            }
        }

        static void Main(string[] args)
        {
            string OS = GetOSVersion();
            Console.WriteLine("当前电脑安装的操作系统为:{0}", OS);
            Console.Read();
        }
    }

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
    public class OSVersionInfo
    {
        public UInt32 OSVersionInfoSize = (UInt32)Marshal.SizeOf(typeof(OSVersionInfo));
        public UInt32 MajorVersion = 0;
        public UInt32 MinorVersion = 0;
        public UInt32 BuildNumber = 0;
        public UInt32 PlatformId = 0;

        // 此属性用于表示将其封送成内联数组
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
        public string CSDVersion = null;

        public UInt16 ServicePackMajor = 0;
        public UInt16 ServicePackMinor = 0;
        public UInt16 SuiteMask = 0;

        public Byte ProductType = 0;
        public Byte Reserved;
    }
}

运行结果还是和上面使用结构体定义的一样,还是附上下图吧:

六、小结

本专题主要介绍了几种类型的数据封送处理, 对于封送处理的一句话概括就是——保证托管代码中定义的数据在内存中的布局与非托管代码中的内存布局相同,专题中也列出了一些简单类型在非托管代码和托管代码中定义的对应关系,对于一些没有列出来的指针类型或回调函数等可以使用万能的IntPtr类型在托管代码中定义.然而本专题只是对数据封送做一个入门的介绍, 要真真掌握数据封送处理还要考虑很多其他的因素,这个就需要大家在平时工作中积累的。

时间: 2024-10-24 21:48:46

C# 互操作性入门系列(三):平台调用中的数据封送处理的相关文章

[转]C# 互操作性入门系列(三):平台调用中的数据封送处理

传送门 C#互操作系列文章: C#互操作性入门系列(一):C#中互操作性介绍 C#互操作性入门系列(二):使用平台调用调用Win32 函数 C#互操作性入门系列(三):平台调用中的数据封送处理 C#互操作性入门系列(四):在C# 中调用COM组件 本专题概要 数据封送介绍 封送Win32数据类型 封送字符串的处理 封送结构体的处理 封送类的处理 小结 一.数据封送介绍 看到这个专题时,大家的第一个疑问肯定是--什么是数据封送呢?(这系列专题中采用假设朋友的提问方式来解说概念,就是希望大家带着问题

[转]C# 互操作性入门系列(四):在C# 中调用COM组件

传送门 C#互操作系列文章: C#互操作性入门系列(一):C#中互操作性介绍 C#互操作性入门系列(二):使用平台调用调用Win32 函数 C# 互操作性入门系列(三):平台调用中的数据封送处理 C#互操作性入门系列(四):在C# 中调用COM组件 本专题概要: 引言 如何在C#中调用COM组件--访问Office 互操作对象 在C# 中调用COM组件的实现原理剖析 错误处理 小结 一.引言 COM(Component Object Modele,组件对象模型)是微软以前推崇的一个开发技术,所以

[转]C# 互操作性入门系列(一):C#中互操作性介绍

传送门 C#互操作系列文章: C# 互操作性入门系列(一):C#中互操作性介绍 C# 互操作性入门系列(二):使用平台调用调用Win32 函数 C# 互操作性入门系列(三):平台调用中的数据封送处理 C# 互操作性入门系列(四):在C#中调用COM组件 本专题概要: 引言 平台调用 C++ Interop(互操作) COM Interop(互操作) 一.引言   这个系列是在C#基础知识中遗留下来的一个系列的,因为在C# 4.0中的一个新特性就是对COM互操作改进,然而COM互操作性却是.NET

C# 互操作性入门系列(二):使用平台调用调用Win32 函数

好文章搬用工模式启动ing ..... { 文章中已经包含了原文链接 就不再次粘贴了 言明 改文章是一个系列,但只收录了2篇,原因是 够用了 } --------------------------------------------------------------------------------------- C#互操作系列文章: C#互操作性入门系列(一):C#中互操作性介绍 C#互操作性入门系列(二):使用平台调用调用Win32 函数 C#互操作性入门系列(三):平台调用中的数据封

Xen入门系列三【Xen 管理工具 xm】

xm命令是管理Xen的最基本的工具,可以通过xm --help 来获得帮助. 1. 列出所有正在运行的虚拟操作系统 # xm list PS[1]:可缩写为 xm li2. 启动虚拟机 # 通过配置文件启动虚拟机 # xm create <ConfigFile> # 通过虚拟机名称启动虚拟机,虚拟机必需已存在 xm list 中 # xm start <DomainName> 参数说明: ConfigFile:虚拟机配置文件 DomainName: 虚拟机名称 PS[2]:很多教程

RxJava入门系列三,响应式编程

RxJava入门系列三,响应式编程 在RxJava入门系列一,我向你介绍了RxJava的基础架构.RxJava入门系列二,我向你展示了RxJava提供的多种牛逼操作符.但是你可能仍然没能劝服自己使用RxJava,这一篇博客里我将向你展示RxJava提供的其他优势,没准了解了这些优势,你就真的想去使用RxJava了. 异常处理 直到目前为止,我都没有去介绍onComplete()和onError()方法.这两个方法是用来停止Observable继续发出事件并告知观察者为什么停止(是正常的停止还是因

C语言快速入门系列(三)

C语言快速入门系列(三) 结构化的程序设计 -----------------------------------转载请注明出处:coder-pig 本节引言: 在前面的学习中,我们对C语言的基本语法进行了了解,可以暂时理解成我们学了单词; 现在要做得就是学语法,也就是算法;就是构成一个一个基本的程序! 在这一节中我们要学习的是C语言中的输入输出,以及程序的三种结构(顺序,选择,循环结构) 本节学习路线图: 正文: 1.字符输入/输出函数 2.格式输入/输出函数 跟前面的单个字符的输入输出不同,

mybatis入门系列三之类型转换器

mybatis入门系列三之类型转换器 类型转换器介绍 mybatis作为一个ORM框架,要求java中的对象与数据库中的表记录应该对应 因此java类名-数据库表名,java类属性名-数据库表字段名,java类属性类型-数据库字段类型 前面两个都容易设置,但是第三点要求经常会出现java类型和数据库的存储类型不一样, 例如java类型是String,数据库中存储的是char.varchar.text 对于一般常见的类型对应,mybatis已经内部包含了类型转换器,使String类型的java属性

RxJava入门系列四,Android中的响应式编程

RxJava入门系列四,Android中的响应式编程 在入门系列1,2,3中,我基本介绍了RxJava是如何使用的.但是作为一名Android开发人员,你怎么让RxJava能为你所用呢?这篇博客我将针对Android开发来介绍一下RxJava的使用场景. RxAndroid RxAndroid是为Android打造的RxJava扩展.通过RxAndroid可以让你的Android开发变得更轻松. 首先,RxAndroid中提供了AndroidSchedulers,你可以用它来切换Android线