android的binder機制研究

來源:互聯網
上載者:User

(一) 概述   
    android的binder機制提供一種處理序間通訊的方法,使一個進程可以以類似遠端程序呼叫的形式調用另一個進程所提供的功能。binder機制在Java環境和C/C++環境都有提供。
    android的代碼中,與C/C++的binder包括一些類型和介面的定義和實現,相關的代碼在下面這幾個檔案中:

  1.     frameworks/base/include/binder/IInterface.h
  2.     frameworks/base/include/binder/Binder.h
  3.     frameworks/base/include/binder/BpBinder.h
  4.     frameworks/base/include/binder/IBinder
  5.     frameworks/base/include/binder/Parcel.h
  6.     frameworks/base/include/binder/IPCThreadState.h
  7.     frameworks/base/include/binder/ProcessState.h
  8.     frameworks/base/libs/binder/Binder.cpp
  9.     frameworks/base/libs/binder/BpBinder.cpp
  10.     frameworks/base/libs/binder/IInterface.cpp
  11.     frameworks/base/libs/binder/IPCThreadState.cpp
  12.     frameworks/base/libs/binder/Parcel.cpp
  13.     frameworks/base/libs/binder/ProcessState.cpp

複製代碼

   為了瞭解這些類、介面之間的關係以及binder的實現機制,最好是結合一個例子來進行研究。我選擇的例子是android內建的媒體播放器的實現。其媒體播放器的相關代碼在下面這些目錄中:

  1.    frameworks/base/include/media
  2.    frameworks/base/media

複製代碼

  使用startUML的反向工程功能分析上面這些代碼,並進行了一定的整理之後,得到下面這幅類圖(點擊可查看原尺寸圖片)。

2010-12-30 09:06 上傳

下載附件 (230.18 KB)

android的媒體播放功能分成兩部分,一部分是媒體播放應用,一部分是媒體播放服務(MediaServer,在系統啟動時由init所啟動,具可參考init.rc檔案)。這兩部分分別跑在不同的進程中。媒體播放應用程式套件括Java程式和部分C++代碼,媒體播放服務是C++代碼,並且需要調用外部模組opencore來實現真正的媒體播放。媒體播放應用和媒體播放服務之間需要通過binder機制來進行相互調用,這些調用包括:
   (1)媒體播放應用向媒體播放服務發控制指令
   (2)媒體播放服務向媒體播放應用發事件通知(notify)
   媒體播放服務對外提供多個介面,在上面得圖中包括其中的2個介面:IMediaService和IMediaPlayer,IMediaplayer用於建立和管理播放執行個體,而IMediaplayer介面則是播放介面,用於實現指定媒體檔案的播放以及播放過程的控制。
   上面的圖中還有媒體播放應用向媒體播放服務提供的1個介面:IMediaPlayerClient,用於接收notify。
   這些介面因為需要跨進程調用,因此需要用到binder機制。每個介面包括兩部分實現,一部分是介面功能的真正實現(BnInterface),這部分運行在介面提供進程中;另一部分是介面的proxy(BpInterface),這部分運行在調用介面的進程中。binder的作用就是讓這兩部分之間建立聯絡。是整個播放器的一個概要說明。

2010-12-30 09:09 上傳

下載附件 (39.27 KB)

媒體播放器比較複雜一些,總共實現了3個介面,不過要瞭解binder的機制,只需要研究其中一個介面就足夠了。在這裡選擇IMediaPlayerService介面來看一下。
     IMediaPlayerService介面包括六個功能函數:create(url)、create(fd)、decode(url)、decode(fd)、createMediaRecord()、createMetadataRetriever()。在這裡不介紹這些函數是做什麼的,我們只關注如何通過binder還提供這些函數介面。
(二) 介面定義
(1) 定義介面類

     首先定義IMediaPlayerService類,這是一個介面類(C++的術語應該叫純虛類)。該介面類定義在檔案frameworks/base/include/media/IMediaPlayerService.h。代碼如下:

  1. class IMediaPlayerService: public IInterface
  2. {
  3. public:
  4.     DECLARE_META_INTERFACE(MediaPlayerService);
  5.     virtual sp<IMediaRecorder>  createMediaRecorder(pid_t pid) = 0;
  6.     virtual sp<IMediaMetadataRetriever> createMetadataRetriever(pid_t pid) = 0;
  7.     virtual sp<IMediaPlayer>    create(pid_t pid, const sp<IMediaPlayerClient>& client, const char* url) = 0;
  8.     virtual sp<IMediaPlayer>    create(pid_t pid, const sp<IMediaPlayerClient>& client, int fd, int64_t offset, int64_t length) = 0;
  9.     virtual sp<IMemory>         decode(const char* url, uint32_t *pSampleRate, int* pNumChannels, int* pFormat) = 0;
  10.     virtual sp<IMemory>         decode(int fd, int64_t offset, int64_t length, uint32_t *pSampleRate, int* pNumChannels, int* pFormat) = 0;
  11. };

複製代碼

可以看到,在這個介面類中定義了IMediaPlayerService需要提供的6個函數介面,因為是介面類,所以定義為純虛函數。需要注意這個介面類的名稱有嚴格要求,必須是以大寫字母I開始。

    重點關注在這些函數前面的一個宏定義: DECLARE_META_INTERFACE(MediaPlayerService)。這個宏定義必須要有,其中封裝了實現binder所需要的一些類成員變數和成員函數通過這些成員函數可以為一個binder實現建立proxy。這個宏定義在問價frameworks/base/include/utils/IInterface.h裡,在後面還會講到。這個宏定義的參數必須是介面類的名稱去除字母I後剩下的部分。
    另外說明一下,可以看到介面類中所定義的函數的傳回值都是sp<xxxx>的形式,看起來有點怪異。sp是android中定義的一個模板類,用於實現智能指標功能。sp<IMediaPlayer>就是IMediaPlayer的智能指標,可以簡單地把它看成是標準C++中的指標定義即 IMediaPlayer* 即可。

(2) 定義和實現binder類

    binder類包括兩個,一個是介面實作類別,一個介面代理類。介面代理類繼承自BpInterface,介面實作類別繼承自BnInterface。這兩個基類都是模板類,封裝了binder的處理序間通訊機制,這樣使用者無需關注底層通訊實現細節。
    對於IMediaPlayerService介面,其binder介面實作類別為BnMediaPlayerService,介面代理類為BpMediaPlayerService。需注意這兩個類的名稱有嚴格要求,必須以Bn和Bp開頭,並且後面的部分必須是前面所定義的介面類的名稱去除字母'I’。比如前面所定義的介面類為IMediaPlayerService,去除字母I後是MediaPlayerService,所以兩個binder類的名稱分別是BnMediaPlayerService和BpMediaPlayerService。為什麼有這樣的要求?原因就在前面提到的宏定義DECLARE_META_INTERFACE()和另一個宏定義IMPLEMENT_META_INTERFACE()裡面。有興趣的話可以去看一下,這兩個宏定義都在檔案frameworks/base/include/utils/IInterface.h裡。
    BpMediaPlayerService是一個最終實作類別。定義並且實現在在檔案frameworks/base/media/libmidia/IMediaPlayerService.cpp中。在看BpMediaPlayerService的代碼之前,先看一下在IMediaPlayerService.cpp檔案的開始部分的一個枚舉定義:

  1. enum {
  2.     CREATE_URL = IBinder::FIRST_CALL_TRANSACTION,
  3.     CREATE_FD,
  4.     DECODE_URL,
  5.     DECODE_FD,
  6.     CREATE_MEDIA_RECORDER,
  7.     CREATE_METADATA_RETRIEVER,
  8. };

複製代碼

這些6個枚舉定義對應於IMediaPlayerService介面所提供的6個功能函數,可以稱為這些功能函數的功能代碼,用於在進程之間進行RPC是標識需要調用哪個函數。如果不想定義這些枚舉值,在後面需要用到這些值的地方直接寫上1,2,3,4,5,6也是可以的,不過……一個合適的程式員會這麼幹嗎?
    下面看一下BpMediaPlayerService的代碼。
(3) BpMediaPlayerService程式碼分析

  1. class BpMediaPlayerService: public BpInterface<IMediaPlayerService>
  2. {
  3. public:
  4.     BpMediaPlayerService(const sp<IBinder>& impl) : BpInterface<IMediaPlayerService>(impl)
  5.     {
  6.     }
  7. //……省略部分
  8.     virtual sp<IMediaPlayer> create(pid_t pid, const sp<IMediaPlayerClient>& client, const char* url)
  9.     {
  10.         Parcel data, reply;
  11.         data.writeInterfaceToken(IMediaPlayerService::getInterfaceDescriptor());
  12.         data.writeInt32(pid);
  13.         data.writeStrongBinder(client->asBinder());
  14.         data.writeCString(url);
  15.         remote()->transact(CREATE_URL, data, &reply);
  16.         return interface_cast<IMediaPlayer>(reply.readStrongBinder());
  17.     }
  18. //……省略部分
  19. };

複製代碼

首先可以看到,這個類繼承自模板類BpInterface,指定類型為介面類IMediaPlayerService。BpInterface模板類定義在檔案IInterface.h。看一下BpInterface的定義就可以發現,BpMediaPlayerService這樣定義了以後,事實上間接繼承了IMediaPlayerService,從而可以提供IMediaPlayerService介面所定義的介面函數。BpMediaPlayerService需要實現這些介面函數。在一個簡單的建構函式之後,就是這些介面函數的實現。可以看到,所有的介面函數的實現方法都是一致的,都是通過binder所提供的機制將參數仍給binder的實作類別,並擷取傳回值。這也就是這個類之所以成為代理類的原因。下面具體看一下一個介面函數。這裡選的是函數create(url)。

  1. virtual sp<IMediaPlayer> create(pid_t pid, const sp<IMediaPlayerClient>& client, const char* url)
  2. {
  3.         Parcel data, reply;
  4.         data.writeInterfaceToken(IMediaPlayerService::getInterfaceDescriptor());
  5.         data.writeInt32(pid);
  6.         data.writeStrongBinder(client->asBinder());
  7.         data.writeCString(url);
  8.         remote()->transact(CREATE_URL, data, &reply);
  9.         return interface_cast<IMediaPlayer>(reply.readStrongBinder());
  10. }

複製代碼

這個介面函數的參數指定了一個URL,函數將為這個URL建立一個播放器執行個體用於播放該URL。
    函數首先定義了兩個局部變數data和reply,變數的類型都是Parcel。Parcel是一個專為binder通訊的資料傳送而定義的類,該類提供了對多種類型的資料的封裝功能,同時提供多個資料讀取和寫入函數,用於多種類型的資料的寫入和讀取,支援的資料類型既包括單一資料型別,也包括對象。這裡定義的變數data是用於封裝create()函數調用所需要的輸入參數,而reply則是用於封裝調用的返回資料(包括輸出參數的值和函數傳回值)。
    函數首先向data中寫入各種資料。第一個寫入的是介面的一個描述字串,binder的實作類別中會用這個字串來對介面做驗證,防止調用錯誤。這個字串也可以不寫,如果不寫,在binder實作類別中相應的也就不要做驗證了。跟在描述字串後面寫入的是該介面函數所需要的各種的輸入參數。需要說明的是,Pacel提供一種先入先出的資料存放區方式,即資料的寫入順序和讀取順序必須嚴格一致,否則將會出錯。
    完成資料寫入後,函數調用remote()->transact()用於完成binder通訊。transact()函數的第一個參數就是前面提到過的功能代碼。transact()的功能是將data中的資料傳給binder的實作類別,函數調用結束後,reply中將包含返回資料。首先來看看remote()成員函數。前面講到過BpMediaPlayerService通過繼承BpInterface模板類間接繼承了IMediaPlayerService介面類,其實BpInterface類是一個有兩個父類的多重繼承子類,另一個父類是BpRefbase(frameworks/base/include/utils/Binder.h)。remote()就是繼承自BpRefBase類的一個成員函數,該函數返回BpRefBase類中定義的一個私人屬性mRemote。mRemote是對IBinder介面類的子類BpBinder的一個對象的引用(參考前面的類別關係圖)。transact()函數在IBinder介面類中定義(frameworks/base/include/utils/Binder.h),並在BpBinder類中實現(frameworks/base/include/utils/BpBinder.h、frameworks/base/libs/utils/BpBinder.cpp)。在transact()函數中將調用IPCThreadState類的transact()函數,並進而通過Lniux核心中的android共用記憶體驅動來實現處理序間通訊。不過這些細節這裡就不多說了。在這裡BpBinder類對象是一個關鍵,是實現Binder代理的核心之一。BpBinder類可以看成是一個通訊handle(類似於網路編程中的socket),用於實現處理序間通訊。接下來需要研究的是這個BpBinder類對象(即mRemote成員變數的值)是從哪裡來的。
    回過頭來BpMediaPlayerService的建構函式(看前面的代碼)。該建構函式的參數是一個IBinder對象的引用。mRemote的值就是在這裡傳進來的這個對象。那麼這個對象又是怎麼來的呢?要搞清楚這一點就需要找到建立BpMediaPlayerService類的執行個體的代碼,這個代碼就就跟在該類的定義代碼的下面。繼續看IMediaPlayerService.cpp檔案,在BpMediaPlayerService類定義的後面,是下面這樣一行代碼:

  1. IMPLEMENT_META_INTERFACE(MediaPlayerService, "android.hardware.IMediaPlayerService");

複製代碼

這行代碼調用了一個宏定義IMPLEMENT_META_INTERFACE()。這個宏定義與前面提到過的DECLARE_META_INTERFACE()相呼應。看名字就知道,IMPLEMENT_META_INTERFACE()宏是對DECLARE_META_INTERFACE()所定義的成員函數的具體實現。這個宏的第一個參數與DECLARE_META_INTERFACE()的參數需完全一樣,第二參數是介面的描述字串(這個字串前面也已經講到過了)。描述字串不重要,重要的是宏裡面定義的一個靜態成員函數asInterface()。BpMediaPlayerService的類執行個體是在IMediaPlayerService的靜態成員函數asInterface()中建立的,在IInterface.h中定義了一個內嵌函式interface_cast(),對這個成員函數進行了封裝。通過看代碼容易知道,BpMediaPlayerService的建構函式的參數是通過interface_cast()的參數傳進來的。
    好,下面就該看看這個interface_cast()是在哪裡調用的,它的參數到底是什麼。找到frameworks/base/media/libmedia/IMediaDeathNotifier.cpp檔案,其中的IMediaDeathNotifier::getMediaPlayerService()的實現代碼:

  1. IMediaDeathNotifier::getMediaPlayerService()
  2. {
  3.     LOGV("getMediaPlayerService");
  4.     Mutex::Autolock _l(sServiceLock);
  5.     if (sMediaPlayerService.get() == 0) {
  6.         sp<IServiceManager> sm = defaultServiceManager();
  7.         sp<IBinder> binder;
  8.         do {
  9.             binder = sm->getService(String16("media.player"));
  10.             if (binder != 0) {
  11.                 break;
  12.              }
  13.              LOGW("Media player service not published, waiting...");
  14.              usleep(500000); // 0.5 s
  15.         } while(true);
  16.         if (sDeathNotifier == NULL) {
  17.         sDeathNotifier = new DeathNotifier();
  18.     }
  19.     binder->linkToDeath(sDeathNotifier);
  20.     sMediaPlayerService = interface_cast<IMediaPlayerService>(binder);
  21.     }
  22.     LOGE_IF(sMediaPlayerService == 0, "no media player service!?");
  23.     return sMediaPlayerService;
  24. }

複製代碼

看一下上面這段代碼中的紅色字型部分。結合前面的分析,可知BpBinder類的對象執行個體是從android的服務管理員的getService()函數中擷取,進一步追進去,會發現下面這樣一段代碼:

  1. {
  2.         Parcel data, reply;
  3.         data.writeInterfaceToken(IServiceManager::getInterfaceDescriptor());
  4.         data.writeString16(name);
  5.         remote()->transact(CHECK_SERVICE_TRANSACTION, data, &reply);
  6.         return reply.readStrongBinder();
  7. }

複製代碼

Android的服務管理員是一個單獨的進程,也向外提供介面。這段代碼的含義,是通過Android的服務管理員的介面代理,請求調用服務管理員的checkService()介面函數,尋找指定的服務(上面就是尋找media.player服務),尋找成功後返回一個BpBinder類的對象執行個體,用於供IMediaPlayerService代理使用。這個對象BpBinder是在Parcel::readStrongBinder()函數裡面建立的。那麼到底是怎麼建立出來的呢?在這裡沒有必要追到ServiceManager的實現代碼裡去,畢竟我們只是想知道BpBinder的對象是如何建立的,我們可以換一個例子來看。回到前面的BpMediaPlayerService::create()函數的實現,是不是很眼熟。沒錯,在那個函數裡也建立了一個BpBinder類對象,那個對象是是給IMediaPlayer介面代理使用的。雖然介面不同,但是建立原理是一樣的。我們繼續,下面該到binder的另一個類——實作類別的代碼了。

(3) BnMediaPlayerService程式碼分析
    BnMediaPlayerService類的定義在檔案frameworks/base/include/media/IMediaPlayService.h,實現則與BpMediaPlayerService一樣是在檔案frameworks/base/media/libmidia/IMediaPlayerService.cpp中。類定義的代碼如下:

  1. class BnMediaPlayerService: public BnInterface<IMediaPlayerService>
  2. {
  3. public:
  4.     virtual status_t    onTransact( uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags = 0);
  5. };

複製代碼

這個類繼承自BnInterface模板類,約束類型為IMediaPlayerService。看一下BnInterface模板類的定義(IInterface.h)就可以知道,BnMediaPlayerService間接繼承了IMediaPlayerService介面類。不過BnInterface類並沒有實現IMediaPlayerService所定義的6個介面函數,因此BnInterface還是一個純虛類。這些介面需要在BnMediaPlayerService的子類中真正實現,這個子類就是MediaPlayerService(frameworks/base/media/libmidiaservice/MediaPlayerService.h,frameworks/base/media/libmidiaservice/MediaPlayerService.cpp)。在BnMediaPlayerService的成員函數onTransact()中,需要調用這6個介面函數。BnMediaPlayerService中主要就是定義並實現了onTransact()函數。當在代理那邊調用了transact()函數後,這邊的onTransact()函數就會被調用。BnMediaPlayerService的實現代碼如下:

  1. #define CHECK_INTERFACE(interface, data, reply) /
  2.         do { if (!data.enforceInterface(interface::getInterfaceDescriptor())) { /
  3.             LOGW("Call incorrectly routed to " #interface); /
  4.             return PERMISSION_DENIED; /
  5.         } } while (0)
  6. status_t BnMediaPlayerService::onTransact(
  7.     uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
  8. {
  9.     switch(code) {
  10.         case CREATE_URL: {
  11.             CHECK_INTERFACE(IMediaPlayerService, data, reply);
  12.             pid_t pid = data.readInt32();
  13.             sp<IMediaPlayerClient> client = interface_cast<IMediaPlayerClient>(data.readStrongBinder());
  14.             const char* url = data.readCString();
  15.             sp<IMediaPlayer> player = create(pid, client, url);
  16.             reply->writeStrongBinder(player->asBinder());
  17.             return NO_ERROR;
  18.         } break;
  19. //……省略部分
  20.         default:
  21.             return BBinder::onTransact(code, data, reply, flags);
  22. }
複製代碼首先是一個宏定義CHECK_INTERFACE(),這個宏定義的作用是檢查介面的描述字串,這個前面也提到過,不需細說。然後就是onTrasact()函數的實現。這個函數的結構也很簡單,就是根據參數code的值分別執行不同的功能調用。code的取值就是前面提到過的介面功能代碼。函數的參數除了code,還包括Parcel類的兩個對象data和reply,分別用於傳送輸入參數和返回資料,與transact()函數的參數相對應。還有一個參數flag在這裡用不上,不討論。對應我們前面所選擇的介面函數的例子create(url),看看這邊對應的實現:
  1. case CREATE_URL: {
  2.      CHECK_INTERFACE(IMediaPlayerService, data, reply);
  3.      pid_t pid = data.readInt32();
  4.      sp<IMediaPlayerClient> client = interface_cast<IMediaPlayerClient>(data.readStrongBinder());
  5.      const char* url = data.readCString();
  6.      sp<IMediaPlayer> player = create(pid, client, url);
  7.      reply->writeStrongBinder(player->asBinder());
  8.      return NO_ERROR;
  9. }

複製代碼

首先是從data對象中依次取出各項輸入參數,然後調用介面函數create()(將在子類MediaPlayerService中實現),最後向reply中寫入返回資料。這個函數返回後,代理那邊的transact()也會跟著返回。
    那麼onTransact()函數是怎麼被調用的呢?通過查看BnInterface模板類的定義可以看到,這個類也是一個多重繼承類,另一個父類是BBinder(frameworks/base/include/utils/Binder.h,frameworks/base/libs/utils/Binder.cpp)。BBinder類繼承自IBinder,也實現了transact()函數,在這個函數中調用onTransact()函數。而BBinder對象的transact()函數則是在IPCThreadState類的executeCommand()成員函數中調用的。這已經涉及到較底層的實現,在這裡不再多說。
    上面這部分代碼還與前面提到過的BpBinder對象的建立有關係。看其中的紅色字型部分,通過create()函數調用會建立一個IMediaPlayer介面類的子類的對象,這個對象其實是MediaPlayerService::Client類(可以看一下MediaPlayerService的定義)的對象執行個體,而MediaPlayerService::Client類是繼承自BnMediaPlayer類的,與BnMediaPlayerService類類似,BnMediaPlayer其實也是一個binder實作類別(是BBinder的子類,進而也是IBinder的子類)。在上述代碼中,通過Parcel的writeStrongBinder()函數將這個對象寫入reply,而在代理側,通過Parcel的readStrongBinder()函數讀取則可以得到一個BpBinder的對象。至於類的具體建立過程已經封裝在Parcel類的定義中,這裡就不再多說了。

(4) 介面功能的真正實現
    到這裡兩個binder類就已經定義完了,下面就是IMediaPlayerService介面函數的真正實現。前面已經說過這些函數在類MediaPlayerService中實現。這個類繼承自BnMediaPlayerService,也間接地繼承了IMediaPlayerService介面類定義的6個功能函數,只需要按照正常方式實現這6個功能函數即可,當然為了實現這6個函數就需要其它一大堆的東西,不過這些具體的實現方法已經與binder機制無關,不再多說。
   在MediaPlayerService類中定義了一個靜態函數instantiate(),在這個函數中建立MediaPlayerService的對象執行個體,並將這個對象註冊到服務管理員中。這樣需要使用的時候就可以從服務管理員擷取IMediaPlayerService的代理對象。這個instantiate()是在MediaServer程式的main()函數中調用的。

  1. void MediaPlayerService::instantiate() {
  2.     defaultServiceManager()->addService(
  3.             String16("media.player"), new MediaPlayerService());
  4. }

複製代碼

(三) 總結一下
    說了這麼多,總結一下。是binder機制的層次模型。
2010-12-30 09:46 上傳

下載附件 (10.16 KB)

如果一個服務需要通過binder機制對外提供跨進程的介面,需要做下面這些事情。
    (1) 第一步,需要為這個介面定義一個繼承自IInterface的介面類,假設叫做IMyService。
    (2) 第二步,需要定義兩個binder類,其中一個是代理類BpMyService,需繼承自BpInterface;另一個是實作類別BnMyService,需繼承自BnInterface。
    (3) 第三步,定義BnMyService的子類,這個子類可以是任何名字,比如就叫MyService,在其中真正實現介面所提供的各個函數。
    (4) 第四步,建立MyService的執行個體,註冊到服務管理員(如IMediaPlayerService),也可以在其它介面的函數中建立(如上面的IMediaPlayer)。
轉自:http://ytydyd.blog.sohu.com/139026338.html 針對android2.3有部分修改,已經登記為這個顏色。

android的binder機制研究一(C++部分)
http://www.eoeandroid.com/forum-viewthread-tid-53565-fromuid-93074.html
android的binder機制研究二(C++部分)
http://www.eoeandroid.com/forum-viewthread-tid-53579-fromuid-93074.html

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.