Android--AIDL,一種實現進程間通信的方式


一、什么是AIDL

  AIDL:Android Interface Definition Language,即Android接口定義語言,是我們實現IPC的一種常用手段。

  我們知道,Android系統中的進程之間不能共享內存,因此,需要提供一些機制在不同進程之間進行數據通信。為了使其他的應用程序也可以訪問本應用程序提供的服務,Android系統采用了遠程過程調用(Remote Procedure Call,RPC)方式來實現。與很多其他的基於RPC的解決方案一樣,Android使用一種接口定義語言(Interface Definition Language,IDL)來公開服務的接口。我們知道4個Android應用程序組件中的3個(Activity、BroadcastReceiver 和ContentProvider)都可以進行跨進程訪問,另外一個Android應用程序組件Service同樣可以。因此,可以將這種可以跨進程訪問的服務稱為AIDL(Android Interface Definition Language)服務。

二、實現AIDL時的所需類、接口

  1、Parcelable、Parcel

   當我們借助AIDL來在進程之間的復雜數據傳遞時,要實現Parcelable接口。那什么是Parcelable接口呢?我們看Android API的相關介紹:

   Interface for classes whose instances can be written to and restored from a Parcel. Classes implementing the Parcelable interface must also have a static field called CREATOR, which is an object implementing the Parcelable.Creator interface.

   Android中所有需要跨進程傳遞的復雜數據對象都必須實現Parceable接口。實現Parcelable接口是為了讓對象序列化,以進行進程間的數據傳遞。Java中也有一種實現序列化的方式:實現Serializable接口。但在Android中,不使用這類方式;實現Parcelable接口,一般更高效。

   我們看下該接口的源碼定義,這里為了方便,去除掉了一些代碼注釋片段:

public interface Parcelable {

    public static final int PARCELABLE_WRITE_RETURN_VALUE = 0x0001;
    
    public static final int CONTENTS_FILE_DESCRIPTOR = 0x0001;
    
    /**
     * Describe the kinds of special objects contained in this Parcelable's
     * marshalled representation.
     *  
     * @return a bitmask indicating the set of special object types marshalled
     * by the Parcelable.
     */
    public int describeContents();
    
    /**
     * Flatten this object in to a Parcel.
     * 
     * @param dest The Parcel in which the object should be written.
     * @param flags Additional flags about how the object should be written.
     * May be 0 or {@link #PARCELABLE_WRITE_RETURN_VALUE}.
     */
    public void writeToParcel(Parcel dest, int flags);

    /**
     * Interface that must be implemented and provided as a public CREATOR
     * field that generates instances of your Parcelable class from a Parcel.
     */
    public interface Creator<T> {
        /**
         * Create a new instance of the Parcelable class, instantiating it
         * from the given Parcel whose data had previously been written by
         * {@link Parcelable#writeToParcel Parcelable.writeToParcel()}.
         * 
         * @param source The Parcel to read the object's data from.
         * @return Returns a new instance of the Parcelable class.
         */
        public T createFromParcel(Parcel source);
        
        /**
         * Create a new array of the Parcelable class.
         * 
         * @param size Size of the array.
         * @return Returns an array of the Parcelable class, with every entry
         * initialized to null.
         */
        public T[] newArray(int size);
    }

    public interface ClassLoaderCreator<T> extends Creator<T> {
        public T createFromParcel(Parcel source, ClassLoader loader);
    }
}

  再看API文檔中給我們提供的一個實現Parcelable接口的典型示例: 

public class MyParcelable implements Parcelable {
     private int mData;

     public int describeContents() {
         return 0;
     }

     public void writeToParcel(Parcel out, int flags) {
         out.writeInt(mData);
     }

     public static final Parcelable.Creator<MyParcelable> CREATOR
             = new Parcelable.Creator<MyParcelable>() {
         public MyParcelable createFromParcel(Parcel in) {
             return new MyParcelable(in);
         }

         public MyParcelable[] newArray(int size) {
             return new MyParcelable[size];
         }
     };
     
     private MyParcelable(Parcel in) {
         mData = in.readInt();
     }
 }

   實現Parcelable接口的步驟:

   1、重寫describeContents方法,內容描述,我們返回0即可。

   2、重寫writeToParcel方法,將自己的對象包裝成一個Parcel對象,即將自定義對象寫入到一個Parcel對象,通過這個序列化的Parcel對象來傳遞數據。這里我們可以將Parcel對象看成是個容器,它包裝了我們定義的對象,方便在別的進程中重新獲取數據。

   3、實例化靜態內部對象CREATOR實現接口Parcelable.Creator,這里接口的實現必須與下面的描述一致,包括大小寫、限定修飾符等

 public static final Parcelable.Creator<MyParcelable> CREATOR

   注:其中public static final一個都不能少,內部對象CREATOR的名稱也不能改變,必須全部大寫。需重寫本接口中的兩個方 法:createFromParcel(Parcel in) 實現從Parcel容器中讀取傳遞數據值,封裝成Parcelable對象返回邏輯層,newArray(int size) 創建一個類型為T,長度為size的數組,僅一句話即可(return new T[size]),供外部類反序列化本類數組使用。

   writeToParcel方法將對象包裝成一個Parcel對象,createFromParcel方法又將Parcel對象映射成定義的對象。

   這里可以看下API文檔中的方法說明,再結合代碼理解:

   writeToParcel():Flatten this object in to a Parcel.

   createFromParcel():Create a new instance of the Parcelable class, instantiating it from the given Parcel whose data had previously been written by Parcelable.writeToParcel().

  可以將Parcel看成是一個流,通過writeToParcel把對象寫到流里面,在通過createFromParcel從流里讀取對象,只不過這個過程需要你來實現,因此寫的順序和讀的順序必須一致。

  2、ServiceConnection

   AIDL通常是一個Client端綁定一個Service端,在客戶端中遠程綁定一個Service服務,需要實現ServiceConnection接口。

   ServiceConnection:Interface for monitoring the state of an application service. See Service and Context.bindService() for more information.Like many callbacks from the system, the methods on this class are called from the main thread of your process.

   要實現ServiceConnection接口,

   必須重寫兩個方法:

   1)、public abstract void onServiceConnected (ComponentName name, IBinder service)

   調用這個方法來傳送Service中返回的IBinder對象,進而可以獲取到這個IBinder對象,在Client端調用AIDL的方法。

   2)、public abstract void onServiceDisconnected (ComponentName name)

   Called when a connection to the Service has been lost. This typically happens when the process hosting the service has crashed or been killed. Android系統在同service的連接意外丟失時調用這個.比如當service崩潰了或被強殺了.當客戶端解除綁定時,這個方法不會被調用.

   要綁定Service時,調用bindService,給它傳遞ServiceConnection的實現和封裝了約定的action的Intent對象。解除綁定時,調用unbindService()。

三、AIDL的實現步驟

   AIDL是IPC的一個輕量級實現,用了對於Java開發者來說很熟悉的語法。Android也提供了一個工具,可以自動創建Stub(類構架,類骨架)。當我們需要在應用間通信時,我們需要按以下幾步走:

    (1)在Eclipse Android工程的Java包目錄中建立一個擴展名為aidl的文件。該文件的語法類似於Java代碼,但會稍有不同。
    (2)如果aidl文件的內容是正確的,ADT會自動生成一個Java接口文件(*.java)。
    (3)建立一個服務類(Service的子類)。
    (4)實現由aidl文件生成的Java接口。
    (5)在AndroidManifest.xml文件中配置AIDL服務,尤其要注意的是,<action>標簽中android:name的屬性值就是客戶端要引用該服務的ID,也就是Intent類的參數值。
   
  如果我們是自定義了一個對象類型,並要將它運用到AIDL中,我們除了要這個類型實現Parcelable接口外,還要為這個類型創建一個aidl文件,目的是定義一個Parcelable類,告訴系統我們需要序列化和反序列化的類型。每一個實現了Parcelable的類型都需要對應的.aidl文件。AIDL編譯器在編譯AIDL文件時會自動查找此類文件。
 
  AIDL使用簡單的語法來聲明接口,描述其方法以及方法的參數和返回值。這些參數和返回值可以是任何類型,甚至是其他AIDL生成的接口。重要的是必須導入所有非內置類型,哪怕是這些類型是在與接口相同的包中。下面是AIDL能支持的數據類型:

  1.AIDL支持Java原始數據類型。

  2.AIDL支持String和CharSequence。

  3.AIDL支持傳遞其他AIDL接口,但你引用的每個AIDL接口都需要一個import語句,即使位於同一個包中。

  4.AIDL支持傳遞實現了android.os.Parcelable接口的復雜類型,同樣在引用這些類型時也需要import語句。

  5.AIDL支持java.util.List和java.util.Map,但是有一些限制。集合中項的允許數據類型包括Java原始類型、 String、CharSequence或是android.os.Parcelable。無需為List和Map提供import語句,但需要為 Parcelable提供import語句。

  6.非原始類型中,除了String和CharSequence以外,其余均需要一個方向指示符。方向指示符包括in、out、和inout。in表示由客戶端設置,out表示由服務端設置,inout表示客戶端和服務端都設置了該值。

  下面先看下Service端和Client端的代碼工程結構:

                                               

  首先我們創建自己的Student類並按照上述規則實現Parcelable接口,本文的示例都是以這個類對象為傳輸的對象:

package com.aidl.service;

public class Student implements Parcelable {

    public Student(String name, int age, String college) {
        this.name = name;
        this.age = age;
        this.college = college;
    }

    public Student() {
    }

    private Student(Parcel source) {
        readFromParcel(source);
    }

    private String name;
    private int age;
    private String college;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getCollege() {
        return college;
    }

    public void setCollege(String college) {
        this.college = college;
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(name);
        dest.writeInt(age);
        dest.writeString(college);
    }

    public void readFromParcel(Parcel source) {
        this.name = source.readString();
        this.age = source.readInt();
        this.college = source.readString();
    }

    public static final Parcelable.Creator<Student> CREATOR = new Parcelable.Creator<Student>() {

        @Override
        public Student createFromParcel(Parcel source) {
            return new Student(source);
        }

        @Override
        public Student[] newArray(int size) {
            return new Student[size];
        }
    };
}

   軟后創建該類對應的aidl文件,告訴我們要序列化和反序列化的類型是Student:

package com.aidl.service;

parcelable Student;  

   創建aidl接口文件,本文是在com.file.aidl包中,這里要注意類型的導入,不然編譯器會識別不出類型:

package com.file.aidl;

import com.aidl.client.Student;

interface StudentInfo {
     String getStudentInfo(in Student p, int year);
     Student setStudentInfo(in Student p);
     int getStudentListCount(in List<Student> list);
}

   這里要注意,Client端和Service端中,aidl接口文件要包中在同名的package,文件名也要相同,不然我們在運行程序時,會拋出: java.lang.SecurityException: Binder invocation to an incorrect interface 進程通信異常。最簡單的方法就是在Service端編寫好代碼后,直接copy到Client端工程中。

   eclipse會幫我們編譯出對應的.java文件:

/*
 * This file is auto-generated.  DO NOT MODIFY.
 * Original file: D:\\Users\\xzm\\workspace\\AndroidAIDLServiceTest1\\src\\com\\file\\aidl\\StudentInfo.aidl
 */
package com.file.aidl;

public interface StudentInfo extends android.os.IInterface {
    /** Local-side IPC implementation stub class. */
    public static abstract class Stub extends android.os.Binder implements com.file.aidl.StudentInfo {
        private static final java.lang.String DESCRIPTOR = "com.file.aidl.StudentInfo";

        /** Construct the stub at attach it to the interface. */
        public Stub() {
            this.attachInterface(this, DESCRIPTOR);
        }

        /**
         * Cast an IBinder object into an com.file.aidl.StudentInfo interface, generating a proxy if needed.
         */
        public static com.file.aidl.StudentInfo asInterface(android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if (((iin != null) && (iin instanceof com.file.aidl.StudentInfo))) {
                return ((com.file.aidl.StudentInfo) iin);
            }
            return new com.file.aidl.StudentInfo.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_getStudentInfo: {
                data.enforceInterface(DESCRIPTOR);
                com.aidl.service.Student _arg0;
                if ((0 != data.readInt())) {
                    _arg0 = com.aidl.service.Student.CREATOR.createFromParcel(data);
                } else {
                    _arg0 = null;
                }
                int _arg1;
                _arg1 = data.readInt();
                java.lang.String _result = this.getStudentInfo(_arg0, _arg1);
                reply.writeNoException();
                reply.writeString(_result);
                return true;
            }
            case TRANSACTION_setStudentInfo: {
                data.enforceInterface(DESCRIPTOR);
                com.aidl.service.Student _arg0;
                if ((0 != data.readInt())) {
                    _arg0 = com.aidl.service.Student.CREATOR.createFromParcel(data);
                } else {
                    _arg0 = null;
                }
                com.aidl.service.Student _result = this.setStudentInfo(_arg0);
                reply.writeNoException();
                if ((_result != null)) {
                    reply.writeInt(1);
                    _result.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
                } else {
                    reply.writeInt(0);
                }
                return true;
            }
            case TRANSACTION_getStudentListCount: {
                data.enforceInterface(DESCRIPTOR);
                java.util.List<com.aidl.service.Student> _arg0;
                _arg0 = data.createTypedArrayList(com.aidl.service.Student.CREATOR);
                int _result = this.getStudentListCount(_arg0);
                reply.writeNoException();
                reply.writeInt(_result);
                return true;
            }
            }
            return super.onTransact(code, data, reply, flags);
        }

        private static class Proxy implements com.file.aidl.StudentInfo {
            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.lang.String getStudentInfo(com.aidl.service.Student p, int year) throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                java.lang.String _result;
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    if ((p != null)) {
                        _data.writeInt(1);
                        p.writeToParcel(_data, 0);
                    } else {
                        _data.writeInt(0);
                    }
                    _data.writeInt(year);
                    mRemote.transact(Stub.TRANSACTION_getStudentInfo, _data, _reply, 0);
                    _reply.readException();
                    _result = _reply.readString();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }

            @Override
            public com.aidl.service.Student setStudentInfo(com.aidl.service.Student p) throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                com.aidl.service.Student _result;
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    if ((p != null)) {
                        _data.writeInt(1);
                        p.writeToParcel(_data, 0);
                    } else {
                        _data.writeInt(0);
                    }
                    mRemote.transact(Stub.TRANSACTION_setStudentInfo, _data, _reply, 0);
                    _reply.readException();
                    if ((0 != _reply.readInt())) {
                        _result = com.aidl.service.Student.CREATOR.createFromParcel(_reply);
                    } else {
                        _result = null;
                    }
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }

            @Override
            public int getStudentListCount(java.util.List<com.aidl.service.Student> list) 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);
                    _data.writeTypedList(list);
                    mRemote.transact(Stub.TRANSACTION_getStudentListCount, _data, _reply, 0);
                    _reply.readException();
                    _result = _reply.readInt();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }
        }

        static final int TRANSACTION_getStudentInfo = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
        static final int TRANSACTION_setStudentInfo = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
        static final int TRANSACTION_getStudentListCount = (android.os.IBinder.FIRST_CALL_TRANSACTION + 2);
    }

    public java.lang.String getStudentInfo(com.aidl.service.Student p, int year) throws android.os.RemoteException;

    public com.aidl.service.Student setStudentInfo(com.aidl.service.Student p) throws android.os.RemoteException;

    public int getStudentListCount(java.util.List<com.aidl.service.Student> list) throws android.os.RemoteException;
}

   最后,編寫自己的Service:

package com.aidl.service;public class IAIDLService extends Service {

    @Override
    public IBinder onBind(Intent intent) {
        return new MyBinder();
    }

    class MyBinder extends StudentInfo.Stub {

        @Override
        public String getStudentInfo(Student p, int year)
                throws RemoteException {
            return "Student'info is: " + "Name: " + p.getName() + " Age: "
                    + p.getAge() + " College: " + p.getCollege() + " Time: "
                    + year;
        }

        @Override
        public Student setStudentInfo(Student p) throws RemoteException {
            if (p.getAge() == 25) {
                p.setCollege("SWJTU");
                p.setName("Jhon");
            } else
                p.setCollege("SCU");
            return p;
        }

        @Override
        public int getStudentListCount(List<Student> list)
                throws RemoteException {
            return list.size();
        }
    }
}

   最后,在配置文件中配置自己的服務:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.aidl.service"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="14"
        android:targetSdkVersion="21" />

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        
        <service android:name="IAIDLService">
            <intent-filter>
                <action android:name="com.aidl.student"/>
            </intent-filter>
        </service>
        
    </application>

</manifest>

  這里,Service端的代碼就結束了。接下來是Client端的代碼。

  有文件目錄可知,Service端和Client端有幾部分代碼可以共用,我們直接copy過去即可,再次提醒,要注意aidl接口文件的路徑。

  先看布局文件:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="${relativePackage}.${activityClass}" >

    <Button 
        android:id="@+id/connect"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="連接服務"
        />
    
	<Button 
	    android:id="@+id/getInfo"
	    android:layout_width="fill_parent"
	    android:layout_height="wrap_content"
	    android:text="獲取學生信息"
	    />
	<Button 
	    android:id="@+id/setInfo"
	    android:layout_width="fill_parent"
	    android:layout_height="wrap_content"
	    android:text="設置學生信息"
	    />
	
	<Button 
	    android:id="@+id/getCount"
	    android:layout_width="fill_parent"
	    android:layout_height="wrap_content"
	    android:text="獲取學生數量"
	    />

	<Button 
	    android:id="@+id/disconnect"
	    android:layout_width="fill_parent"
	    android:layout_height="wrap_content"
	    android:text="斷開連接"
	    />
	
</LinearLayout>

    接着是主Activity文件:

package com.aidl.client;public class MainActivity extends Activity {

    private static final String action = "com.aidl.student";

    private Button getButton;
    private Button setButton;
    private Button getCountButton;
    private Button bConnect;
    private Button bDisconnect;

    private StudentInfo info;

    private boolean bool = false;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        getButton = (Button) findViewById(R.id.getInfo);
        setButton = (Button) findViewById(R.id.setInfo);
        getCountButton = (Button) findViewById(R.id.getCount);
        bConnect = (Button) findViewById(R.id.connect);
        bDisconnect = (Button) findViewById(R.id.disconnect);

        bConnect.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                if (!bool) {
                    Intent intent = new Intent(action); // 這里的action就是Service端配置的action,必須要保持一致
                    Log.d("test", "before bindService()");
                    bindService(intent, connection, BIND_AUTO_CREATE); // 綁定到遠程Service
                    Log.d("test", "after bindService()");
                }
                bConnect.setClickable(false);
                bDisconnect.setClickable(true);
            }
        });
        
        getButton.setOnClickListener(new OnClickListener() {
            
            @Override
            public void onClick(View v) {
                Student stu = new Student("Jim", 18, "Bb college");
                try {
                    String stuInfo = info.getStudentInfo(stu, 2010);
                    Toast.makeText(getApplicationContext(), stuInfo,
                            Toast.LENGTH_SHORT).show();
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        });

        setButton.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                Student stu = new Student("Jerry", 25, "Cc college");
                try {
                    Student s = info.setStudentInfo(stu);
                    String info = "Name: " + s.getName() + " Age: "
                            + s.getAge() + " College: " + s.getCollege();
                    Toast.makeText(getApplicationContext(), info,
                            Toast.LENGTH_SHORT).show();
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        });

        getCountButton.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                List<Student> list = new ArrayList<Student>();
                list.add(new Student("cc", 19, "Dd coolege"));
                list.add(new Student("dd", 20, "Ee coolege"));
                try {
                    int count = info.getStudentListCount(list);
                    Toast.makeText(getApplicationContext(),
                            "The count: " + count, Toast.LENGTH_SHORT).show();
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        });
        
        bDisconnect.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                if (bool) {
                    Log.d("test", "before unbindService()");
                    unbindService(connection);
                    Log.d("test", "after unbindService()");
                }
                bDisconnect.setClickable(false);
                bConnect.setClickable(true);
                bool = false;
            }
        });
    }

    private MyConnection connection = new MyConnection();

    private class MyConnection implements ServiceConnection {

        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            info = StudentInfo.Stub.asInterface(service); // 綁定成功就會拿到傳過來的MyBinder對象
            if (info == null) {
                Toast.makeText(getApplicationContext(), "服務連接失敗!",
                        Toast.LENGTH_SHORT).show();
                return;
            } else {
                bool = true;
                Toast.makeText(getApplicationContext(), "服務連接成功!",
                        Toast.LENGTH_SHORT).show();
            }
            Log.d("test", "onServiceConnected");
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            Log.d("test", "onServiceDisconnected");
        }
    }
}

上幾張運行效果圖,運行時要先連接服務,再進行后續操作:

               源碼工程下載連接

參考資料:

http://www.cnblogs.com/renqingping/archive/2012/10/25/Parcelable.html

http://blog.csdn.net/liuhe688/article/details/6409708


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM