温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

从Android源码的角度分析Binder机制

发布时间:2020-06-23 11:07:45 来源:网络 阅读:491 作者:lzwxx 栏目:移动开发

IPC

为了弄懂IPC的来龙去脉,我将从以下三个方面为大家来讲解,希望对大家理解IPC会有帮助

什么是IPC

IPC是Inter Process Communication的缩写,其意思就是进程间的通信,也就是两个进程之间的通信过程。我们都知道在Android系统中,每个应用都运行在一个进程上,具有自己的DVM实例,而且进程之间是相互隔离的,也就是说各个进程之间的数据是互相独立,互不影响的,而如果一个进程崩溃了,也不会影响到另一个进程。
采取这样的设计是有一定道理的,例如这样的前提下将互相不影响的系统功能分拆到不同的进程里面去,有助于提升系统的稳定性,毕竟我们都不想自己的应用进程崩溃会导致整个手机系统的崩溃。
进程之间隔离是不错的选择,可是如果进程之间想要互相通信,进行数据交互的时候那该怎么办呢?例如我们在自己的应用中想要访问手机通讯录中的联系人,很显然这是两个不同的进程,如果Android没有提供一种进程之间交流的机制,那么这种功能将无法实现。
不过由于Android系统使用的是Linux内核,而在Linux系统中进程之间的交互是有一套机制的,所以Android也借鉴了其中的一些机制,从而形成了Android的IPC机制。
上面只是粗略的讲解了IPC是啥,关于它的使用和原理我将一一为大家呈上。

为什么要用IPC

上一点中我举了访问手机通讯录的例子。但你可能觉得我不需要用到这种功能,那么我就不用管IPC啦!其实不然,IPC在我们的应用开发过程中随处可见,下面我将举一个例子来说明他的重要性。
我们在MainActivity中修改一个静态变量,接着在另一个进程的SecondActivity中去访问该变量,看看能否读取已经修改过的变量。

1、新建一个Student类,并声明一个静态变量

public class Student {    public static String name="BOB";
}

2、在MainActivity的onCreate方法中修改name的值,并打印log

@Overrideprotected void onCreate(Bundle savedInstanceState) {    super.onCreate(savedInstanceState);    setContentView(R.layout.activity_main);
    Student.name = "JACK";
    Log.d("MainActivity:Sname=", Student.name);
}

3、将SecondActivity设置为新进程,并在其onCreate方法中访问name

<!-- 在清单文件中通过android:process属性为SecondActivity指定特定的进程:com.bob.aidltest:second --><activity     android:name=".SecondActivity"    android:process=":second"></activity>
public class SecondActivity extends AppCompatActivity {    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.second_activity);
        Log.d("SecondActivity:Sname=" , Student.name);
    }
}

4、通过log,可以看到在MainActivity中修改了name的值,但是在SecondActivity中却无法读取修改后的值

通过以上的实验,大家应该明白了一点:在不同的进程之间访问同一个静态变量是行不通的。其原因是:每一个进程都分配有一个独立的虚拟机,不同的虚拟机在内存分配上有不同的地址空间,这就导致在不同的虚拟机上访问同一个对象会产生多个副本。例如我们在MainActivity中访问的name的值只会影响当前进程,而对其他进程不会造成影响,所以在SecondActivity中访问name时依旧只能访问自己进程中的副本。

Android中解决IPC的方法

上面也讲到,为了解决这些跨进程的问题,Android沿用了一些Linux的进程管理机制,使得进程之间能够进行交互,下面我将列出一些常见的IPC方式,需要指出的是本文主要讲解Binder机制,所以会注重讲解AIDL,其他方式请读者自行查阅相关资料。

名称特点使用场景
Bundle只能传输实现了Serializable或者Parcelable接口或者一些Android支持的特殊对象适合用于四大组件之间的进程交互
文件不能做到进程间的即时通信,并且不适合用于高并发的场景适合用于SharedPreference以及IO操作
ContentProvider可以访问较多的数据,支持一对多的高并发访问,因为ContentProvider已经自动做好了关于并发的机制适合用于一对多的数据共享并且需要对数据进行频繁的CRUD操作
Socket通过网络传输字节流,支持一对多的实时通信,但是实现起来比较复杂适合用于网络数据共享
Messenger底层原理是AIDL,只是对其做了封装,但是不能很好的处理高并发的场景,并且传输的数据只能支持Bundle类型低并发的一对多的即时通信
AIDL功能强大,使用Binder机制(接下来会讲解),支持一对多的高并发实时通信,但是需要处理好线程同步一对多并且有远程进程通信的场景

Binder

终于来到这篇文章的重头戏了,上面讲到Android解决IPC的方法中有一种是AIDL,它使用的原理就是Binder,只有理解了Binder,我们才算是理解了Android跨进程通信的原理。在这里我会带大家看看Android中有哪一些重要的地方使用到了Binder,接着我们会通过一个实例来了解如何使用Binder,最后我们会分析Binder的源码来理解他的工作流程。

Binder在Android中的运用

说起Binder在Android的使用场景,可以说是无处不在,我列出一些最常见的场景:

  • 四大组件的生命周期都是使用Binder机制进行管理的

  • View的工作原理也使用了Binder

  • WindowManager的工作机制同样使用了Binder

以上三个方面只是最常见的场景,但是却几乎包括了我们开发的整个流程。我们开发的应用都离不开四大组件,而四大组件也正是依靠Binder机制运行的;对于我们最常见的View,他是如何显示的,View又是如何响应我们的动作的,这其中也用到了Binder(关于这些内容我会在后续的文章中为大家分析)。可以说了解Binder对于我们的开发是很有帮助的,那接下来我们就来看看我们该如何使用Binder进行进程间的通信吧!

如何使用Binder

现在我们需要实现这样的功能:客户端与服务端位于不同的进程,客户端需要向服务端添加学生,同时客户端还可以向服务端发起查询学生列表的请求。

1、创建Student.java,Student.aidl,IStudentManager.aidl

  • Student.java

package com.bob.aidltest.aidl;import android.os.Parcel;import android.os.Parcelable;/** * Created by bob on 17-7-3. * 所有需要在Binder传递的数据类型都需要实现Parcelable接口 */public class Student implements Parcelable{    public static String name="BOB";    public int s_id;    public String s_name;    public String s_gender;    public Student(Parcel in) {
        s_id = in.readInt();
        s_name = in.readString();
        s_gender = in.readString();

    }    public Student(int s_id, String s_name, String s_gender) {        this.s_id = s_id;        this.s_name = s_name;        this.s_gender = s_gender;
    }    @Override
    public int describeContents() {        return 0;
    }    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(s_id);
        dest.writeString(s_name);
        dest.writeString(s_gender);
    }    public static final Creator<Student> CREATOR = new Creator<Student>() {        @Override
        public Student createFromParcel(Parcel in) {            return new Student(in);
        }        @Override
        public Student[] newArray(int size) {            return new Student[size];
        }
    };    @Override
    public String toString() {        return String.format("[StudentID: %s , StudentName: %s , StudentGender: %s]", s_id, s_name, s_gender);
    }
}
  • Student.aidl

// Student1.aidlpackage com.bob.aidltest.aidl;parcelable Student;
  • IStudentManager.aidl

// IStudentManager.aidlpackage com.bob.aidltest.aidl;import com.bob.aidltest.aidl.Student;interface IStudentManager {    List<Student> getStudentList();    void addStudent(in Student student);
}

创建完毕之后手动编译项目(Build-->ReBuild Project),接着就会在app/build/generated/source/aidl/debug/com/bob/aidltest/aidl/IStudentManager.java中看到自动生成的IStudentManager接口,如下图:

2、分析IStudentManager.java

先来看看自动生成的代码:

public interface IStudentManager extends android.os.IInterface{    /** 内部类Stub,继承自Binder并且实现了IStudentManager接口,因此他也是一个Binder对象,这个内部类是需要在服务端手动实现的,并且会通过onBind方法返回给客户端 */
    public static abstract class Stub extends android.os.Binder implements com.bob.aidltest.aidl.IStudentManager
    {        private static final java.lang.String DESCRIPTOR = "com.bob.aidltest.aidl.IStudentManager";        /** 构造方法 */
        public Stub()
        {            this.attachInterface(this, DESCRIPTOR);
        }        /**         * 将服务端的Binder对象转换为客户端的所需的AIDL接口类型的对象,客户端拿到这个对象就可以通过这个对象远程访问服务端的方法         */
        public static com.bob.aidltest.aidl.IStudentManager asInterface(android.os.IBinder obj)
        {            if ((obj==null)) {                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);            if (((iin!=null)&&(iin instanceof com.bob.aidltest.aidl.IStudentManager))) {                return ((com.bob.aidltest.aidl.IStudentManager)iin);
            }            return new com.bob.aidltest.aidl.IStudentManager.Stub.Proxy(obj);
        }        @Override public android.os.IBinder asBinder()
        {            return this;
        }        
        /**         * 运行在服务端进程的Binder线程池中;当客户端进程发起远程请求时,远程请求会要求系统底层执行回调该方法         * @param code 客户端进程请求方法标识符。服务端进程会根据该标识确定所请求的目标方法         * @param data 目标方法的参数,他是客户端进程传进来的,当我们调用addStudent(Student student)方法时,参数就是Student对象         * @param reply 目标方法执行后的结果,将会返回给客户端,例如当我们调用getStudentList,返回的就是一个Student的列表         */
        @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_getStudentList:
                {
                    data.enforceInterface(DESCRIPTOR);
                    java.util.List<com.bob.aidltest.aidl.Student> _result = this.getStudentList();
                    reply.writeNoException();
                    reply.writeTypedList(_result);                    return true;
                }                case TRANSACTION_addStudent:
                {
                    data.enforceInterface(DESCRIPTOR);
                    com.bob.aidltest.aidl.Student _arg0;                    if ((0!=data.readInt())) {
                        _arg0 = com.bob.aidltest.aidl.Student.CREATOR.createFromParcel(data);
                    }                    else {
                        _arg0 = null;
                    }                    this.addStudent(_arg0);
                    reply.writeNoException();                    return true;
                }
            }            return super.onTransact(code, data, reply, flags);
        }        /**         * 代理的内部类,他实现了IStudentManager接口,这个代理类就是服务端返回给客户端的AIDL接口对象,客户端可以通过这个代理类访问服务端的方法         */
        private static class Proxy implements com.bob.aidltest.aidl.IStudentManager
        {            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 java.util.List<com.bob.aidltest.aidl.Student> getStudentList() throws android.os.RemoteException
            {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                java.util.List<com.bob.aidltest.aidl.Student> _result;                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    mRemote.transact(Stub.TRANSACTION_getStudentList, _data, _reply, 0);
                    _reply.readException();
                    _result = _reply.createTypedArrayList(com.bob.aidltest.aidl.Student.CREATOR);
                }                finally {
                    _reply.recycle();
                    _data.recycle();
                }                return _result;
            }            @Override public void addStudent(com.bob.aidltest.aidl.Student student) 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 ((student!=null)) {
                        _data.writeInt(1);
                        student.writeToParcel(_data, 0);
                    }                    else {
                        _data.writeInt(0);
                    }
                    mRemote.transact(Stub.TRANSACTION_addStudent, _data, _reply, 0);
                    _reply.readException();
                }                finally {
                    _reply.recycle();
                    _data.recycle();
                }
            }
        }        static final int TRANSACTION_getStudentList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);        static final int TRANSACTION_addStudent = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
    }    public java.util.List<com.bob.aidltest.aidl.Student> getStudentList() throws android.os.RemoteException;    public void addStudent(com.bob.aidltest.aidl.Student student) throws android.os.RemoteException;
}

可能看了上面的注释大家还是一头雾水,那就先看看这个类的结构图吧:

有关这个类的细节我们待会讲,现在只需要知道我们需要在服务端手动实现Proxy类并实现其中的方法。

创建StudentManagerService.java,并为其指定进程

/** * Created by bob on 17-7-3. * 服务端代码 */public class StudentManagerService extends Service {    private static final String TAG = "StudentManagerService";    //判断Service是否销毁
    private AtomicBoolean mIsServiceDestroyed = new AtomicBoolean(false);    //适合用于进程间传输的列表类
    private CopyOnWriteArrayList<Student> mStudentList = new CopyOnWriteArrayList<Student>();    @Override
    public void onCreate() {        super.onCreate();        //在服务端手动添加两位默认的学生
        mStudentList.add(new Student(1, "BOB", "man"));
        mStudentList.add(new Student(2, "MAY", "woman"));

    }    @Override
    public IBinder onBind(Intent intent) {        return mBinder;
    }    @Override
    public void onDestroy() {
        mIsServiceDestroyed.set(false);        super.onDestroy();
    }    private Binder mBinder = new IStudentManager.Stub() {        @Override
        public List<Student> getStudentList() throws RemoteException {
            SystemClock.sleep(5000);//休眠5s模拟耗时操作
            return mStudentList;
        }        @Override
        public void addStudent(Student student) throws RemoteException {
            mStudentList.add(student);
        }

    };
    
}

在清单文件中指定服务的进程

<service     android:name=".StudentManagerService"    android:process=":remote"></service>

可以看到这个服务类跟普通的服务类相差并不大,唯一的区别在于它创建了一个IStudentManager.Stub的匿名内部类并且实现了其中的方法,在onBind方法中将这个IBinder对象返回给客户端。这里需要说明一下:Binder是实现了IBinder接口的,所以他同时也是一个IBinder对象。

在客户端愉快的绑定Service吧!

public class MainActivity extends AppCompatActivity {    private static final String TAG = "MainActivity_Client";    private static final int MESSAGE_QUERY_STUDENTLIST=1;    private int student_size = 3;    private IStudentManager mRemoteStudentManager;    private ServiceConnection mConnection=new ServiceConnection() {        //onServiceConnected与onServiceDisconnected都是在主线程中的,所以如果里面如果涉及到服务端的耗时操作那么需要在子线程中进行
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {            //获取到IStudentManager对象
            final IStudentManager studentManager =IStudentManager.Stub.asInterface(service);
            mRemoteStudentManager = studentManager;
        }        @Override
        public void onServiceDisconnected(ComponentName name) {
            mRemoteStudentManager = null;
            Log.d(TAG, "onServiceDisconnected.threadname:" + Thread.currentThread().getName());

        }
    };    @Override
    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);
        Intent intent = new Intent(this, StudentManagerService.class);        bindService(intent, mConnection, BIND_AUTO_CREATE);
    }    @Override
    protected void onDestroy() {        unbindService(mConnection);        super.onDestroy();
    }    //将服务端返回的数据显示在界面上
    private Handler mHandler=new Handler(){        @Override
        public void handleMessage(Message msg) {            switch (msg.what) {                case MESSAGE_QUERY_STUDENTLIST:
                    Toast.makeText(MainActivity.this, msg.obj.toString(),Toast.LENGTH_SHORT).show();                default:                    super.handleMessage(msg);
            }
        }
    };    /**     * 在客户端向服务端添加一名学生     * @param view     */
    public void addStudent(View view) {        if (mRemoteStudentManager != null) {            try{                int student_id = student_size+ 1;
                Student newStudent;                if (student_id % 2 == 0) {
                    newStudent= new Student(student_id, "新学生" + student_id, "man");
                } else {
                    newStudent= new Student(student_id, "新学生" + student_id, "woman");
                }
                mRemoteStudentManager.addStudent(newStudent);
                Log.d(TAG, "添加一位学生:" + newStudent.toString());
            }catch(Exception e){
                e.printStackTrace();
            }
        }
    }    /**     * 在客户端向服务端发起查询学生的请求     * @param view     */
    public void get_student_list(View view) {
        Toast.makeText(this, "正在获取学生列表", Toast.LENGTH_SHORT).show();        //由于服务端的查询操作是耗时操作,所以客户端需要开启子线程进行工作
        new Thread(new Runnable() {            @Override
            public void run() {                if (mRemoteStudentManager != null) {                    try{                        final List<Student> students = mRemoteStudentManager.getStudentList();
                        student_size = students.size();
                        Log.d(TAG, "从服务器成功获取到学生列表:" + students.toString());
                        mHandler.obtainMessage(MESSAGE_QUERY_STUDENTLIST, students).sendToTarget();
                    }catch(Exception e){
                        e.printStackTrace();
                    }
                }
            }
        }).start();
    }
}

可以看到我们在客户端只需要绑定远程的服务端,服务端就会返回一个IBinder对象,接着我们需要调用IStudentManager.Stub.asInterface()方法,将这个IBinder对象转换为我们客户端可用的AIDL接口对象,拿到这个对象之后我们就可以远程调用服务端的方法了。是不是很容易?
但是需要注意的一点是为了模拟耗时操作,我们在服务端的getStudentList的方法中使用休眠以模拟耗时操作,所以客户端在调用该方法时不能直接在主线程中调用,而是应该开启一个子线程,在子线程中调用这个耗时的操作。

看看效果

首先我们获取学生列表,接着连续添加4个学生,再次查看学生列表,最终的结果如下图,可以看到我们已经实现了两个进程之间的交互,接下来我们将分析Binder的原理。

Binder的原理

进程的机制

首先我们需要了解进程之间为什么不能直接进行通信,以下是两个进程的示意图:

从上面的图我们可以得到以下几点:

  • 一个进程空间分为:用户态和内核态,即把进程内用户和内核隔离开来

  • 进程之间,由于Android系统为每个进程分配了一个独立的虚拟机,用户空间和内核空间的数据不可交互

  • Binder作为进程间的介质,充当了中介,使得进程间的内核态可以通过Binder进行数据交互

IPC交互示意图


图中总共有四个元素,分别是充当客户端的Activity,服务端的StudentManagerService,充当服务管理者的IStudentManager以及充当访问介质的Binder驱动。他们的职责如下:

  • StudentManagerService: 服务提供者,这里面会有许多我们常用的服务,在本例中提供的服务就是添加学生以及获取学生列表。而在系统中则包括有ActivityService 、 WindowMananger等服务,这些系统服务提供的功能,对四大组件以及Window的工作提供的保障。

  • Activity: 服务调用者,一般就是我们的应用,在这里我们通过调用StudentManagerService的服务来完成工作。

  • IStudentManager: 他是负责管理服务的,在其内部通过map集合来存储Service与Binder的映射关系,这样客户端在向其请求服务的时候就能够返回特定的Binder。

  • Binder驱动: 他是IStudentManager连接各种Service的桥梁,同时也是客户端与服务端交流的桥梁。

总结起来说,应用程序(Activity)首先向IStudentManager发送请求StudentManagerService的服务,IStudentManager查看已经注册在里面的服务的列表,找到相应的服务后,通过Binder驱动将其中的Binder对象返回给客户端,从而完成对服务的请求。

源码分析

我们主要分析的就是IStudentManager这个类,从上面得到讲解我们已经知道它包含了两个类:Stub和Proxy。先来看看Proxy类

//Proxy.javapublic java.util.List<com.bob.aidltest.aidl.Student> getStudentList() throws android.os.RemoteException{
    android.os.Parcel _data = android.os.Parcel.obtain();
    android.os.Parcel _reply = android.os.Parcel.obtain();
    java.util.List<com.bob.aidltest.aidl.Student> _result;    try {
        _data.writeInterfaceToken(DESCRIPTOR);
        mRemote.transact(Stub.TRANSACTION_getStudentList, _data, _reply, 0);
        _reply.readException();
        _result = _reply.createTypedArrayList(com.bob.aidltest.aidl.Student.CREATOR);
    }    finally {
        _reply.recycle();
        _data.recycle();
    }    return _result;
}public void addStudent(com.bob.aidltest.aidl.Student student) 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 ((student!=null)) {
            _data.writeInt(1);
            student.writeToParcel(_data, 0);
        }        else {
            _data.writeInt(0);
        }
        mRemote.transact(Stub.TRANSACTION_addStudent, _data, _reply, 0);
        _reply.readException();
    }    finally {
        _reply.recycle();
        _data.recycle();
    }
}

上面截取了Proxy的两个方法,其中Proxy是运行在客户端的,他是用服务端返回来的Binder对象调用了public static IStudentManager asInterface(IBinder obj)方法返回来的。
既然Proxy运行在客户端,那么客户端也是通过Proxy来调用远程服务端的方法的,也就是说我们将调用方法需要用到的参数传递给Proxy,接着由Proxy来访问服务端,所以我们能够看到,Proxy将我们的参数写进了_data,而_reply则代表从服务端返回来的结果。
从代码中我们还看到客户端在将数据传递给服务端之后就处于阻塞状态,直到服务端返回结果,所以如果调用的服务端方法是一个耗时方法,那么我们就需要在子线程中进行工作了。
数据准备好之后当然是需要传递了,可以看到Proxy通过transact方法讲数据传递出去了,接下来就来看transact方法:

//Binder#transactpublic final boolean transact(int code, Parcel data, Parcel reply,            int flags) throws RemoteException {        if (false) Log.v("Binder", "Transact: " + code + " to " + this);        if (data != null) {
            data.setDataPosition(0);
        }        //调用了Binder的onTransact
        boolean r = onTransact(code, data, reply, flags);        if (reply != null) {
            reply.setDataPosition(0);
        }        return r;
    }

可以看到transact方法实际上调用了Binder的onTransact,而这里的Binder就是指Stub了,我们看一下Stub的定义:

public static abstract class Stub extends android.os.Binder implements com.bob.aidltest.aidl.IStudentManager

可以看到Stub确实继承了Binder并且也实现了IStudentManager接口,接下来我们继续看Stub中的onTransact方法:

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_getStudentList:
        {
            data.enforceInterface(DESCRIPTOR);
            java.util.List<com.bob.aidltest.aidl.Student> _result = this.getStudentList();
            reply.writeNoException();
            reply.writeTypedList(_result);            return true;
        }        case TRANSACTION_addStudent:
        {
            data.enforceInterface(DESCRIPTOR);
            com.bob.aidltest.aidl.Student _arg0;            if ((0!=data.readInt())) {
                _arg0 = com.bob.aidltest.aidl.Student.CREATOR.createFromParcel(data);
            }            else {
                _arg0 = null;
            }            this.addStudent(_arg0);
            reply.writeNoException();            return true;
        }
    }    return super.onTransact(code, data, reply, flags);
}

可以看到,服务端通过客户端传递过来的code常量来判断客户端需要调用的是哪个方法,接着就执行该方法,执行完之后如果有数据返回则将结果写入reply,接着Proxy就可以收到结果了。而整个通信过程也就结束了。
最后我借用Carson_Ho的一张流程图来描述这个完整的流程:


向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

AI