English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية

Android AIDL:プロセス通信メカニズムの詳細

Android AIDL、Androidプロセスメカニズム通信メカニズム、ここではAIDLの知識を整理し、皆さんがこの部分の知識を学び理解する手助けをします!

AIDLとは何か

AIDL(Android Interface Definition Language)の全称は「安卓インターフェース定義言語」です。聞こえが難しく感じられますが、本質はプロセス間通信インターフェースを生成する補助ツールです。存在形態は .aidl ファイルであり、開発者はそのファイルでプロセス間通信のインターフェースを定義します。コンパイル時に IDE は .aidl インターフェースファイルに基づいて、プロジェクトで使用できる .java ファイルを生成します。これは私たちが言う「シンタックス糖」に少し似ています。

AIDLの文法はJavaの文法であり、パッケージの導入に少し違いがあります。Javaでは、同じパッケージ内の2つのクラスは導入操作を行わない必要がありますが、AIDLでは導入宣言を行う必要があります。

 AIDLの詳細

あるシナリオを想像してみましょう:私たちは図書管理システムを持っており、このシステムはCSモデルで実現されています。具体的な管理機能はサーバー側プロセスで実現され、クライアントは対応するインターフェースを呼び出すだけで十分です。

まず、この管理システムのADILインターフェースを定義します。

私たちは /rc 新建 aidl パッケージ、パッケージにはBook.java、Book.aidl、IBookManager.aidlの3つのファイルがあります。

package com.example.aidl book
public class Book implements Parcelable {
 int bookId;
 String bookName;
 public Book(int bookId, String bookName) {
   this.bookId = bookId;
   this.bookName = bookName;
 }
 ...
}

package com.example.aidl;

Parcelable Book;

package com.example.aidl;
import com.example.aidl.Book;
inteface IBookManager {
  List<Book> getBookList();
  void addBook(in Book book);
}

以下にこれら3つのファイルについて説明します:

Book.javaは私たちが定義したエンティティクラスであり、Parcelableインターフェースを実装しています。これにより、Bookクラスがプロセス間で転送できるようになります。
Book.aidlはこのエンティティクラスがAIDLで宣言されている場所です。
IBookManagerはサーバー側とクライアント側の通信インターフェースです。(注意:AIDLインターフェースでは、基本型以外のパラメータには方向を加える必要があります。inは入力型パラメータ、outは出力型パラメータ、inoutは入出力型パラメータを示します)

コンパイラがコンパイルした後、Android Studioは私たちのプロジェクトに自動的に.javaファイルを作成します。このファイルには、IBookManager、Stub、Proxyの3つのクラスが含まれています。これらのクラスはすべて静的タイプであり、完全に分離することができます。3つのクラスの定義は以下の通りです:

IBookManager

public interface IBookManager extends android.os.IInterface {
  public void addBook(net.bingyan.library.Book book) throws android.os.RemoteException;
  public java.util.List<net.bingyan.library.Book> getBookList() throws android.os.RemoteException;
}

Stub

public static abstract class Stub extends android.os.Binder implements net.bingyan.library.IBookManager {
    private static final java.lang.String DESCRIPTOR = "net.bingyan.library.IBookManager";
    static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
    static final int TRANSACTION_getBookList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
    /**
     * Construct the stub at attach it to the interface.
     */
    public Stub() {
      this.attachInterface(this, DESCRIPTOR);
    }
    /**
     * Cast an IBinder object into an net.bingyan.library.IBookManager interface,
     * generating a proxy if needed. http://www.manongjc.com/article/1501.html
     */
    public static net.bingyan.library.IBookManager asInterface(android.os.IBinder obj) {
      if ((obj == null)) {
        return null;
      }
      android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
      if (((iin != null) && (iin instanceof net.bingyan.library.IBookManager))) {
        return ((net.bingyan.library.IBookManager) iin);
      }
      return new net.bingyan.library.IBookManager.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_addBook: {
          data.enforceInterface(DESCRIPTOR);
          net.bingyan.library.Book _arg0;
          if ((0 != data.readInt())) {
            _arg0 = net.bingyan.library.Book.CREATOR.createFromParcel(data);
          } else {
            _arg0 = null;
          }
          this.addBook(_arg0);
          reply.writeNoException();
          return true;
        }
        case TRANSACTION_getBookList: {
          data.enforceInterface(DESCRIPTOR);
          java.util.List<net.bingyan.library.Book> _result = this.getBookList();
          reply.writeNoException();
          reply.writeTypedList(_result);
          return true;
        }
      }
      return super.onTransact(code, data, reply, flags);
    }
}

Proxy

private static class Proxy implements net.bingyan.library.IBookManager {
      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() {
        DESCRIPTOR を返します;
      }
      /**
       * 使用できる基本的なデータ型の例を示します
       * AIDL で返される値について説明します。http://www.manongjc.com/article/1500.html
       */
      @Override
      public void addBook(net.bingyan.library.Book book) throws android.os.RemoteException {
        android.os.Parcel _data = android.os.Parcel.obtain();
        android.os.Parcel _reply = android.os.Parcel.obtain();
        try {
          _data.writeInterfaceToken(DESCRIPTOR);
          if ((book != null)) {
            _data.writeInt(1);
            book.writeToParcel(_data, 0);
          } else {
            _data.writeInt(0);
          }
          mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0);
          _reply.readException();
        }
          _reply.recycle();
          _data.recycle();
        }
      }
      @Override
      public java.util.List<net.bingyan.library.Book> getBookList() throws android.os.RemoteException {
        android.os.Parcel _data = android.os.Parcel.obtain();
        android.os.Parcel _reply = android.os.Parcel.obtain();
        java.util.List<net.bingyan.library.Book> _result;
        try {
          _data.writeInterfaceToken(DESCRIPTOR);
          mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
          _reply.readException();
          _result = _reply.createTypedArrayList(net.bingyan.library.Book.CREATOR);
        }
          _reply.recycle();
          _data.recycle();
        }
        return _result;
      }
    }

生成されたこれらの3つのクラスの説明は以下の通りです:

  1. IBookManager このクラスは私たちが定義したインターフェースであり、android studio はその親クラスとして android.os.IInterface を追加し、android.os.IInterface は IBinder asBinder() という1つのメソッドを持っています。これにより、IBookManager には3つの実装されたメソッドがあります。これはサーバープロセスとクライアントプロセスが通信する窓口です。
  2. Stub これは抽象クラスで、android.os.Binder クラスを継承し、IBookManager インターフェースを実装しています。Stub では、asBinder() インターフェースメソッドが既に実装されており、子クラスが実装するために定義した AIDL インターフェースメソッドが2つあります。これはサーバーエンドポイントで使用され、したがってサーバーエンドポイントではこれらの2つのメソッドを実装する必要があります。
  3. Proxy という名の通り、代理クラスです。これはサーバーエンドポイントがクライアントエンドポイントに持つ代理であり、IBookManager インターフェースも実装しており、IBookManager 内のすべてのメソッドも実装しています。クライアントで使用される場合、これはサーバーエンドポイントがクライアントエンドポイントに持つ代理です。これらの3つのクラスについてそれぞれ分析します:
  4. IBookManager このクラスについて特に言うことはありませんが、asInterface インターフェースを単純に継承しています。その役割は IBookManager を IBinder に変換することです。
  5. Proxy このクラスは既に触れましたが、プロセス間通信メカニズムのエンブレス(封装)クラスであり、内部実装メカニズムは Binder です。構造メソッドからも簡単に見て取れます。構造メソッドは IBinder 型の引数を取り、それがリモートエンドポイントを表す remote と名付けられています。このクラスのメソッド addBook() と getBookList() を見てみましょう:
@Override
public void addBook(net.bingyan.library.Book book) throws android.os.RemoteException {
   android.os.Parcel _data = android.os.Parcel.obtain();
   android.os.Parcel _reply = android.os.Parcel.obtain();
   try {
      _data.writeInterfaceToken(DESCRIPTOR)
      if ((book != null)) {
        _data.writeInt(1);
        book.writeToParcel(_data, 0);
      } else {
        _data.writeInt(0);
      }
      mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0);
      _reply.readException();
    }
      _reply.recycle();
      _data.recycle();
    }
}
@Override /* http://www.manongjc.com/article/1547.html */
public java.util.List<net.bingyan.library.Book> getBookList() throws android.os.RemoteException {
    android.os.Parcel _data = android.os.Parcel.obtain();
    android.os.Parcel _reply = android.os.Parcel.obtain();
    java.util.List<net.bingyan.library.Book> _result;
    try {
       _data.writeInterfaceToken(DESCRIPTOR);
       mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
       _reply.readException();
       _result = _reply.createTypedArrayList(net.bingyan.library.Book.CREATOR);
    }
      _reply.recycle();
      _data.recycle();
    }
    return _result;
}

これらはコンパイラが自動的に実装するもので、この二つの方法には多くの類似点があります。ここで少し教えましょう:これら二つの方法はクライアントプロセスがサーバープロセスを呼び出すウィンドウです。これら二つの方法の開始部では、どちらも二つの Parcel(中国語訳名:パーセル)オブジェクトを定義します。Parcel というクラスは私たちにとって馴染みがあるように見えます。もちろん、Book クラスの writeToParcel() と CREATOR 内の createFromParcel() の引数は Parcel クラスのものです。このクラスについての説明は以下のドキュメントにあります:

メッセージ(データとオブジェクトの参照)を送信できるコンテナです。Parcel は、IPCの他方で展開される(特定のタイプの書き込みメソッドや一般的な{@link Parcelable}インターフェースを使用して)展開されないデータを含むことができ、生の{@link IBinder}オブジェクトの参照を含むことができ、これにより、他方で元のParcel内のIBinderに接続されたプロキシIBinderを受け取ります。

Proxy は、IBinder を介してメッセージを伝達できるコンテナです。Parcel はシリアライズ可能なデータを含むことができ、これらのデータは IPC の他方でデシリアライズされます;また、IBinder オブジェクトへの参照を含むことができ、これにより、他方で IBinder 型のプロキシオブジェクトを受け取ります。このプロキシオブジェクトは、Parcel 内の元の IBinder オブジェクトに接続されています。

以下に図を使って具体的に説明します:

Android プロセス通信メカニズムの AIDL

図に示されるように、サーバーは Parcel をデータのパケットとして、Binder を介してクライアントと通信を行います。データのパケットはシリアライズされたオブジェクトです。

前述のように、この二つの方法はそれぞれ _data と _reply という名前の Parcel オブジェクトを定義しています。端的に言えば、クライアントの視点から見ると、_data はクライアントがサーバーに送信するデータのパケットであり、_reply はサーバーがクライアントに送信するデータのパケットです。

その後、この2つのオブジェクトを使ってサービス側と通信を始めます。観察すると、2つのメソッドにはmRemote.transact()というメソッド呼び出しがあります。この4つのパラメータのうち、最初のパラメータの意味は後で説明しますが、2番目のパラメータ_dataはサービス側にデータパケット(例えばインターフェースメソッドのパラメータ)を送信する役割を持ち、3番目のパラメータ_replyはサービス側からデータパケット(例えばインターフェースメソッドの返値)を受け取る役割を持ちます。この一行のコードはシンプルなメソッド呼び出しだけですが、AIDL通信の最も核心的な部分であり、実際にはリモートメソッド呼び出し(クライアントがローカルプロキシProxyを介して公開したインターフェースメソッドを呼び出し、サービス側のStubの同名メソッドを呼び出す)を了一次に実行していますので、時間がかかる操作となります。

私たちの例では:

1.void addBook(Book book)は、_dataを介してサービス側にBook:bookを送信するために必要です。その送信方法は、Bookをその実装したwriteToParcel(Parcel out)メソッドを介して Packing至_dataにすることです。思い出せると思いますが、_dataは実際にはパラメータoutです。Bookのこのメソッドの実装を覚えていますか?私たちはBookのフィールドを一つずつParcelにPackingしていました。

2.List<Book> getBookList()は、_replyを介してサービス側から返されるList<Book>:booksを取得するために必要です。その方法は、BookのCREATORという静的フィールドを_paramsのcreateTypedArrayList()メソッドに引数として渡すことです。BookのCREATORを覚えていますか?当時、この静的フィールドはどのように使われるべきか疑問に思ったかもしれませんね。今では明らかになりました。このオブジェクト(理解しやすく言えば「デシリアライザ」)を使って、サービス側のデータをデシリアライズし、再びシリアライズ可能なオブジェクトまたはオブジェクトリストを再生成する必要があります。明らかに、CREATORは_replyを介してList<Book>:booksを生成しています。

もちろん、これらのメソッドの_dataと_replyはオブジェクトだけでなく、一部の検証情報も伝達しています。これは深く追求する必要はありませんが、Parcelのパッキングとアンパッキングの順序は厳密に一致する必要があります。例えば、最初にパッキングされたのはint:iであれば、最初にアンパッキングされるべきもこの整型値です。つまり、パッキング時にParcel.writeInt(int)を最初に呼び出した場合、アンパッキング時にはParcel.readInt()を最初に呼び出すべきです。

ここまでで、クライアントのProxyの説明が終わりました。次に、サーバーのStubを見てみましょう。

StubはIBookManagerの1つのメソッドを実装しています。これは非常に単純で、自身を単に返すだけです。なぜなら、StubはBinderを継承しており、BinderはIBinderを継承しているため、何の問題もありません。あなたはまだ2つのメソッドが実装されていないと言うかもしれません。これら2つのメソッドは、私たちが定義したインターフェースメソッドです。これらはサーバープロセスに実装を残します。つまり、その時点で、サーバープロセスでStubの実装者を定義する必要があります。以下では、Stubの2つの重要メソッドについて分析します:

IBookManager asInterface(IBinder obj)

public static net.bingyan.library.IBookManager asInterface(android.os.IBinder obj) {
      if ((obj == null)) {
        return null;
      }
      android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
      if (((iin != null) && (iin instanceof net.bingyan.library.IBookManager))) {
        return ((net.bingyan.library.IBookManager) iin);
      }
      return new net.bingyan.library.IBookManager.Stub.Proxy(obj);
    }

このメソッドの役割は、StubクラスをIBookManagerインターフェースに変換することです。メソッドには判断があります:サーバープロセスとクライアントプロセスが同じプロセスである場合、直接Stubクラスを型変換してIBookManagerに変換します。同じプロセスでない場合、Proxyクラスを通じてStubをIBookManagerに変換します。なぜこんなことをするのか、サーバープロセスとクライアントプロセスが同じプロセスでない場合、彼らのメモリは共有できず、一般的な通信方法を通じて通信できません。私たちがプロセス間通信の方法を実現する場合、一般的な開発者にとってコストが高すぎますので、コンパイラがプロセス間通信を封装したツールを生成してくれます。それがこのProxyであり、このクラスは下層のプロセス通信メカニズムを封装し、同時にインターフェースメソッドを公開しています。クライアントは、プロセス間通信(実はメソッドのリモート呼び出し)を実現するために、これらの2つのメソッドを呼び出すだけで十分で、その詳細については知る必要はありません。

このメソッドを使うことで、クライアント側でIBinder型の変数を私たちが定義したインターフェースIBookManagerに変換することができます。その使用シーンは後の例で説明します。

onTransact(int code, Parcel data, Parcel reply, int flags)

@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_addBook: {
          data.enforceInterface(DESCRIPTOR);
          net.bingyan.library.Book _arg0;
          if ((0 != data.readInt())) {
            _arg0 = net.bingyan.library.Book.CREATOR.createFromParcel(data);
          } else {
            _arg0 = null;
          }
          this.addBook(_arg0);
          reply.writeNoException();
          return true; /* http://www.manongjc.com/article/1499.html */
        }
        case TRANSACTION_getBookList: {
          data.enforceInterface(DESCRIPTOR);
          java.util.List<net.bingyan.library.Book> _result = this.getBookList();
          reply.writeNoException();
          reply.writeTypedList(_result);
          return true;
        }
      }
      return super.onTransact(code, data, reply, flags);
}

この方法、私たちもとても馴染みがあると思いますか?Proxyの中でも似たようなメソッドtransact(int, Parcel, Parcel, int)を見ましたが、その引数も同じで、それらはBinderの中のメソッドです。それらにはどんな関係がありますか?

前述の通り、transact() はリモートコールを実行します。transact() がリモートコールの発起者ならば、onTransact() はリモートコールの応答です。実際のプロセスは、クライアントがリモートメソッドコールを発行し、Android システムがこのコールに対してベースレベルのコードを通じて応答し処理し、その後、サービス側の onTransact() メソッドに戻され、データパッケージからメソッドパラメータを取り出し、サービス側の同名メソッドを呼び出し、最後に戻り値をパッケージしてクライアントに返します。

onTransact() はサーバーサイドプロセスの Binder スレッドプールで実行されます。これは、onTransact() メソッドで UI を更新する必要がある場合、Handler を使用する必要があることを意味します。

これらのメソッドの最初の引数の意味は、AIDL インターフェースメソッドの識別コードです。Stub では、これらのメソッドの識別として2つの定数を定義しています:

static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
   static final int TRANSACTION_getBookList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);

code == TRANSACTION_addBook なら、クライアントが addBook() を呼び出していることを示しています;code == TRANSACTION_getBookList なら、クライアントが getBookList() を呼び出し、それが対応するサーバーサイドメソッドで処理されます。この通信プロセス全体を図に示します:

Android プロセス通信メカニズムの AIDL

AIDL の全体のプロセスを理解したら、次は AIDL が Android プログラムでどのように使われるかです。

 AIDL の使用

皆さんは Service の使い方をよく理解していると思います。Service は「サービス」と呼ばれており、バックグラウンドで動作していますが、デフォルトでは常にデフォルトプロセスのメインスレッドで動作しています。実際には Service をデフォルトプロセスで動作させるのは、材木を無駄に使ったようなものです。Android の多くのシステムサービスは、他のアプリケーションが呼び出すために独立したプロセスで動作しています。例えば、ウィンドウ管理サービスです。この方法の利点は、同じサービスを複数のアプリケーションで共有できるため、リソースを節約し、クライアントを集中管理しやすくなることです。問題点はスレッドセキュリティです。

その次に、AIDL を使用してシンプルな CS アーキテクチャの図書管理システムを実現します。

まずサーバーサイドを定義します:

BookManagerService

public class BookManagerService extends Service {
  private final List<Book> mLibrary = new ArrayList<>();
  private IBookManager mBookManager = new IBookManager.Stub() {
    @Override
    public void addBook(Book book) throws RemoteException {
      synchronized (mLibrary) {
        mLibrary.add(book);
        Log.d("BookManagerService", "now our library has ", + mLibrary.size() + " books");
      }
    }
    @Override
    public List<Book> getBookList() throws RemoteException {
      return mLibrary;
    }
  };
  @Override /* http://www.manongjc.com/article/1496.html */
  public IBinder onBind(Intent intent) {
    return mBookManager.asBinder();
  }
}
<service
   android:process=":remote"
   android:name=".BookManagerService"/>

サーバーサイドでは、BookManagerService というクラスを定義し、その中でサーバーサイドの Stub オブジェクトを作成し、必要な 2 つの AIDL インターフェースメソッドを実装してサーバーサイドの図書管理戦略を定義しました。onBind() メソッドでは、IBookManager オブジェクトを IBinder として返します。私たちは知っていますように、サービスをバインドする際には、システムが onBinder() メソッドを呼び出し、サーバーサイドの IBinder オブジェクトを取得し、それをクライアントの IBinder オブジェクトに変換してクライアントに渡します。サーバーサイドの IBinder とクライアントの IBinder は異なる IBinder オブジェクトですが、底層では同じオブジェクトです。xml で Service を登録する際には、プロセス名を指定して、Service が独立したプロセスで動作するようにしました。

次にクライアントの実装を見てみましょう:

Client

public class Client extends AppCompatActivity {
  private TextView textView;
  private IBookManager bookManager;
  @Override
  protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.library_book_manager_system_client);
    Intent i = new Intent(Client.this, BookManagerService.class);
    bindService(i, conn, BIND_AUTO_CREATE);
    Button addABook = (Button) findViewById(R.id.button);
    addABook.setOnClickListener(v -> {
      if (bookManager == null) return;
      try {
        bookManager.addBook(new Book(0, "book"));
        textView.setText(getString(R.string.book_management_system_book_count, String.valueOf(bookManager.getBookList().size())));
      } catch (RemoteException e) {
        e.printStackTrace();
      }
    });
    textView = (TextView) findViewById(R.id.textView);
  }
  private ServiceConnection conn = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
      Log.d("Client" --">", service.toString());
      bookManager = IBookManager.Stub.asInterface(service);
    }
    @Override
    public void onServiceDisconnected(ComponentName name) {
      Log.d("Client", name.toString());
    }
  };
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:orientation="vertical" android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:weightSum="1"
  android:gravity="center">
  <Button
    android:text="http://www.manongjc.com/article/1495.html"
    android:layout_width="111dp"
    android:layout_height="wrap_content"
    android:id="@"+id/button" />
  <TextView
    android:layout_marginTop="10dp"
    android:text="@string/book_management_system_book_count"
    android:layout_width="231dp"
    android:gravity="center"
    android:layout_height="wrap_content"
    android:id="@"+id/textView" />
</LinearLayout>

クライアントは Activity であり、onCreate() でサービスのバインドが行われました。bindService() メソッドには ServiceConnection:conn という引数があり、サービスのバインドは非同期に行われるため、この引数の役割はサービスのバインドが成功した後のリターンインターフェースです。このインターフェースには二つのリターンメソッドがあります:サービスが接続成功した場合のリターンと、サービスと接続が切れた場合のリターンです。現在、我々が関心を持っているのは onServiceConnected() メソッドです。ここでは、サービス側から転送された IBinder オブジェクトを AIDL インターフェースに変換し、IBookManager:bookManager フィールドを定義してその参照を保持します。これにより、bookManager を通じてリモートメソッドの呼び出しを行うことができます。クライアントの Button にイベントを登録し、クリックごとにサービス側に本を追加し、図書館の現在の本の数を表示します。

プログラムの実行結果を見てみましょう:

ボタンをクリックするたびに、サービス側に本を追加し、AIDL を介して跨プロセス通信が成功したことを示しています。

ご読阅ありがとうございます。皆様のサポートに感謝します!

声明:本文の内容はインターネットから取得しており、著作権者に帰属します。インターネットユーザーにより自発的に貢献し、自己でアップロードされたものであり、本サイトは所有権を持ちません。また、人工的な編集は行われていません。著作権に関する問題がある場合は、notice#w までメールを送信してください。3codebox.com(メールを送信する際は、#を@に置き換えてください。報告を行い、関連する証拠を提供してください。一旦確認がとりついた場合、本サイトは即座に侵害を疑われるコンテンツを削除します。)

おすすめ