溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Android中Binder詳細(xì)學(xué)習(xí)心得

發(fā)布時(shí)間:2020-09-06 07:37:32 來源:腳本之家 閱讀:164 作者:忘了12138 欄目:移動(dòng)開發(fā)

該文章是一個(gè)系列文章,是本人在Android開發(fā)的漫漫長途上的一點(diǎn)感想和記錄,我會(huì)盡量按照先易后難的順序進(jìn)行編寫該系列。該系列引用了《Android開發(fā)藝術(shù)探索》以及《深入理解Android 卷Ⅰ,Ⅱ,Ⅲ》中的相關(guān)知識,另外也借鑒了其他的優(yōu)質(zhì)博客,在此向各位大神表示感謝,膜拜?。?!另外,本系列文章知識可能需要有一定Android開發(fā)基礎(chǔ)和項(xiàng)目經(jīng)驗(yàn)的同學(xué)才能更好理解,也就是說該系列文章面向的是Android中高級開發(fā)工程師。

前言

上一次還不如不說去面試了呢,估計(jì)是掛了,數(shù)據(jù)結(jié)構(gòu)與算法方面雖然面試前突擊了一波,但是時(shí)間太短,當(dāng)時(shí)學(xué)的也不好。另外Android的一些知識也不是很了解。不過這也加大了我寫博客的動(dòng)力。許多知識總覺得自己掌握的還挺好,不過一問到比較細(xì)節(jié)的方面就不太清楚了。所以寫這整個(gè)博客的目的也是加深自己的知識,培養(yǎng)自己的溝通能力,和大家一起學(xué)習(xí)吧。
好了,閑話少說,我們這一篇先解決上一篇中遺留的問題,之后有時(shí)間的話,我把這次的面試經(jīng)歷單寫一篇博客,和大家共勉。
本篇我們來看一下ServiceManager。上一篇中沒怎么說它,ServiceManager作為Android系統(tǒng)服務(wù)的大管家。我們還是有必要來看一下它的。

ServiceManager概述

ServiceManager是Android世界中所有重要系統(tǒng)服務(wù)的大管家。像前文提到的AMS(ActivityManagerService),還有許多以后可能分析到的PackageManagerService等等服務(wù)都需要像ServiceManager中注冊。那么為何需要一個(gè)ServiceManager呢,其重要作用何在呢?私認(rèn)為有以下幾點(diǎn):

ServiceManager能集中管理系統(tǒng)內(nèi)的所有服務(wù),它能施加權(quán)限控制,并不是任何進(jìn)程都能注冊服務(wù)的。 ServiceManager支持通過字符串名稱來查找對應(yīng)的Service。這個(gè)功能很像DNS。由于各種原因的影響,Server進(jìn)程可能生死無常。 如果讓每個(gè)Client都去檢測,壓力實(shí)在太大了。 現(xiàn)在有了統(tǒng)一的管理機(jī)構(gòu),Client只需要查詢ServiceManager,就能把握動(dòng)向,得到最新信息。

ServiceManager

[SystemServer.java]

public void setSystemProcess() {
  try {
    //注冊服務(wù),第二個(gè)參數(shù)為this,這里假設(shè)SystemServer通過“socket”與SM交互
    ServiceManager.addService(Context.ACTIVITY_SERVICE, this, true);
    ..........
  } catch (PackageManager.NameNotFoundException e) {
    ........
  }
}

 

我們SystemServer進(jìn)程中的AMS通過SM的代理與SM進(jìn)程交互(讀者也可以把這個(gè)過程想象為你所能理解的進(jìn)程間通信方式,例如管道、Socket等),并把自己注冊在SM中。這個(gè)情況下,我們使用ServiceManager的代理與SM進(jìn)程交互,既然有代理,那么也得有對應(yīng)的服務(wù)端。那么根據(jù)我們之前博客的思路分析的話,就是如下的流程:

ServiceManager是如何啟動(dòng)的?

按照我們之前博客的思路,我們在SystemServer端有了個(gè)ServiceManager的代理,那么Android系統(tǒng)中應(yīng)該提供類似AMS這樣的繼承或間接繼承自java層Binder然后重寫onTransact方法以處理請求。但是并沒有,ServiceManager并沒有使用如AMS這樣復(fù)雜的Binder類結(jié)構(gòu)。而是直接與Binder驅(qū)動(dòng)設(shè)備打交道。所以我們上一篇說了ServiceManager不一樣。我們來看具體看一下。

ServiceManager在init.rc配置文件中配置啟動(dòng),是一個(gè)以c/c++語言編寫的程序。init進(jìn)程、SM進(jìn)程等關(guān)系如下圖

Android中Binder詳細(xì)學(xué)習(xí)心得

我們來看它的main方法。

int main(int argc, char **argv)
{
  struct binder_state *bs;
  //①應(yīng)該是打開binder設(shè)備吧?
  bs = binder_open(128*1024);
  if (!bs) {
    ALOGE("failed to open binder driver\n");
    return -1;
  }
  //②成為manager
  if (binder_become_context_manager(bs)) {
    ALOGE("cannot become context manager (%s)\n", strerror(errno));
    return -1;
  }
  ......
  //③處理客戶端發(fā)過來的請求
  binder_loop(bs, svcmgr_handler);
  return 0;
}

①打開Binder設(shè)備

[binder.c]

struct binder_state*binder_open(unsigned mapsize)
{
  struct binder_state*bs;
  bs=malloc(sizeof(*bs));
  ......
  //打開Binder設(shè)備
  bs->fd=open("/dev/binder",O_RDWR);
  ......
  bs->mapsize=mapsize;
  //進(jìn)行內(nèi)存映射
  bs->mapped=mmap(NULL,mapsize,PROT_READ,MAP_PRIVATE,bs->
  fd,0);
}

 

這一步的目的是把內(nèi)核層的binder驅(qū)動(dòng)映射到用戶空間。我們知道進(jìn)程之間是獨(dú)立的,進(jìn)程呢運(yùn)行在用戶空間內(nèi),內(nèi)核層的Binder驅(qū)動(dòng)可以看成是一個(gè)文件(實(shí)際上它也是,Linux上都是文件)。這一步呢,可以看成把一個(gè)文件映射到用戶空間,我們的進(jìn)程呢通過這個(gè)文件進(jìn)行交互。

Android中Binder詳細(xì)學(xué)習(xí)心得

②成為manager

[Binder.c]

int binder_become_context_manager(struct binder_state*bs)
{
  //實(shí)現(xiàn)太簡單了!這個(gè)有個(gè)0,什么鬼?
  return ioctl(bs->fd,BINDER_SET_CONTEXT_MGR,0);
}

 

③處理客戶端發(fā)過來的請求

[Binder.c]

void binder_loop(struct binder_state *bs, binder_handler func)
{
  int res;
  struct binder_write_read bwr;
  uint32_t readbuf[32];
  bwr.write_size = 0;
  bwr.write_consumed = 0;
  bwr.write_buffer = 0;
  readbuf[0] = BC_ENTER_LOOPER;
  binder_write(bs, readbuf, sizeof(uint32_t));
  for (;;) {//果然是循環(huán)
    bwr.read_size = sizeof(readbuf);
    bwr.read_consumed = 0;
    bwr.read_buffer = (uintptr_t) readbuf;
    res = ioctl(bs->fd, BINDER_WRITE_READ, &bwr);
    if (res < 0) {
      ALOGE("binder_loop: ioctl failed (%s)\n", strerror(errno));
      break;
    }
    //接收到請求交給binder_parse,最終會(huì)調(diào)用func來處理這些請求
    res = binder_parse(bs, 0, (uintptr_t) readbuf, bwr.read_consumed, func);
    if (res == 0) {
      ALOGE("binder_loop: unexpected reply?!\n");
      break;
    }
    if (res < 0) {
      ALOGE("binder_loop: io error %d %s\n", res, strerror(errno));
      break;
    }
  }
}

上面?zhèn)魅雈unc的是svcmgr_ handler函數(shù)指針,所以會(huì)在svcmgr_handler中進(jìn)行集中處理客戶端的請求。

[service_manager.c]

ServiceManager的代理是如何獲得的?

我們來回到最初的調(diào)用

[SystemServer.java]

public void setSystemProcess() {
  try {
    //注冊服務(wù),第二個(gè)參數(shù)為this,這里假設(shè)SystemServer通過“socket”與SM交互
    ServiceManager.addService(Context.ACTIVITY_SERVICE, this, true);
    ..........
  } catch (PackageManager.NameNotFoundException e) {
    ........
  }
}

上面的請求最終是通過SM服務(wù)代理發(fā)送的,那這個(gè)代理是怎么來的呢?我們來看

[ServiceManager.java]

public static void addService(String name, IBinder service) {
  try {
    getIServiceManager().addService(name, service, false);
  } catch (RemoteException e) {
    Log.e(TAG, "error in addService", e);
  }
}
private static IServiceManager getIServiceManager() {
  if (sServiceManager != null) {
    return sServiceManager;
  }
  // 是這里,沒錯(cuò)了
  sServiceManager = ServiceManagerNative.asInterface(BinderInternal.getContextObject());
  return sServiceManager;
}

我們先來看BinderInternal.getContextObject()

[BinderInternal.java]

//好吧,它還是個(gè)native函數(shù)
public static final native IBinder getContextObject();

跟進(jìn)[android_ util_Binder.cpp]

static jobject android_os_BinderInternal_getContextObject(JNIEnv* env, jobject clazz)
{
  sp<IBinder> b = ProcessState::self()->getContextObject(NULL);
  return javaObjectForIBinder(env, b);
}

跟進(jìn)[ProcessState.cpp]

sp<IBinder> ProcessState::getContextObject(const sp<IBinder>& /*caller*/)
{
  return getStrongProxyForHandle(0);
}
/*這個(gè)函數(shù)是不是我們之前見過*/
sp<IBinder> ProcessState::getStrongProxyForHandle(int32_t handle)
{
  sp<IBinder> result;
  AutoMutex _l(mLock);
  handle_entry* e = lookupHandleLocked(handle);
  if (e != NULL) {
    IBinder* b = e->binder;
    if (b == NULL || !e->refs->attemptIncWeak(this)) {
      if (handle == 0) {//這里我們的handle為0
        Parcel data;
      //在handle對應(yīng)的BpBinder第一次創(chuàng)建時(shí)
      //會(huì)執(zhí)行一次虛擬的事務(wù)請求,以確保ServiceManager已經(jīng)注冊
        status_t status = IPCThreadState::self()->transact(
            0, IBinder::PING_TRANSACTION, data, NULL, 0);
        if (status == DEAD_OBJECT)
          return NULL;//如果ServiceManager沒有注冊,直接返回
      }
      //這里還是以handle參數(shù)創(chuàng)建了BpBinder
      b = new BpBinder(handle); 
      e->binder = b;
      if (b) e->refs = b->getWeakRefs();
      result = b;
    } else {
      
      result.force_set(b);
      e->refs->decWeak(this);
    }
  }
  return result;
}

我們再一步步返回

[android_ util_Binder.cpp]

static jobject android_os_BinderInternal_getContextObject(JNIEnv* env, jobject clazz)
{
  sp<IBinder> b = ProcessState::self()->getContextObject(NULL);
  //這里的b = new BpBinder(0);
  return javaObjectForIBinder(env, b);
}
/*這個(gè)函數(shù)我們上一篇是不是也見過*/
jobject javaObjectForIBinder(JNIEnv* env, const sp<IBinder>& val) {
  if (val == NULL) return NULL;
  //如果val是Binder對象,進(jìn)入下面分支,此時(shí)val是BpBinder
  if (val->checkSubclass(&gBinderOffsets)) {
    // One of our own!
    jobject object = static_cast<JavaBBinder*>(val.get())->object();
    LOGDEATH("objectForBinder %p: it's our own %p!\n", val.get(), object);
    return object;
  }
  .........
  //調(diào)用BpBinder的findObject函數(shù)
  //在Native層的BpBinder中有一個(gè)ObjectManager,它用來管理在Native BpBinder上創(chuàng)建的Java BinderProxy對象
  //findObject用于判斷gBinderProxyOffsets中,是否存儲(chǔ)了已經(jīng)被ObjectManager管理的Java BinderProxy對象
  jobject object = (jobject)val->findObject(&gBinderProxyOffsets);
  if (object != NULL) {
    jobject res = jniGetReferent(env, object);
    ............
    //如果該Java BinderProxy已經(jīng)被管理,則刪除這個(gè)舊的BinderProxy
    android_atomic_dec(&gNumProxyRefs);
    val->detachObject(&gBinderProxyOffsets);
    env->DeleteGlobalRef(object);
  }
  //創(chuàng)建一個(gè)新的BinderProxy對象
  object = env->NewObject(gBinderProxyOffsets.mClass, gBinderProxyOffsets.mConstructor);
  if (object != NULL) {
    env->SetLongField(object, gBinderProxyOffsets.mObject, (jlong)val.get());
    val->incStrong((void*)javaObjectForIBinder);
    jobject refObject = env->NewGlobalRef(
        env->GetObjectField(object, gBinderProxyOffsets.mSelf));
    //新創(chuàng)建的BinderProxy對象注冊到BpBinder的ObjectManager中,同時(shí)注冊一個(gè)回收函數(shù)proxy_cleanup
    //當(dāng)BinderProxy對象detach時(shí),proxy_cleanup函數(shù)將被調(diào)用,以釋放一些資源
    val->attachObject(&gBinderProxyOffsets, refObject,
        jnienv_to_javavm(env), proxy_cleanup);
    // Also remember the death recipients registered on this proxy
    sp<DeathRecipientList> drl = new DeathRecipientList;
    drl->incStrong((void*)javaObjectForIBinder);
    //將死亡通知list和BinderProxy聯(lián)系起來
    env->SetLongField(object, gBinderProxyOffsets.mOrgue, reinterpret_cast<jlong>(drl.get()));
    // Note that a new object reference has been created.
    android_atomic_inc(&gNumProxyRefs);
    //垃圾回收相關(guān);利用gNumRefsCreated記錄創(chuàng)建出的BinderProxy數(shù)量
    //當(dāng)創(chuàng)建出的BinderProxy數(shù)量大于200時(shí),該函數(shù)將利用BinderInternal的ForceGc函數(shù)進(jìn)行一個(gè)垃圾回收
    incRefsCreated(env);
    return object;
  }
}

接著返回到[ServiceManager.java]

private static IServiceManager getIServiceManager() {
  if (sServiceManager != null) {
    return sServiceManager;
  }
  // 是這里,沒錯(cuò)了BinderInternal.getContextObject()是BinderProxy對象
  sServiceManager = ServiceManagerNative.asInterface(BinderInternal.getContextObject());
  return sServiceManager;
}

跟進(jìn)[[ServiceManagerNative.java]]

static public IServiceManager asInterface(IBinder obj)
{
  if (obj == null) {
    return null;
  }
  我們知道這里的obj指向的是BinderProxy對象
  IServiceManager in =
    (IServiceManager)obj.queryLocalInterface(descriptor);
  if (in != null) {
    return in;
  }
  return new ServiceManagerProxy(obj);
}

跟進(jìn)[Binder.java]

final class BinderProxy implements IBinder {
  public IInterface queryLocalInterface(String descriptor) {
    return null;
  }
}

跟進(jìn)[ServiceManagerNative.java]

class ServiceManagerProxy implements IServiceManager {
  public ServiceManagerProxy(IBinder remote) {
    //這里的mRemote指向了BinderProxy,與我們上一篇博客中講述的遙相呼應(yīng)
    mRemote = remote;
  }
}

本節(jié)小結(jié)

我們詳盡講述了SM進(jìn)程的啟動(dòng)以及它作為服務(wù)大管家的意義。結(jié)合上一篇的內(nèi)容我們總算是把Binder講述的比較清楚了。

Binder補(bǔ)充說明 AIDL

經(jīng)過上面的介紹,你應(yīng)該明白Java層Binder的架構(gòu)中,Bp端可以通過BinderProxy的transact()方法與Bn端發(fā)送請求,而Bn端通過集成Binder重寫onTransact()接收并處理來自Bp端的請求。這個(gè)結(jié)構(gòu)非常清晰簡單,在Android6.0,我們可以處處看到這樣的設(shè)計(jì),比如我們的ActivityManagerNavtive這個(gè)類,涉及到Binder通信的基本上都是這種設(shè)計(jì)。不過如果我們想要自己來定義一些遠(yuǎn)程服務(wù)。那這樣的寫法就比較繁瑣,還好Android提供了AIDL,并且在Android8.0之后,我們可以看到與ActivityManagerNavtive相似的許多類已經(jīng)被標(biāo)注過時(shí),因?yàn)锳ndroid系統(tǒng)也使用AIDL了。

AIDL的簡單例子

AIDL的語法與定義一個(gè)java接口非常類似。下面我就定以一個(gè)非常簡單的aidl

IMyAidlInterface.aidl

interface IMyAidlInterface {
  int getTest();
}

然后基本上就行了,我們重新build之后會(huì)得到一個(gè)
IMyAidlInterface.java文件,這個(gè)文件由aidl工具生成,我們現(xiàn)在使用的基本是AndroidStudio,即使你使用的是Eclipse也沒關(guān)系,這個(gè)文件會(huì)自動(dòng)生成,不需要你操心。但是我們還是得來看看我們生成的這個(gè)文件

public interface IMyAidlInterface extends android.os.IInterface {
  //抽象的Stub類,繼承自Binder并實(shí)現(xiàn)我們定義的IMyAidlInterface接口
  //繼承自Binder,重寫onTransact方法,是不是感覺跟我們的XXXNative很像
  public static abstract class Stub extends android.os.Binder implements com.mafeibiao.testapplication.IMyAidlInterface {
    private static final java.lang.String DESCRIPTOR = "com.mafeibiao.testapplication.IMyAidlInterface";
    /**
     * Construct the stub at attach it to the interface.
     */
    public Stub() {
      this.attachInterface(this, DESCRIPTOR);
    }
    /**
     * Cast an IBinder object into an com.mafeibiao.testapplication.IMyAidlInterface interface,
     * generating a proxy if needed.
     */
    public static com.mafeibiao.testapplication.IMyAidlInterface asInterface(android.os.IBinder obj) {
      if ((obj == null)) {
        return null;
      }
      android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
      if (((iin != null) && (iin instanceof com.mafeibiao.testapplication.IMyAidlInterface))) {
        return ((com.mafeibiao.testapplication.IMyAidlInterface) iin);
      }
      return new com.mafeibiao.testapplication.IMyAidlInterface.Stub.Proxy(obj);
    }
    @Override
    public android.os.IBinder asBinder() {
      return this;
    }
    @Override
    public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
      switch (code) {
        case INTERFACE_TRANSACTION: {
          reply.writeString(DESCRIPTOR);
          return true;
        }
        case TRANSACTION_getTest: {
          data.enforceInterface(DESCRIPTOR);
          int _result = this.getTest();
          reply.writeNoException();
          reply.writeInt(_result);
          return true;
        }
      }
      return super.onTransact(code, data, reply, flags);
    }
    /*這個(gè)Proxy不用說肯定是代理了,其內(nèi)部還有個(gè)mRemote對象*/
    private static class Proxy implements com.mafeibiao.testapplication.IMyAidlInterface {
      private android.os.IBinder mRemote;

      Proxy(android.os.IBinder remote) {
        mRemote = remote;
      }
      @Override
      public android.os.IBinder asBinder() {
        return mRemote;
      }
      public java.lang.String getInterfaceDescriptor() {
        return DESCRIPTOR;
      }
      @Override
      public int getTest() throws android.os.RemoteException {
        android.os.Parcel _data = android.os.Parcel.obtain();
        android.os.Parcel _reply = android.os.Parcel.obtain();
        int _result;
        try {
          _data.writeInterfaceToken(DESCRIPTOR);
          mRemote.transact(Stub.TRANSACTION_getTest, _data, _reply, 0);
          _reply.readException();
          _result = _reply.readInt();
        } finally {
          _reply.recycle();
          _data.recycle();
        }
        return _result;
      }
    }
    static final int TRANSACTION_getTest = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
  }
  public int getTest() throws android.os.RemoteException;
}

可見,AIDL的本質(zhì)與XXXNative之類的類并沒有什么本質(zhì)的不同,不過他的出現(xiàn)使得構(gòu)建一個(gè)Binder服務(wù)的工作大大簡化了。

AIDL的使用詳解

上面用一個(gè)非常簡單的小例子來解密AIDL的本質(zhì),但是在實(shí)際使用AIDL的時(shí)候還有許多地方需要注意。

AIDL支持的數(shù)據(jù)類型 基本數(shù)據(jù)類型(int,long,charmboolean,double等) String和CharSequence List:只支持ArrrayList,并且里面每個(gè)元素的類型必須是AIDL支持的 Map:只支持HashMap,t,并且里面每個(gè)元素的類型必須是AIDL支持的 Parcelable:所有實(shí)現(xiàn)Parcelable接口的對象 AIDL:所有的AIDL接口本身也可以在AIDL文件中使用

以上6種數(shù)據(jù)類型就是AIDL所支持的所有類型,其中自定義的Parcel對象和AIDL對象必須要顯示import進(jìn)來,不管他們是否和當(dāng)前的AIDL文件位于同一個(gè)包內(nèi)。

另外一個(gè)需要注意的地方是如果我們在AIDL中使用了自定義的Parcelable接口的對象,那么我們必須新建一個(gè)和它同名的AIDL文件,并在其中聲明它為Parcelable類型。

如下例

[IBookManager.aidl]

package com.ryg.chapter_2.aidl;
/*這里顯示import*/
import com.ryg.chapter_2.aidl.Book;
interface IBookManager {
   //這里我們使用了自定義的Parcelable對象
   List<Book> getBookList();
   void addBook(in Book book);
}

這里我們新建一個(gè)與Book同名的AIDL文件并聲明

[Book.aidl]

package com.ryg.chapter_2.aidl;
parcelable Book;

定向tag

定向tag:這是一個(gè)極易被忽略的點(diǎn)——這里的“被忽略”指的不是大家都不知道,而是很少人會(huì)正確的使用它。
AIDL中的定向 tag 表示了在跨進(jìn)程通信中數(shù)據(jù)的流向,其中 in 表示數(shù)據(jù)只能由客戶端流向服務(wù)端, out 表示數(shù)據(jù)只能由服務(wù)端流向客戶端,而 inout 則表示數(shù)據(jù)可在服務(wù)端與客戶端之間雙向流通。其中,數(shù)據(jù)流向是針對在客戶端中的那個(gè)傳入方法的對象而言的。in 為定向 tag 的話表現(xiàn)為服務(wù)端將會(huì)接收到一個(gè)那個(gè)對象的完整數(shù)據(jù),但是客戶端的那個(gè)對象不會(huì)因?yàn)榉?wù)端對傳參的修改而發(fā)生變動(dòng);out 的話表現(xiàn)為服務(wù)端將會(huì)接收到那個(gè)對象的的空對象,但是在服務(wù)端對接收到的空對象有任何修改之后客戶端將會(huì)同步變動(dòng);inout 為定向 tag 的情況下,服務(wù)端將會(huì)接收到客戶端傳來對象的完整信息,并且客戶端將會(huì)同步服務(wù)端對該對象的任何變動(dòng)。

另外,Java 中的基本類型和 String ,CharSequence 的定向 tag 默認(rèn)且只能是 in 。還有,請注意,請不要濫用定向 tag ,而是要根據(jù)需要選取合適的——要是不管三七二十一,全都一上來就用 inout ,等工程大了系統(tǒng)的開銷就會(huì)大很多——因?yàn)榕帕姓韰?shù)的開銷是很昂貴的。

所有的非基本參數(shù)都需要一個(gè)定向tag來指出數(shù)據(jù)的流向,不管是 in , out , 還是 inout 。基本參數(shù)的定向tag默認(rèn)是并且只能是 in 。

本篇總結(jié)

我們本篇詳細(xì)分析了ServiceManager,ServiceManager并沒有使用復(fù)雜的類結(jié)構(gòu),他直接與Binder驅(qū)動(dòng)設(shè)備交互達(dá)到IPC通信的目的。感謝你對億速云的支持。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI