<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ThinkChat2.0新版上線,更智能更精彩,支持會話、畫圖、視頻、閱讀、搜索等,送10W Token,即刻開啟你的AI之旅 廣告
                在回答上一節的問題之前,不知讀者思考過下面的問題:實現文件描述符跨進程傳遞的目的是什么? 以上節所示讀取音樂專輯的縮略圖為例,問題的答案就是,讓客戶端能夠讀取專輯的縮略圖文件。為什么客戶端不先獲得對應專輯縮略圖的文件存儲路徑,然后直接打開這個文件,卻要如此大費周章呢?原因有二: - 出于安全的考慮,MediaProvider不希望客戶端繞過它去直接讀取存儲設備上的文件。另外,客戶端須額外聲明相關的存儲設備讀寫權限,然后才能直接讀取其上面的文件。 - 雖然本例針對的是一個實際文件,但是從可擴展性角度看,我們希望客戶端使用一個更通用的接口,通過這個接口可讀取實際文件的數據,也可讀取來自的網絡的數據,而作為該接口的使用者無需關心數據到底從何而來。 * * * * * **提示**:實際上還有更多的原因,讀者不妨嘗試在以上兩點原因的基礎上拓展思考。 * * * * * 1. 序列化ParcelFileDescriptor 好了,繼續討論本例的情況。現在服務端已經打開了某個縮略圖文件,并且獲得了一個文件描述符對象FileDescriptor。這個文件是服務端打開的。如何讓客戶端也打開這個文件呢?各據前文分析,客戶端不會也不應該通過文件路徑自己去打開這個文件。那該如何處理? 沒關系,Binder驅動支持跨進程傳遞文件描述符。先來看ParcelFileDescriptor的序列化函數writeToParcel,代碼如下: **ParcelFileDescriptor.java::writeToParcel** ~~~ public void writeToParcel(Parcel out, int flags) { //往Parcel包中直接寫入mFileDescriptor指向的FileDescriptor對象 out.writeFileDescriptor(mFileDescriptor); if((flags&PARCELABLE_WRITE_RETURN_VALUE) != 0 && !mClosed) { try { close(); }...... } } ~~~ Parcel的writeFileDescriptor是一個native函數,代碼如下: **android_util_Binder.cpp::android_os_Parcel_writeFileDescriptor** ~~~ static voidandroid_os_Parcel_writeFileDescriptor(JNIEnv* env, jobject clazz,jobject object) { Parcel*parcel = parcelForJavaObject(env, clazz); if(parcel != NULL) { //先調用jniGetFDFromFileDescriptor從Java層FileDescriptor對象中 //取出對應的文件描述符。在Native層,文件描述符是一個int整型 //然后調用Native parcel對象的writeDupFileDescriptor函數 const status_t err = parcel->writeDupFileDescriptor( jniGetFDFromFileDescriptor(env, object)); if(err != NO_ERROR) { signalExceptionForError(env, clazz, err); } } } ~~~ Native Parcel類的writeDupFileDescriptor代碼如下: **Parcel.cpp::writeDupFileDescriptor** ~~~ status_t Parcel::writeDupFileDescriptor(int fd) { returnwriteFileDescriptor(dup(fd), true); } //直接來看writeFileDescriptor函數 status_t Parcel::writeFileDescriptor(int fd, booltakeOwnership) { flat_binder_object obj; obj.type= BINDER_TYPE_FD; obj.flags = 0x7f | FLAT_BINDER_FLAG_ACCEPTS_FDS; obj.handle = fd; //將MediaProvider打開的文件描述符傳遞給Binder協議 obj.cookie = (void*) (takeOwnership ? 1 : 0); returnwriteObject(obj, true); } ~~~ 有上邊代碼可知,ParcelFileDescriptor的序列化過程就是將其內部對應文件的文件描述符取出,并存儲到一個由Binder驅動的flat_binder_object對象中。該對象最終會發送給Binder驅動。 2. 反序列化ParcelFileDescriptor 假設客戶端進程收到了來自服務端的回復,客戶端要做的就是根據服務端的回復包構造一個新的ParcelFileDescriptor。我們重點關注文件描述符的反序列化,其中調用的函數是Parcel的readFileDescriptor,其代碼如下: **ParcelFileDescriptor.java::readFileDescriptor** ~~~ public final ParcelFileDescriptorreadFileDescriptor() { //從internalReadFileDescriptor中返回一個FileDescriptor對象 FileDescriptor fd = internalReadFileDescriptor(); //構造一個ParcelFileDescriptor對象,該對象對應的文件就是服務端打開的那個縮略圖文件 return fd!= null ? new ParcelFileDescriptor(fd) : null; ~~~ internalReadFileDescriptor是一個native函數,其實現代碼如下: **android_util_Binder.cpp::android_os_Parcel_readFileDescriptor** ~~~ static jobjectandroid_os_Parcel_readFileDescriptor(JNIEnv* env, jobject clazz) { Parcel*parcel = parcelForJavaObject(env, clazz); if(parcel != NULL) { //調用Parcel的readFileDescriptor得到一個文件描述符 intfd = parcel->readFileDescriptor(); if(fd < 0) return NULL; fd =dup(fd);//調用dup復制該文件描述符 if(fd < 0) return NULL; //調用jniCreateFileDescriptor以返回一個Java層的FileDescriptor對象 return jniCreateFileDescriptor(env, fd); } returnNULL; } ~~~ 來看Parcel的readFileDescriptor函數,代碼如下: **Parcel.cpp::readFileDescriptor** ~~~ int Parcel::readFileDescriptor() const { constflat_binder_object* flat = readObject(true); if(flat) { switch (flat->type) { case BINDER_TYPE_FD: //當服務端發送回復包的時候,handle變量指向fd。當客戶端接收回復包的時候, //又從handle中得到fd。此fd是彼fd嗎? return flat->handle; } } returnBAD_TYPE; } ~~~ 筆者在以上代碼中提到了一個較深刻的問題:此fd是彼fd嗎?這個問題的真實含義是: - 服務端打開了一個文件,得到了一個fd。注意,fd是一個整型。在服務端上,這個fd確實對應了一個已經打開的文件。 - 客戶端得到的也是一個整型值,它對應的是一個文件嗎? 如果說客戶端得到一個整型值,就認為它得到了一個文件,這種說法未免有些草率。在以上代碼中,我們發現客戶端確實根據收到的那個整型值創建了一個FileDescriptor對象。那么,怎樣才可知道這個整型值在客戶端中一定代表一個文件呢? 這個問題的終極解答在Binder驅動的代碼中。來看它的binder_transaction函數。 3. 文件描述符傳遞之Binder驅動的處理 **binder.c::binder_transaction** ~~~ static void binder_transaction(struct binder_proc*proc, structbinder_thread *thread, structbinder_transaction_data *tr, int reply) ...... switch(fp->type) { caseBINDER_TYPE_FD: { int target_fd; struct file *file; if (reply) { ...... //Binder驅動根據服務端返回的fd找到內核中文件的代表file,其數據類型是 //struct file file = fget(fp->handle); ...... //target_proc為客戶端進程,task_get_unused_fd_flags函數用于從客戶端 //進程中找一個空閑的整型值,用做客戶端進程的文件描述符 target_fd = task_get_unused_fd_flags(target_proc, O_CLOEXEC); ...... //將客戶端進程的文件描述符和代表文件的file對象綁定 task_fd_install(target_proc, target_fd, file); fp->handle = target_fd; }break; ......//其他處理 } ~~~ 一切真相大白!原來,Binder驅動代替客戶端打開了對應的文件,所以現在可以肯定,客戶端收到的整型值確確實實代表一個文件。 4. 深入討論 在研究這段代碼時,筆者曾經向所在團隊同仁問過這樣一個問題:在Linux平臺上,有什么辦法能讓兩個進程共享同一文件的數據呢?曾得到下面這些回答: - 兩個進程打開同一文件。這種方式前面討論過了,安全性和可擴展性都比較差,不是我們想要的方式。 - 通過父子進程的親緣關系,使用文件重定向技術。由于這兩個進程關系太親近,這種實現方式拓展性較差,也不是我們想要的。 - 跳出兩個進程打開同一個文件的限制。在兩個進程間創建管道,然后由服務端讀取文件數據并寫入管道,再由客戶端進程從管道中獲取數據。這種方式和前面介紹的openAssetFileDescriptor有殊途同歸之處。 在缺乏類似Binder驅動支持的情況下,要在Linux平臺上做到文件描述符的跨進程傳遞是件比較困難的事。從上面三種回答來看,最具擴展性的是第三種方式,即進程間采用管道作為通信手段。但是對Android平臺來說,這種方式的效率顯然不如現有的openAssetFileDescriptor的實現。原因在于管道本身的特性。 服務端必須單獨啟動一個線程來不斷地往管道中寫數據,即整個數據的流動是由寫端驅動的(雖然當管道無空間的時候,如果讀端不讀取數據,寫端也沒法再寫入數據,但是如果寫端不寫數據,則讀端一定讀不到數據。基于這種認識,筆者認為管道中數據流動的驅動力應該在寫端)。 Android 3.0以后為ContentProvider提供了管道支持,我們來看相關的函數。 **ContentProvider.java::openPipeHelper** ~~~ public <T> ParcelFileDescriptoropenPipeHelper(final Uri uri, finalString mimeType, final Bundle opts, final T args, final PipeDataWriter<T> func) throws FileNotFoundException { try { //創建管道 final ParcelFileDescriptor[] fds = ParcelFileDescriptor. createPipe(); //構造一個AsyncTask對象 AsyncTask<Object, Object, Object> task = new AsyncTask<Object,Object, Object>() { @Override protected Object doInBackground(Object... params) { //往管道寫端寫數據,如果沒有這個后臺線程的寫操作,客戶端無論如何 //也讀不到數據的 func.writeDataToPipe(fds[1],uri, mimeType, opts, args); try { fds[1].close(); } ...... return null; } }; //AsyncTask.THREAD_POOL_EXECUTOR是一個線程池,task的doInBackground //函數將在線程池中的一個線程中運行 task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Object[])null); return fds[0];//返回讀端給客戶端 } ...... } ~~~ 由以上代碼可知,采用管道這種方式的開銷確實比客戶端直接獲取文件描述符的開銷大。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看