<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    posts - 101,  comments - 29,  trackbacks - 0

            在Android系統中,不同的應用程序是不能直接讀寫對方的數據文件的,如果它們想共享數據的話,只能通過Content Provider組件來實現。那么,Content Provider組件又是如何突破應用程序邊界權限控制來實現在不同的應用程序之間共享數據的呢?在前面的文章中,我們已經簡要介紹過它是通過Binder進程間通信機制以及匿名共享內存機制來實現的,在本文中,我們將詳細分析它的數據共享原理。

            Android應用程序之間不能直接訪問對方的數據文件的障礙在于每一個應用程序都有自己的用戶ID,而每一個應用程序所創建的文件的讀寫權限都是只賦予給自己所屬的用戶,因此,就限制了應用程序之間相互讀寫數據的操作,關于Android應用程序的權限問題,具體可以參考前面一篇文章Android應用程序組件Content Provider簡要介紹和學習計劃。通過前面Android進程間通信(IPC)機制Binder簡要介紹和學習計劃等一系列文章的學習,我們知道,Binder進程間通信機制可以突破了以應用程序為邊界的權限控制來實現在不同應用程序之間傳輸數據,而Content Provider組件在不同應用程序之間共享數據正是基于Binder進程間通信機制來實現的。雖然Binder進程間通信機制突破了以應用程序為邊界的權限控制,但是它是安全可控的,因為數據的訪問接口是由數據的所有者來提供的,換句話來說,就是數據提供方可以在接口層來實現安全控制,決定哪些數據是可以讀,哪些數據可以寫。雖然Content Provider組件本身也提供了讀寫權限控制,但是它的控制粒度是比較粗的,如果有需要,我們還是可以在接口訪問層做更細粒度的權限控制以達到數據安全的目的。

            Binder進程間通信機制雖然打通了應用程序之間共享數據的通道,但是還有一個問題需要解決,那就是數據要以什么來作來媒介來傳輸。我們知道,應用程序采用Binder進程間通信機制進行通信時,要傳輸的數據都是采用函數參數的形式進行的,對于一般的進程間調來來說,這是沒有問題的,然而,對于應用程序之間的共享數據來說,它們的數據量可能是非常大的,如果還是簡單的用函數參數的形式來傳遞,效率就會比較低下。通過前面Android系統匿名共享內存Ashmem(Anonymous Shared Memory)簡要介紹和學習計劃等一系列文章的學習,我們知道,在應用程序進程之間以匿名共享內存的方式來傳輸數據效率是非常高的,因為它們之間只需要傳遞一個文件描述符就可以了。因此,Content Provider組件在不同應用程序之間傳輸數據正是基于匿名共享內存機制來實現的。

            在繼續分析Content Provider組件在不同應用程序之間共享數據的原理之前,我們假設應用程序之間需要共享的數據是保存在數據庫(SQLite)中的,因此,接下來的分析都是基于SQLite數據庫游標(SQLiteCursor)來進行的。SQLiteCursor在共享數據的傳輸過程中發揮著重要的作用,因此,我們先來它和其它相關的類的關系圖,如下所示:


            首先在第三方應用程序這一側,當它需要訪問Content Provider中的數據時,它會在本進程中創建一個CursorWindow對象,它在內部創建了一塊匿名共享內存,同時,它實現了Parcel接口,因此它可以在進程間傳輸。接下來第三方應用程序把這個CursorWindow對象(連同它內部的匿名共享內存文件描述符)通過Binder進程間調用傳輸到Content Provider這一側。這個匿名共享內存文件描述符傳輸到Binder驅動程序的時候,Binder驅動程序就會在目標進程(即Content Provider所在的進程)中創建另一個匿名共享文件描述符,指向前面已經創建好的匿名共享內存,因此,就實現了在兩個進程中共享同一塊匿名內存,這個過程具體可以參考Android系統匿名共享內存Ashmem(Anonymous Shared Memory)在進程間共享的原理分析一文。

            在Content Provider這一側,利用在Binder驅動程序為它創建好的這個匿名共享內存文件描述符,在本進程中創建了一個CursorWindow對象。現在,Content Provider開始要從本地中從數據庫中查詢第三方應用程序想要獲取的數據了。Content Provider首先會創建一個SQLiteCursor對象,即SQLite數據庫游標對象,它繼承了AbstractWindowedCursor類,后者又繼承了AbstractCursor類,而AbstractCursor類又實現了CrossProcessCursor和Cursor接口。其中,最重要的是在AbstractWindowedCursor類中,有一個成員變量mWindow,它的類型為CursorWindow,這個成員變量是通過AbstractWindowedCursor的子類SQLiteCursor的setWindow成員函數來設置的。這個SQLiteCursor對象設置好了父類AbstractWindowedCursor類的mWindow成員變量之后,它就具有傳輸數據的能力了,因為這個mWindow對象內部包含一塊匿名共享內存。此外,這個SQLiteCursor對象的內部有兩個成員變量,一個是SQLite數據庫對象mDatabase,另外一個是SQLite數據庫查詢對象mQuery。SQLite數據庫查詢對象mQuery的類型為SQLiteQuery,它繼承了SQLiteProgram類,后者又繼承了SQLiteClosable類。SQLiteProgram類代表一個數據庫存查詢計劃,它的成員變量mCompiledSql包含了一個已經編譯好的SQL查詢語句,SQLiteCursor對象就是利用這個編譯好的SQL查詢語句來獲得數據的,但是它并不是馬上就去獲取數據的,而是等到需要時才去獲取。

            那么,要等到什么時候才會需要獲取數據呢?一般來說,如果第三方應用程序在請求Content Provider返回數據時,如果指定了要返回關于這些數據的元信息時,例如數據條目的數量,那么Content Provider在把這個SQLiteCursor對象返回給第三方應用程序之前,就會去獲取數據,因為只有獲取了數據之后,才知道數據條目的數量是多少。SQLiteCursor對象通過調用成員變量mQuery的fillWindow成員函數來把從SQLite數據庫中查詢得到的數據保存其父類AbstractWindowedCursor的成員變量mWindow中去,即保存到第三方應用程序創建的這塊匿名共享內存中去。如果第三方應用程序在請求Content Provider返回數據時,沒有指定要返回關于這些數據的元信息,那么,就要等到第三方應用程序首次調用這個從Content Provider處返回的SQLiteCursor對象的數據獲取方法時,才會真正執行從數據庫存中查詢數據的操作,例如調用了SQLiteCursor對象的getCount或者moveToFirst成員函數時。這是一種數據懶加載機制,需要的時候才去加載,這樣就提高了數據傳輸過程中的效率。

            上面說到,Content Provider向第三方應用程序返回的數據實際上是一個SQLiteCursor對象,那么,這個SQLiteCursor對象是如何傳輸到第三方應用程序的呢?因為它本身并不是一個Binder對象,我們需要對它進行適配一下。首先,Content Provider會根據這個SQLiteCursor對象來創建一個CursorToBulkCursorAdaptor適配器對象,這個適配器對象是一個Binder對象,因此,它可以在進程間傳輸,同時,它實現了IBulkCursor接口。Content Provider接著就通過Binder進程間通信機制把這個CursorToBulkCursorAdaptor對象返回給第三方應用程序,第三方應用程序得到了這個CursorToBulkCursorAdaptor之后,再在本地創建一個BulkCursorToCursorAdaptor對象,這個BulkCursorToCursorAdaptor對象的繼承結構和SQLiteCursor對象是一樣的,不過,它沒有設置父類AbstractWindowedCursor的mWindow成員變量,因此,它只可以通過它內部的CursorToBulkCursorAdaptor對象引用來訪問匿名共享內存中的數據,即通過訪問Content Provider這一側的SQLiteCursor對象來訪問共享數據。

            上面描述的數據共享模型還是比較復雜的,一下子理解不了也不要緊,下面我們還會結合第三方應用程序和Content Provider傳輸共享數據的完整過程來進一步分析Content Provider的數據共享原理,到時候再回過頭來看這個數據共享模型就會清晰很多了。在接下來的內容中,我們就繼續Android應用程序組件Content Provider應用實例一文的例子來分析Content Provider在不同應用程序之間共享數據的原理。在Android應用程序組件Content Provider應用實例這篇文章介紹的應用程序Article中,它的主窗口MainActivity是通過調用它的內部ArticlesAdapter對象的getArticleByPos成員函數來把ArticlesProvider中的文章信息條目一條一條地取回來顯示在ListView中的,在這篇文章中,我們就從ArticlesAdapter類的getArticleByPos函數開始,一步一步地分析第三方應用程序Article從ArticlesProvider這個Content Provider中獲取數據的過程。同樣,我們先來看看這個過程的序列圖,然后再詳細分析每一個步驟:


          Step 1. ArticlesAdapter.getArticleByPos

          這個函數定義在前面一篇文章Android應用程序組件Content Provider應用實例介紹的應用程序Artilce源代碼工程目錄下,在文件為packages/experimental/Article/src/shy/luo/article/ArticlesAdapter.java中:

    public class ArticlesAdapter {
    	......
    
    	private ContentResolver resolver = null;
    
    	public ArticlesAdapter(Context context) {
    		resolver = context.getContentResolver();
    	}
    
    	......
    
    	public Article getArticleByPos(int pos) {
    		Uri uri = ContentUris.withAppendedId(Articles.CONTENT_POS_URI, pos);
    
    		String[] projection = new String[] {
    			Articles.ID,
    			Articles.TITLE,
    			Articles.ABSTRACT,
    			Articles.URL
    		};
    
    		Cursor cursor = resolver.query(uri, projection, null, null, Articles.DEFAULT_SORT_ORDER);
    		if (!cursor.moveToFirst()) {
    			return null;
    		}
    
    		int id = cursor.getInt(0);
    		String title = cursor.getString(1);
    		String abs = cursor.getString(2);
    		String url = cursor.getString(3);
    
    		return new Article(id, title, abs, url);
    	}
    }
          這個函數通過應用程序上下文的ContentResolver接口resolver的query函數來獲得與Articles.CONTENT_POS_URI這個URI對應的文章信息條目。常量Articles.CONTENT_POS_URI是在應用程序ArticlesProvider中定義的,它的值為“content://shy.luo.providers.articles/pos”,通過調用ContentUris.withAppendedId函數來在后面添加了一個整數,表示要獲取指定位置的文章信息條目。這個位置是指ArticlesProvider這個Content Provider中的所有文章信息條目按照Articles.DEFAULT_SORT_ORDER來排序后得到的位置的,常量Articles.DEFAULT_SORT_ORDER也是在應用程序ArticlesProvider中定義的,它的值為“_id asc”,即按照文章信息的ID值從小到大來排列。

     

          Step 2. ContentResolver.query

          這個函數定義在frameworks/base/core/java/android/content/ContentResolver.java文件中:

    public abstract class ContentResolver {
    	......
    
    	public final Cursor query(Uri uri, String[] projection,
    			String selection, String[] selectionArgs, String sortOrder) {
    		IContentProvider provider = acquireProvider(uri);
    		if (provider == null) {
    			return null;
    		}
    		try {
    			......
    			Cursor qCursor = provider.query(uri, projection, selection, selectionArgs, sortOrder);
    			......
    			
    			return new CursorWrapperInner(qCursor, provider);
    		} catch (RemoteException e) {
    			......
    		} catch(RuntimeException e) {
    			......
    		}
    	}
    
    	......
    }
          這個函數首先通過調用acquireProvider函數來獲得與參數uri對應的Content Provider接口,然后再通過這個接口的query函數來獲得相應的數據。我們先來看看acquireProvider函數的實現,再回過頭來分析這個Content Provider接口的query函數的實現。

     

          Step 3. ContentResolver.acquireProvider

          Step 4. ApplicationContentResolver.acquireProvider

          Step 5. ActivityThread.acquireProvider

          Step 6. ActivityThread.getProvider

          從Step 3到Step 6是獲取與上面Step 2中傳進來的參數uri對應的Content Provider接口的過程。在前面一篇文章Android應用程序組件Content Provider的啟動過程源代碼分析中,我們已經詳細介紹過這個過程了,這里不再詳述。不過這里我們假設,這個Content Provider接口之前已經創建好了,因此,在Step 6的ActivityThread.getProvider函數中,通過getExistingProvider函數就把之前已經好的Content Provider接口返回來了。

          回到Step 2中的ContentResolver.query函數中,它繼續調用這個返回來的Content Provider接口來獲取數據。從這篇文章Android應用程序組件Content Provider的啟動過程源代碼分析中,我們知道,這個Content Provider接口實際上是一個在ContentProvider類的內部所創建的一個Transport對象的遠程接口。這個Transport類繼承了ContentProviderNative類,是一個Binder對象的Stub類,因此,接下來就會進入到這個Binder對象的Proxy類ContentProviderProxy中執行query函數。

          Step 7. ContentProviderProxy.query

          這個函數定義在frameworks/base/core/java/android/content/ContentProviderNative.java文件中:

    final class ContentProviderProxy implements IContentProvider {
    	......
    
    	public Cursor query(Uri url, String[] projection, String selection,
    			String[] selectionArgs, String sortOrder) throws RemoteException {
    		//TODO make a pool of windows so we can reuse memory dealers
    		CursorWindow window = new CursorWindow(false /* window will be used remotely */);
    		BulkCursorToCursorAdaptor adaptor = new BulkCursorToCursorAdaptor();
    		IBulkCursor bulkCursor = bulkQueryInternal(
    			url, projection, selection, selectionArgs, sortOrder,
    			adaptor.getObserver(), window,
    			adaptor);
    		if (bulkCursor == null) {
    			return null;
    		}
    		return adaptor;
    	}
    
    	......
    }

            這個函數首先會創建一個CursorWindow對象,前面已經說過,這個CursorWindow對象包含了一塊匿名共享內存,它的作用是把這塊匿名共享內存通過Binder進程間通信機制傳給Content Proivder,好讓Content Proivder在里面返回所請求的數據。下面我們就先看看這個CursorWindow對象的創建過程,重點關注它是如何在內部創建匿名共享內存的,然后再回過頭來看看它調用bulkQueryInternal函數來做了些什么事情。

            CursorWindow類定義在frameworks/base/core/java/android/database/CursorWindow.java文件中,我們來看看它的構造函數的實現:

     

    public class CursorWindow extends SQLiteClosable implements Parcelable {
    	......
    
    	private int nWindow;
    
    	......
    
    	public CursorWindow(boolean localWindow) {
    		......
    
    		native_init(localWindow);
    	}
    
    	......
    }

     

            它主要調用本地方法native_init來執行初始化的工作,主要就是創建匿名共享內存了,傳進來的參數localWindow為false,表示這個匿名共享內存只能通過遠程調用來訪問,即前面我們所說的,通過Content Proivder返回來的Cursor接口來訪問這塊匿名共享內存里面的數據。

            Step 8. CursorWindow.native_init

            這是一個JNI方法,它對應定義在frameworks/base/core/jni/android_database_CursorWindow.cpp文件中的native_init_empty函數:

     

    static JNINativeMethod sMethods[] =
    {
         /* name, signature, funcPtr */
        {"native_init", "(Z)V", (void *)native_init_empty},
        ......
    };
    
             函數native_init_empty的定義如下所示:

     

     

    static void native_init_empty(JNIEnv * env, jobject object, jboolean localOnly)
    {
    	......
    
    	CursorWindow * window;
    
    	window = new CursorWindow(MAX_WINDOW_SIZE);
    	......
    
    	if (!window->initBuffer(localOnly)) {
    		......
    	}
    
    	......
    	SET_WINDOW(env, object, window);
    }
             這個函數在C++層創建了一個CursorWindow對象,然后通過調用SET_WINDOW宏來把這個C++層的CursorWindow對象與Java層的CursorWindow對象關系起來:

     

     

    #define SET_WINDOW(env, object, window) (env->SetIntField(object, gWindowField, (int)window))
             這里的gWindowField即定義為Java層的CursorWindow對象中的nWindow成員變量:

     

     

    static jfieldID gWindowField;
    
    ......
    
    int register_android_database_CursorWindow(JNIEnv * env)
    {
    	jclass clazz;
    
    	clazz = env->FindClass("android/database/CursorWindow");
    	......
    
    	gWindowField = env->GetFieldID(clazz, "nWindow", "I");
    
    	......
    }
            這種用法在Android應用程序框架層中非常普遍。

     

            下面我們重點關注C++層的CursorWindow對象的initBuffer函數的實現。

            Step 9. CursorWindow.initBuffer

            C++層的CursorWindow類定義在frameworks/base/core/jni/CursorWindow.cpp文件中:

     

    bool CursorWindow::initBuffer(bool localOnly)
    {
    	......
    
    	sp<MemoryHeapBase> heap;
    	heap = new MemoryHeapBase(mMaxSize, 0, "CursorWindow");
    	if (heap != NULL) {
    		mMemory = new MemoryBase(heap, 0, mMaxSize);
    		if (mMemory != NULL) {
    			mData = (uint8_t *) mMemory->pointer();
    			if (mData) {
    				mHeader = (window_header_t *) mData;
    				mSize = mMaxSize;
    
    				......
    			}
    		}
    		......
    	} else {
    		......
    	}
    }
            這里我們就可以很清楚地看到,在CursorWindow類的內部有一個成員變量mMemory,它的類型是MemoryBase。MemoryBase類為我們封裝了匿名共享內存的訪問以及在進程間的傳輸等問題,具體可以參考前面一篇文章Android系統匿名共享內存(Anonymous Shared Memory)C++調用接口分析,這里就不再詳述了。

     

            通過Step 8和Step 9兩步,用來在第三方應用程序和Content Provider之間傳輸數據的媒介就準備好了,我們回到Step 7中,看看系統是如何把這個匿名共享存傳遞給Content Provider使用的。在Step 7中,最后調用bulkQueryInternal函數來進一步操作。

            Step 10. ContentProviderProxy.bulkQueryInternal

          這個函數定義在frameworks/base/core/java/android/content/ContentProviderNative.java文件中:

    final class ContentProviderProxy implements IContentProvider
    {
    	......
    
    	private IBulkCursor bulkQueryInternal(
    			Uri url, String[] projection,
    			String selection, String[] selectionArgs, String sortOrder,
    			IContentObserver observer, CursorWindow window,
    			BulkCursorToCursorAdaptor adaptor) throws RemoteException {
    		Parcel data = Parcel.obtain();
    		Parcel reply = Parcel.obtain();
    
    		data.writeInterfaceToken(IContentProvider.descriptor);
    
    		url.writeToParcel(data, 0);
    		int length = 0;
    		if (projection != null) {
    			length = projection.length;
    		}
    		data.writeInt(length);
    		for (int i = 0; i < length; i++) {
    			data.writeString(projection[i]);
    		}
    		data.writeString(selection);
    		if (selectionArgs != null) {
    			length = selectionArgs.length;
    		} else {
    			length = 0;
    		}
    		data.writeInt(length);
    		for (int i = 0; i < length; i++) {
    			data.writeString(selectionArgs[i]);
    		}
    		data.writeString(sortOrder);
    		data.writeStrongBinder(observer.asBinder());
    		window.writeToParcel(data, 0);
    
    		// Flag for whether or not we want the number of rows in the
    		// cursor and the position of the "_id" column index (or -1 if
    		// non-existent).  Only to be returned if binder != null.
    		final boolean wantsCursorMetadata = (adaptor != null);
    		data.writeInt(wantsCursorMetadata ? 1 : 0);
    
    		mRemote.transact(IContentProvider.QUERY_TRANSACTION, data, reply, 0);
    
    		DatabaseUtils.readExceptionFromParcel(reply);
    
    		IBulkCursor bulkCursor = null;
    		IBinder bulkCursorBinder = reply.readStrongBinder();
    		if (bulkCursorBinder != null) {
    			bulkCursor = BulkCursorNative.asInterface(bulkCursorBinder);
    
    			if (wantsCursorMetadata) {
    				int rowCount = reply.readInt();
    				int idColumnPosition = reply.readInt();
    				if (bulkCursor != null) {
    					adaptor.set(bulkCursor, rowCount, idColumnPosition);
    				}
    			}
    		}
    
    		data.recycle();
    		reply.recycle();
    
    		return bulkCursor;
    	}
    
    	......
    }
          這個函數有點長,不過它的邏輯很簡單,就是把查詢參數都寫到一個Parcel對象data中去,然后通過下面Binder進程間通信機制把查詢請求傳給Content Provider處理:

     

    mRemote.transact(IContentProvider.QUERY_TRANSACTION, data, reply, 0);
          從這個Binder調用返回以后,就會得到一個IBulkCursor接口,它是一個Binder引用,實際是指向在Content Provider這一側創建的一個CursorToBulkCursorAdaptor對象,后面我們將會看到。有了這個IBulkCursor接口之后,我們就可以通過Binder進程間調用來訪問從Content Provider中查詢得到的數據了。這個IBulkCursor接口最終最設置了上面Step 7中創建的BulkCursorToCursorAdaptor對象adaptor中去:

     

    adaptor.set(bulkCursor, rowCount, idColumnPosition);
          BulkCursorToCursorAdaptor類實現了Cursor接口,因此,我們可以通過Curosr接口來訪問這些查詢得到的共享數據。

     

          在前面把查詢參數寫到Parcel對象data中去的過程中,有兩個步驟是比較重要的,分別下面這段執行語句:

    window.writeToParcel(data, 0);
    
    // Flag for whether or not we want the number of rows in the
    // cursor and the position of the "_id" column index (or -1 if
    // non-existent).  Only to be returned if binder != null.
    final boolean wantsCursorMetadata = (adaptor != null);
    data.writeInt(wantsCursorMetadata ? 1 : 0);
          調用window.writeToParcel是把window對象內部的匿名共享內存塊通過Binder進程間通信機制傳輸給Content Provider來使用;而當傳進來的參數adaptor不為null時,就會往data中寫入一個整數1,表示讓Content Provider把查詢得到數據的元信息一起返回來,例如數據的行數、數據行的ID列的索引位置等信息,這個整數值會促使Content Provider把前面說的IBulkCursor接口返回給第三方應用程序之前,真正執行一把數據庫查詢操作,后面我們將看到這個過程。

     

          現在,我們重點來關注一下CursorWindow類的writeToParcel函數,看看它是如何把它內部的匿名共享內存對象寫到數據流data中去的。

          Step 11. CursorWindow.writeToParcel

          這個函數定義在frameworks/base/core/java/android/database/CursorWindow.java文件中:

    public class CursorWindow extends SQLiteClosable implements Parcelable {
    	......
    
    	public void writeToParcel(Parcel dest, int flags) {
    		......
    		dest.writeStrongBinder(native_getBinder());
    		......
    	}
    
    	......
    }
          這個函數最主要的操作就是往數據流dest寫入一個Binder對象,這個Binder對象是通過調用本地方法native_getBinder來得到的。

     

          Step 12. CursorWindow.native_getBinder

          這個函數定義在frameworks/base/core/jni/android_database_CursorWindow.cpp文件中:

    static jobject native_getBinder(JNIEnv * env, jobject object)
    {
    	CursorWindow * window = GET_WINDOW(env, object);
    	if (window) {
    		sp<IMemory> memory = window->getMemory();
    		if (memory != NULL) {
    			sp<IBinder> binder = memory->asBinder();
    			return javaObjectForIBinder(env, binder);
    		}
    	}
    	return NULL;
    }
          在前面的Step 8中,我們在C++層創建了一個CursorWindow對象,這個對象保存在Java層創建的CursorWindow對象的成員變量nWindow中,這里通過GET_WINDOW宏來把這個在C++層創建的CurosrWindow對象返回來:

     

    #define GET_WINDOW(env, object) ((CursorWindow *)env->GetIntField(object, gWindowField))
          獲得了這個CursorWindow對象以后,就調用它的getMemory函數來獲得一個IMemory接口,這是一個Binder接口,具體可以參考前面一篇文章Android系統匿名共享內存(Anonymous Shared Memory)C++調用接口分析

     

          Step 13. CursorWindow.getMemory

          這個函數定義在frameworks/base/core/jni/CursorWindow.h文件中:

    class CursorWindow
    {
    public:
    	......
    
    	sp<IMemory>         getMemory() {return mMemory;}
    
    	......
    }
          這個CursorWindow對象的成員變量mMemory就是在前面Step 9中創建的了。

     

          這樣,在第三方應用程序這一側創建的匿名共享存對象就可以傳遞給Content Provider來使用了。回到前面的Step 10中,所有的參數都就準備就緒以后,就通過Binder進程間通信機制把數據查詢請求發送給相應的Content Proivder了。這個請求是在ContentProviderNative類的onTransact函數中響應的。

          Step 14. ContentProviderNative.onTransact

          這個函數定義在frameworks/base/core/java/android/content/ContentProviderNative.java文件中:

    abstract public class ContentProviderNative extends Binder implements IContentProvider {
    	......
    
    	@Override
    	public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
    	throws RemoteException {
    		try {
    			switch (code) {
    			case QUERY_TRANSACTION:
    				{
    					data.enforceInterface(IContentProvider.descriptor);
    
    					Uri url = Uri.CREATOR.createFromParcel(data);
    
    					// String[] projection
    					int num = data.readInt();
    					String[] projection = null;
    					if (num > 0) {
    						projection = new String[num];
    						for (int i = 0; i < num; i++) {
    							projection[i] = data.readString();
    						}
    					}
    
    					// String selection, String[] selectionArgs...
    					String selection = data.readString();
    					num = data.readInt();
    					String[] selectionArgs = null;
    					if (num > 0) {
    						selectionArgs = new String[num];
    						for (int i = 0; i < num; i++) {
    							selectionArgs[i] = data.readString();
    						}
    					}
    
    					String sortOrder = data.readString();
    					IContentObserver observer = IContentObserver.Stub.
    						asInterface(data.readStrongBinder());
    					CursorWindow window = CursorWindow.CREATOR.createFromParcel(data);
    
    					// Flag for whether caller wants the number of
    					// rows in the cursor and the position of the
    					// "_id" column index (or -1 if non-existent)
    					// Only to be returned if binder != null.
    					boolean wantsCursorMetadata = data.readInt() != 0;
    
    					IBulkCursor bulkCursor = bulkQuery(url, projection, selection,
    						selectionArgs, sortOrder, observer, window);
    					reply.writeNoException();
    					if (bulkCursor != null) {
    						reply.writeStrongBinder(bulkCursor.asBinder());
    
    						if (wantsCursorMetadata) {
    							reply.writeInt(bulkCursor.count());
    							reply.writeInt(BulkCursorToCursorAdaptor.findRowIdColumnIndex(
    								bulkCursor.getColumnNames()));
    						}
    					} else {
    						reply.writeStrongBinder(null);
    					}
    
    					return true;
    				}
    			......
    			}
    		} catch (Exception e) {
    			DatabaseUtils.writeExceptionToParcel(reply, e);
    			return true;
    		}
    
    		return super.onTransact(code, data, reply, flags);
    	}
    
    	......
    }
          這一步其實就是前面Step 10的逆操作,把請求參數從數據流data中讀取出來。這里我們同樣是重點關注下面這兩個參數讀取的步驟:

     

    CursorWindow window = CursorWindow.CREATOR.createFromParcel(data);
    
    // Flag for whether caller wants the number of
    // rows in the cursor and the position of the
    // "_id" column index (or -1 if non-existent)
    // Only to be returned if binder != null.
    boolean wantsCursorMetadata = data.readInt() != 0;
          通過調用CursorWindow.CREATOR.createFromParcel函數來從數據流data中重建一個本地的CursorWindow對象;接著又將數據流data的下一個整數值讀取出來,如果這個整數值不為0,變量wantsCursorMetadata的值就為true,說明Content Provider在返回IBulkCursor接口給第三方應用程序之前,要先實際執行一把數據庫查詢操作,以便把結果數據的元信息返回給第三方應用程序。

     

          通過下面的代碼我們可以看到,調用bulkQuery函數之后,就得到了一個IBulkCursor接口,這表示要返回的數據準備就緒了,但是這時候實際上還沒有把結果數據從數據庫中提取出來,而只是準備好了一個SQL查詢計劃,等到真正要使用這些結果數據時,系統才會真正執行查詢數據庫的操作:

    if (wantsCursorMetadata) {
        reply.writeInt(bulkCursor.count());
        ......
    }
          在將這個IBulkCursor接口返回給第三方應用程序之前,如果發現wantsCursorMetadata的值就為true,就會調用它的count函數來獲得結果數據的總行數,這樣就會導致系統真正去執行數據庫查詢操作,并把結果數據保存到前面得到的CursorWindow對象中的匿名共享內存中去。

     

          下面我們就重點關注CursorWindow.CREATOR.createFromParcel函數是如何從數據流data中在本地構造一個CursorWindow對象的。

          Step 15. CursorWindow.CREATOR.createFromParcel

          這個函數定義在frameworks/base/core/java/android/database/CursorWindow.java文件中:

    public class CursorWindow extends SQLiteClosable implements Parcelable {
    	......
    
    	private CursorWindow(Parcel source) {
    		IBinder nativeBinder = source.readStrongBinder();
    		......
    
    		native_init(nativeBinder);
    	}
    
    	......
    
    	public static final Parcelable.Creator<CursorWindow> CREATOR
    			= new Parcelable.Creator<CursorWindow>() {
    		public CursorWindow createFromParcel(Parcel source) {
    			return new CursorWindow(source);
    		}
    
    		......
    	};
    
    	......
    }
          在創建CursorWindow對象的過程中,首先是從數據流source中將在前面Step 10中寫入的Binder接口讀取出來,然后使用這個Binder接口來初始化這個CursorWindow對象,通過前面的Step 13,我們知道,這個Binder接口的實際類型為IMemory,它封裝了對匿名共享內存的訪問操作。初始化這個匿名共享內存對象的操作是由本地方法native_init函數來實現的,下面我們就看看它的實現。

     

          Step 16. CursorWindow.native_init

          這個函數定義在frameworks/base/core/jni/android_database_CursorWindow.cpp文件中,對應的函數為native_init_memory函數:

    static JNINativeMethod sMethods[] =
    {
        ......
        {"native_init", "(Landroid/os/IBinder;)V", (void *)native_init_memory},
    };
          函數native_init_memory的實現如下所示:

     

    static void native_init_memory(JNIEnv * env, jobject object, jobject memObj)
    {
        sp<IMemory> memory = interface_cast<IMemory>(ibinderForJavaObject(env, memObj));
        ......
    
        CursorWindow * window = new CursorWindow();
        ......
        if (!window->setMemory(memory)) {
           ......
        }
    
        ......
        SET_WINDOW(env, object, window);
    }
          函數首先是將前面Step 15中傳進來的Binder接口轉換為IMemory接口,接著創建一個C++層的CursorWindow對象,再接著用這個IMemory接口來初始化這個C++層的CursorWindow對象,最后像前面的Step 8一樣,通過宏SET_WINDOW把這個C++層的CursorWindow對象和前面在Step 15中創建的Java層CursorWindow對象關聯起來。

     

          下面我們就重點關注CursorWindow類的setMemory函數的實現,看看它是如何使用這個IMemory接口來初始化其內部的匿名共享內存對象的。

          Step 17. CursorWindow.setMemory

          這個函數定義在frameworks/base/core/jni/CursorWindow.cpp文件中:

    bool CursorWindow::setMemory(const sp<IMemory>& memory)
    {
    	mMemory = memory;
    	mData = (uint8_t *) memory->pointer();
    	......
    	mHeader = (window_header_t *) mData;
    
    	// Make the window read-only
    	ssize_t size = memory->size();
    	mSize = size;
    	mMaxSize = size;
    	mFreeOffset = size;
    	......
    	return true;
    }
          從前面一篇文章Android系統匿名共享內存(Anonymous Shared Memory)C++調用接口分析中,我們知道,這里得到的IMemory接口,實際上是一個Binder引用,它指向前面在Step 9中創建的MemoryBase對象,當我們第一次調用這個接口的pointer函數時,它便會通過Binder進程間通信機制去請求這個MemoryBase對象把它內部的匿名共享內存文件描述符返回來給它,而Binder驅動程序發現要傳輸的是一個文件描述符的時候,就會在目標進程中創建另外一個文件描述符,這個新建的文件描述符與要傳輸的文件描述符指向的是同一個文件,在我們這個情景中,這個文件就是我們前面創建的匿名共享內存文件了。因此,在目標進程中,即在Content Provider進程中,它可以通過這個新建的文件描述符來訪問這塊匿名共享內存,這也是匿名共享內存在進程間的共享原理,具體可以參考另外一篇文章Android系統匿名共享內存Ashmem(Anonymous Shared Memory)在進程間共享的原理分析

     

          這樣,在Content Provider這一側,就可以把第三方應用程序請求的數據保存在這個匿名共享內存中了,回到前面的Step 14中,下一步要執行的函數便是bulkQuery了,它的作用為請求的數據制定好一個SQL數據庫查詢計劃。這個bulkQuery函數是由一個實現了IContentProvider接口的Binder對象來實現的,具體可以參考前面一篇文章Android應用程序組件Content Provider的啟動過程源代碼分析中,這個Binder對象的實際類型是定義在ContentProivder類內部的Transport類。

          Step 18. Transport.bulkQuery

          這個函數定義在frameworks/base/core/java/android/content/ContentProvider.java文件中:

    public abstract class ContentProvider implements ComponentCallbacks {
    	......
    
    	class Transport extends ContentProviderNative {
    		......
    
    		public IBulkCursor bulkQuery(Uri uri, String[] projection,
    				String selection, String[] selectionArgs, String sortOrder,
    				IContentObserver observer, CursorWindow window) {
    			......
    
    			Cursor cursor = ContentProvider.this.query(uri, projection,
    				selection, selectionArgs, sortOrder);
    			......
    
    			return new CursorToBulkCursorAdaptor(cursor, observer,
    				ContentProvider.this.getClass().getName(),
    				hasWritePermission(uri), window);
    		}
    
    		......
    	}
    
    	......
    }

            這個函數主要做了兩件事情,一是調用ContentProvider的子類的query函數構造一個數據庫查詢計劃,注意,從這個函數返回來的時候,還沒有真正執行數據庫查詢的操作,而只是按照查詢條件準備好了一個SQL語句,要等到第一次使用的時候才會去執行數據庫查詢操作;二是使用前面一步得到的Cursor接口以及傳下來的參數window來創建一個CursorToBulkCursorAdaptor對象,這個對象實現了IBulkCursor接口,同時它也是一個Binder對象,是用來返回給第三方應用程序使用的,第三方應用程序必須通過這個接口來獲取從ContentProvider中查詢得到的數據,而這個CursorToBulkCursorAdaptor對象的功能就是利用前面獲得的Cursor接口來執行數據庫查詢操作,然后把查詢得到的結果保存在從參數傳下來的window對象內部所引用的匿名共享內存中去。我們先來看ContentProvider的子類的query函數的實現,在我們這個情景中,這個子類就是ArticlesProvider了,然后再回過頭來看看這個CursorToBulkCursorAdaptor對象是如何把數據庫查詢計劃與匿名共享內存關聯起來的。

            Step 19. ArticlesProvider.query

            這個函數定義在前面一篇文章Android應用程序組件Content Provider應用實例介紹的應用程序ArtilcesProvider源代碼工程目錄下,在文件packages/experimental/ArticlesProvider/src/shy/luo/providers/articles/ArticlesProvider.java 中:

    public class ArticlesProvider extends ContentProvider {
    	......
    
    	@Override
    	public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
    		SQLiteDatabase db = dbHelper.getReadableDatabase();
    
    		SQLiteQueryBuilder sqlBuilder = new SQLiteQueryBuilder();
    		String limit = null;
    
    		switch (uriMatcher.match(uri)) {
    			......
    			case Articles.ITEM_POS: {
    				String pos = uri.getPathSegments().get(1);
    				sqlBuilder.setTables(DB_TABLE);
    				sqlBuilder.setProjectionMap(articleProjectionMap);
    				limit = pos + ", 1";
    				break;
    			}
    			......
    		}
    
    		Cursor cursor = sqlBuilder.query(db, projection, selection, selectionArgs, null, null, TextUtils.isEmpty(sortOrder) ? Articles.DEFAULT_SORT_ORDER : sortOrder, limit);
    		......
    
    		return cursor;
    	}
    
    	......
    }
          從前面的Step 1中可以看到,傳進來的參數uri的值為“content://shy.luo.providers.articles/pos”,通過uriMatcher的match函數來匹配這個uri的時候,得到的匹配碼為Articles.ITEM_POS,這個知識點可以參考前面這篇文章Android應用程序組件Content Provider應用實例。因為我們的數據是保存在SQLite數據庫里面的,因此,必須要構造一個SQL語句來將所請求的數據查詢出來。這里是通過SQLiteQueryBuilder類來構造這個SQL查詢語句的,構造好了以后,就調用它的query函數來準備一個數據庫查詢計劃。

     

          Step 20. SQLiteQueryBuilder.query

          這個函數定義在frameworks/base/core/java/android/database/sqlite/SQLiteQueryBuilder.java文件中:

    public class SQLiteQueryBuilder
    {
    	......
    
    	public Cursor query(SQLiteDatabase db, String[] projectionIn,
    			String selection, String[] selectionArgs, String groupBy,
    			String having, String sortOrder, String limit) {
    		......
    
    		String sql = buildQuery(
    			projectionIn, selection, groupBy, having,
    			sortOrder, limit);
    
    		......
    
    		return db.rawQueryWithFactory(
    			mFactory, sql, selectionArgs,
    			SQLiteDatabase.findEditTable(mTables));
    	}
    
    	......
    }
          這里首先是調用buildQuery函數來構造一個SQL語句,它無非就是根據從參數傳來列名子句、select子句、where子句、group by子句、having子句、order子句以及limit子句來構造一個完整的SQL子句,這些都是SQL語法的基礎知識了,這里我們就不關注了。構造好這個SQL查詢語句之后,就調用從參數傳下來的數據庫對象db的rawQueryWithFactory函數來進一步操作了。

     

           Step 21. SQLiteDatabase.rawQueryWithFactory

           這個函數定義在frameworks/base/core/java/android/database/sqlite/SQLiteDatabase.java文件中:

    public class SQLiteDatabase extends SQLiteClosable {
    	......
    
    	public Cursor rawQueryWithFactory(
    			CursorFactory cursorFactory, String sql, String[] selectionArgs,
    			String editTable) {
    		......
    
    		SQLiteCursorDriver driver = new SQLiteDirectCursorDriver(this, sql, editTable);
    
    		Cursor cursor = null;
    		try {
    			cursor = driver.query(
    				cursorFactory != null ? cursorFactory : mFactory,
    				selectionArgs);
    		} finally {
    			......
    		}
    
    		return cursor;
    	}
    
    	......
    }
          這個函數會在內部創建一個SQLiteCursorDriver對象driver,然后調用它的query函數來創建一個Cursor對象,這個Cursor對象的實際類型是SQLiteCursor,下面我們將會看到,前面我們也已經看到,這個SQLiteCursor的內部就包含了一個數據庫查詢計劃。

     

          Step 22. SQLiteCursorDriver.query

          這個函數定義在frameworks/base/core/java/android/database/sqlite/SQLiteDirectCursorDriver.java文件中:

    public class SQLiteDirectCursorDriver implements SQLiteCursorDriver {
    	......
    
    	public Cursor query(CursorFactory factory, String[] selectionArgs) {
    		// Compile the query
    		SQLiteQuery query = new SQLiteQuery(mDatabase, mSql, 0, selectionArgs);
    
    		try {
    			......
    
    			// Create the cursor
    			if (factory == null) {
    				mCursor = new SQLiteCursor(mDatabase, this, mEditTable, query);
    			} else {
    				mCursor = factory.newCursor(mDatabase, this, mEditTable, query);
    			}
    
    			......
    			return mCursor;
    		} finally {
    			......
    		}
    	}
    
    	......
    }
          這里我們就可以清楚地看到,這個函數首先會根據數據庫對象mDatabase和原生SQL語句來構造一個SQLiteQuery對象,這個對象的創建的過程中,就會解析這個原生SQL語句,并且創建好數據庫查詢計劃,這樣做的好處是等到真正查詢的時候就可以馬上從數據庫中獲得取數據了,而不用去分析和理解這個SQL字符串語句,這個就是所謂的SQL語句編譯了。有了這個SQLiteQuery對象之后,再把它和數據庫對象mDatabase等待信息一起來創建一個SQLiteCursor對象,于是,這個SQLiteCursor對象就圈定要將來要從數據庫中獲取的數據了。這一步執行完成之后,就把這個SQLiteCursor對象返回給上層,最終回到Step 18中的Transport類bulkQuery函數中。有了這個SQLiteCursor對象之后,就通過創建一個CursorToBulkCursorAdaptor對象來把它和匿名共享內存關聯起來,這樣,就為將來從數據庫中查詢得到的數據找到了歸宿。

     

          CursorToBulkCursorAdaptor類定義在frameworks/base/core/java/android/database/CursorToBulkCursorAdaptor.java文件中,我們來看看它的對象的構造過程,即它的構造函數的實現:

    public final class CursorToBulkCursorAdaptor extends BulkCursorNative
    		implements IBinder.DeathRecipient {
    	......
    
    	public CursorToBulkCursorAdaptor(Cursor cursor, IContentObserver observer, String providerName,
    			boolean allowWrite, CursorWindow window) {
    		try {
    			mCursor = (CrossProcessCursor) cursor;
    			if (mCursor instanceof AbstractWindowedCursor) {
    				AbstractWindowedCursor windowedCursor = (AbstractWindowedCursor) cursor;
    				......
    
    				windowedCursor.setWindow(window);
    			} else {
    				......
    			}
    		} catch (ClassCastException e) {
    			......
    		}
    		
    		......
    	}
    
    	......
    }
          這里傳進來的參數cursor的類型為SQLiteCursor,從上面的類圖我們可以知道,SQLiteCursor實現了CrossProcessCursor接口,并且繼承了AbstractWindowedCursor類,因此,上面第一個if語句的條件成立,于是就會把這個SQLiteCurosr對象轉換為一個AbstractWindowedCursor對象,目的是為了調用它的setWindow函數來把傳進來的CursorWindow對象window保存起來,以便后面用來保存數據。

     

          Step 23. AbstractWindowedCursor.setWindow

          這個函數定義在frameworks/base/core/java/android/database/AbstractWindowedCursor.java文件中:

    public abstract class AbstractWindowedCursor extends AbstractCursor
    {
    	......
    
    	public void setWindow(CursorWindow window) {
    		......
    
    		mWindow = window;
    	}
    
    	......
    
    	protected CursorWindow mWindow;
    }
          這個函數很簡單,只是把參數window保存在AbstractWindowedCursor類的成員變量mWindow中。注意,這個成員變量mWindow的訪問權限為protected,即AbstractWindowedCursor的子類可以直接訪問這個成員變量。

     

          這一步完成以后,就返回到前面的Step 14中去了,執行下面語句:

    if (bulkCursor != null) {
    	reply.writeStrongBinder(bulkCursor.asBinder());
    
    	if (wantsCursorMetadata) {
    		reply.writeInt(bulkCursor.count());
    		reply.writeInt(BulkCursorToCursorAdaptor.findRowIdColumnIndex(
    			bulkCursor.getColumnNames()));
    	}
    } else {
    	......
    }
          這里的bulkCursor不為null,于是,就會把這個bulkCursor對象寫入到數據流reply中,這個接口是要通過Binder進程間通信機制返回到第三方應用程序的,它的實際類型就是我們在前面Step 18中創建的CursorToBulkCursorAdaptor對象了。

     

          從前面的Step 14的分析中,我們知道,這里的布爾變量wantsCursorMetadata為true,于是就會把請求數據的行數以及數據行的ID列索引號一起寫入到數據流reply中去了。這里,我們重點分析IBulkCursor接口的count函數,因為這個調用使得這個Content Provider會真正去執行數據庫查詢的操作。至于是如何得到從數據庫查詢出來的數據行的ID列的位置呢?回憶前面這篇文章Android應用程序組件Content Provider應用實例,我們提到,如果我們想將數據庫表中的某一列作為數據行的ID列的話,那么就必須把這個列的名稱設置為"_id",這里的BulkCursorToCursorAdaptor類的靜態成員函數findRowIdColumnIndex函數就是根據這個列名"_id"來找到它是位于數據行的第幾列的。

          CursorToBulkCursorAdaptor類定義在frameworks/base/core/java/android/database/CursorToBulkCursorAdaptor.java文件中,它的count成員函數的實現如下所示:

    public final class CursorToBulkCursorAdaptor extends BulkCursorNative
    		implements IBinder.DeathRecipient {
    	......
    
    	public int count() {
    		return mCursor.getCount();
    	}
    
    	......
    }
          它的成員變量mCursor即為在前面Step 22中創建的SQLiteCursor對象,于是,下面就會執行SQLiteCursor類的getCount成員函數。

     

          Step 24. SQLiteCursor.getCount
          這個函數定義在frameworks/base/core/java/android/database/sqlite/SQLiteCursor.java文件中:

    public class SQLiteCursor extends AbstractWindowedCursor {
    	......
    
    	@Override
    	public int getCount() {
    		if (mCount == NO_COUNT) {
    			fillWindow(0);
    		}
    		return mCount;
    	}
    
    	......
    }
          它里面的成員變量mCount的初始化為NO_COUNT,表示還沒有去執行數據庫查詢操作,因此,還不知道它的值是多少,需要通過調用fillWindow函數來從數據據庫中查詢中,第三方應用程序所請求的數據一共有多少行。

     

          Step 25. QLiteCursor.fillWindow
          這個函數定義在frameworks/base/core/java/android/database/sqlite/SQLiteCursor.java文件中:


    public class SQLiteCursor extends AbstractWindowedCursor {
    	......
    
    	private void fillWindow (int startPos) {
    		......
    
    		mCount = mQuery.fillWindow(mWindow, mInitialRead, 0);
    		
    		......
    	}
    
    	......
    }
          注意,這里的成員變量mWindow實際上是SQLiteCursor的父類AbstractWindowedCursor的成員變量,是在Step 23中設置的,它的訪問權限為protected,因此,SQLiteCursor類可以直接訪問它。真正的數據庫查詢操作是由SQLiteCursor類的成員變量mQuery來執行的,它的類型是SQLiteCursor,是前面的Step 22中創建的,它知道如何去把第三方應用程序請求的數據從數據庫中提取出來。

     

          Step 26. SQLiteCursor.fillWindow

          這個函數定義在frameworks/base/core/java/android/database/sqlite/SQLiteQuery.java文件中:

    public class SQLiteQuery extends SQLiteProgram {
    	......
    
    	/* package */ int fillWindow(CursorWindow window,
    			int maxRead, int lastPos) {
    		......
    		try {
    			......
    			try {
    				......
    				// if the start pos is not equal to 0, then most likely window is
    				// too small for the data set, loading by another thread
    				// is not safe in this situation. the native code will ignore maxRead
    				int numRows = native_fill_window(window, window.getStartPosition(), mOffsetIndex,
    					maxRead, lastPos);
    
    				......
    				return numRows;
    			} catch (IllegalStateException e){
    				......
    			} catch (SQLiteDatabaseCorruptException e) {
    				......
    			} finally {
    				......
    			}
    		} finally {
    			......
    		}
    	}
    
    	......
    } 
          這里我們可以看到,真正的數據庫查詢操作是由本地方法native_fill_window來執行的,它最終也是調用了sqlite的庫函數來執行數據庫查詢的操作,這里我們就不跟進去了,對sqlite有興趣的讀者可以自己研究一下。這個函數執行完成之后,就會把從數據庫中查詢得到的數據的行數返回來,這個行數最終返回到Step 25中的SQLiteCursor.fillWindow函數,設置在SQLiteCursor類的成員變量mCount中,于是,下次再調用它的getCount函數時,就可以馬上返回了。

     

          這一步執行完成之后,就回到前面的Step 14中,最終就把從Content Provider中查詢得到的數據通過匿名共享內存返回給第三方應用程序了。

          至此,Android應用程序組件Content Provider在應用程序之間共享數據的原理就分析完成了,總的來說,它就是通過Binder進程間通信機制和匿名共享內存來實現的了。

          關于應用程序間的數據共享還有另外的一個重要話題,就是數據更新通知機制了。因為數據是在多個應用程序中共享的,當其中一個應用程序改變了這些共享數據的時候,它有責任通知其它應用程序,讓它們知道共享數據被修改了,這樣它們就可以作相應的處理。在下一篇文章中,我們將分析Android應用程序組件Content Provider的數據更新通知機制,敬請關注。

    作者:Luoshengyang 發表于2011-12-5 1:00:28 原文鏈接
    閱讀:3092 評論:13 查看評論
    posted on 2012-04-17 21:32 mixer-a 閱讀(1456) 評論(0)  編輯  收藏

    只有注冊用戶登錄后才能發表評論。


    網站導航:
     
    主站蜘蛛池模板: 性色av极品无码专区亚洲| 猫咪社区免费资源在线观看| 国产高清在线免费视频| 亚洲乱妇老熟女爽到高潮的片| 99在线在线视频免费视频观看| 亚洲国产成人久久综合一 | 亚洲人成网站观看在线播放| 亚洲精品乱码久久久久久V| 国产免费AV片无码永久免费| 国产99久久亚洲综合精品| 亚洲AV伊人久久青青草原 | 香蕉国产在线观看免费| 国产偷窥女洗浴在线观看亚洲| 色www永久免费| 亚洲黄色三级视频| 毛片免费在线播放| 深夜A级毛片视频免费| 国产亚洲美女精品久久久久狼| 免费看又黄又无码的网站| 亚洲av成人综合网| 免费观看午夜在线欧差毛片| 亚洲一区二区三区免费| 久久国产亚洲高清观看| 成人黄18免费视频| 免费人成动漫在线播放r18| 亚洲大成色www永久网站| 国产成在线观看免费视频| 香蕉视频亚洲一级| 亚洲成av人在线视| 成年午夜视频免费观看视频| 一区在线免费观看| 精品亚洲aⅴ在线观看| 午夜无遮挡羞羞漫画免费| eeuss草民免费| 亚洲国产视频一区| 亚洲一区二区三区免费| 四虎在线免费视频| 一级A毛片免费观看久久精品| 亚洲视频一区二区在线观看| 国产v片免费播放| 四虎在线成人免费网站|