国产成人精品久久免费动漫-国产成人精品天堂-国产成人精品区在线观看-国产成人精品日本-a级毛片无码免费真人-a级毛片毛片免费观看久潮喷

您的位置:首頁技術(shù)文章
文章詳情頁

詳解Android文件描述符

瀏覽:3日期:2022-09-19 15:21:05

介紹文件描述符的概念以及工作原理,并通過源碼了解 Android 中常見的 FD 泄漏。

一、什么是文件描述符?

文件描述符是在 Linux 文件系統(tǒng)的被使用,由于Android基 于Linux 系統(tǒng),所以Android也繼承了文件描述符系統(tǒng)。我們都知道,在 Linux 中一切皆文件,所以系統(tǒng)在運(yùn)行時有大量的文件操作,內(nèi)核為了高效管理已被打開的文件會創(chuàng)建索引,用來指向被打開的文件,這個索引即是文件描述符,其表現(xiàn)形式為一個非負(fù)整數(shù)。

可以通過命令 ls -la /proc/$pid/fd 查看當(dāng)前進(jìn)程文件描述符使用信息。

詳解Android文件描述符

上圖中 箭頭前的數(shù)組部分是文件描述符,箭頭指向的部分是對應(yīng)的文件信息。

詳解Android文件描述符

Android系統(tǒng)中可以打開的文件描述符是有上限的,所以分到每一個進(jìn)程可打開的文件描述符也是有限的。可以通過命令 cat /proc/sys/fs/file-max 查看所有進(jìn)程允許打開的最大文件描述符數(shù)量。

詳解Android文件描述符

當(dāng)然也可以查看進(jìn)程的允許打開的最大文件描述符數(shù)量。Linux默認(rèn)進(jìn)程最大文件描述符數(shù)量是1024,但是較新款的Android設(shè)置這個值被改為32768。

詳解Android文件描述符

可以通過命令 ulimit -n 查看,Linux 默認(rèn)是1024,比較新款的Android設(shè)備大部分已經(jīng)是大于1024的,例如我用的測試機(jī)是:32768。

通過概念性的描述,我們知道系統(tǒng)在打開文件的時候會創(chuàng)建文件操作符,后續(xù)就通過文件操作符來操作文件。那么,文件描述符在代碼上是怎么實現(xiàn)的呢,讓我們來看一下Linux中用來描述進(jìn)程信息的 task_struct 源碼。

struct task_struct{// 進(jìn)程狀態(tài)long state;// 虛擬內(nèi)存結(jié)構(gòu)體struct mm_struct *mm;// 進(jìn)程號pid_t pid;// 指向父進(jìn)程的指針struct task_struct*parent;// 子進(jìn)程列表struct list_head children;// 存放文件系統(tǒng)信息的指針struct fs_struct* fs;// 存放該進(jìn)程打開的文件指針數(shù)組struct files_struct *files;};

task_struct 是 Linux 內(nèi)核中描述進(jìn)程信息的對象,其中files指向一個文件指針數(shù)組 ,這個數(shù)組中保存了這個進(jìn)程打開的所有文件指針。 每一個進(jìn)程會用 files_struct 結(jié)構(gòu)體來記錄文件描述符的使用情況,這個 files_struct 結(jié)構(gòu)體為用戶打開表,它是進(jìn)程的私有數(shù)據(jù),其定義如下:

/* * Open file table structure */struct files_struct { /* * read mostly part */ atomic_t count;//自動增量 bool resize_in_progress; wait_queue_head_t resize_wait; struct fdtable __rcu *fdt; //fdtable類型指針 struct fdtable fdtab; //fdtable變量實例 /* * written part on a separate cache line in SMP */ spinlock_t file_lock ____cacheline_aligned_in_smp; unsigned int next_fd; unsigned long close_on_exec_init[1];//執(zhí)行exec時需要關(guān)閉的文件描述符初值結(jié)合(從主進(jìn)程中fork出子進(jìn)程) unsigned long open_fds_init[1];//todo 含義補(bǔ)充 unsigned long full_fds_bits_init[1];//todo 含義補(bǔ)充 struct file __rcu * fd_array[NR_OPEN_DEFAULT];//默認(rèn)的文件描述符長度};

一般情況,“文件描述符”指的就是文件指針數(shù)組 files 的索引。

Linux 在2.6.14版本開始通過引入struct fdtable作為file_struct的間接成員,file_struct中會包含一個struct fdtable的變量實例和一個struct fdtable的類型指針。

struct fdtable { unsigned int max_fds; struct file __rcu **fd; //指向文件對象指針數(shù)組的指針 unsigned long *close_on_exec; unsigned long *open_fds; //指向打開文件描述符的指針 unsigned long *full_fds_bits; struct rcu_head rcu;};

在file_struct初始化創(chuàng)建時,fdt指針指向的其實就是當(dāng)前的的變量fdtab。當(dāng)打開文件數(shù)超過初始設(shè)置的大小時,file_struct發(fā)生擴(kuò)容,擴(kuò)容后fdt指針會指向新分配的fdtable變量。

struct files_struct init_files = { .count = ATOMIC_INIT(1), .fdt= &init_files.fdtab,//指向當(dāng)前fdtable .fdtab = {.max_fds = NR_OPEN_DEFAULT,.fd = &init_files.fd_array[0],//指向files_struct中的fd_array.close_on_exec = init_files.close_on_exec_init,//指向files_struct中的close_on_exec_init.open_fds = init_files.open_fds_init,//指向files_struct中的open_fds_init.full_fds_bits = init_files.full_fds_bits_init,//指向files_struct中的full_fds_bits_init }, .file_lock = __SPIN_LOCK_UNLOCKED(init_files.file_lock), .resize_wait = __WAIT_QUEUE_HEAD_INITIALIZER(init_files.resize_wait),};

RCU(Read-Copy Update)是數(shù)據(jù)同步的一種方式,在當(dāng)前的Linux內(nèi)核中發(fā)揮著重要的作用。

RCU主要針對的數(shù)據(jù)對象是鏈表,目的是提高遍歷讀取數(shù)據(jù)的效率,為了達(dá)到目的使用RCU機(jī)制讀取數(shù)據(jù)的時候不對鏈表進(jìn)行耗時的加鎖操作。這樣在同一時間可以有多個線程同時讀取該鏈表,并且允許一個線程對鏈表進(jìn)行修改(修改的時候,需要加鎖)。

RCU適用于需要頻繁的讀取數(shù)據(jù),而相應(yīng)修改數(shù)據(jù)并不多的情景,例如在文件系統(tǒng)中,經(jīng)常需要查找定位目錄,而對目錄的修改相對來說并不多,這就是RCU發(fā)揮作用的最佳場景。

struct file 處于內(nèi)核空間,是內(nèi)核在打開文件時創(chuàng)建,其中保存了文件偏移量,文件的inode等與文件相關(guān)的信息,在 Linux 內(nèi)核中,file結(jié)構(gòu)表示打開的文件描述符,而inode結(jié)構(gòu)表示具體的文件。在文件的所有實例都關(guān)閉后,內(nèi)核釋放這個數(shù)據(jù)結(jié)構(gòu)。

struct file { union {struct llist_node fu_llist; //用于通用文件對象鏈表的指針struct rcu_head fu_rcuhead;//RCU(Read-Copy Update)是Linux 2.6內(nèi)核中新的鎖機(jī)制 } f_u; struct path f_path;//path結(jié)構(gòu)體,包含vfsmount:指出該文件的已安裝的文件系統(tǒng),dentry:與文件相關(guān)的目錄項對象 struct inode*f_inode; /* cached value */ const struct file_operations *f_op;//文件操作,當(dāng)進(jìn)程打開文件的時候,這個文件的關(guān)聯(lián)inode中的i_fop文件操作會初始化這個f_op字段 /* * Protects f_ep_links, f_flags. * Must not be taken from IRQ context. */ spinlock_t f_lock; enum rw_hintf_write_hint; atomic_long_t f_count; //引用計數(shù) unsigned intf_flags; //打開文件時候指定的標(biāo)識,對應(yīng)系統(tǒng)調(diào)用open的int flags參數(shù)。驅(qū)動程序為了支持非阻塞型操作需要檢查這個標(biāo)志 fmode_t f_mode;//對文件的讀寫模式,對應(yīng)系統(tǒng)調(diào)用open的mod_t mode參數(shù)。如果驅(qū)動程序需要這個值,可以直接讀取這個字段 struct mutexf_pos_lock; loff_t f_pos; //目前文件的相對開頭的偏移 struct fown_struct f_owner; const struct cred *f_cred; struct file_ra_state f_ra; u64 f_version;#ifdef CONFIG_SECURITY void *f_security;#endif /* needed for tty driver, and maybe others */ void *private_data; #ifdef CONFIG_EPOLL /* Used by fs/eventpoll.c to link all the hooks to this file */ struct list_head f_ep_links; struct list_head f_tfile_llink;#endif /* #ifdef CONFIG_EPOLL */ struct address_space *f_mapping; errseq_tf_wb_err; errseq_tf_sb_err; /* for syncfs */}

整體的數(shù)據(jù)結(jié)構(gòu)示意圖如下:

詳解Android文件描述符

到這里,文件描述符的基本概念已介紹完畢。

二、文件描述符的工作原理

上文介紹了文件描述符的概念和部分源碼,如果要進(jìn)一步理解文件描述符的工作原理,需要查看由內(nèi)核維護(hù)的三個數(shù)據(jù)結(jié)構(gòu)。

詳解Android文件描述符

i-node是 Linux 文件系統(tǒng)中重要的概念,系統(tǒng)通過i-node節(jié)點讀取磁盤數(shù)據(jù)。表面上,用戶通過文件名打開文件。實際上,系統(tǒng)內(nèi)部先通過文件名找到對應(yīng)的inode號碼,其次通過inode號碼獲取inode信息,最后根據(jù)inode信息,找到文件數(shù)據(jù)所在的block,讀出數(shù)據(jù)。

三個表的關(guān)系如下:

詳解Android文件描述符

進(jìn)程的文件描述符表為進(jìn)程私有,該表的值是從0開始,在進(jìn)程創(chuàng)建時會把前三位填入默認(rèn)值,分別指向 標(biāo)準(zhǔn)輸入流,標(biāo)準(zhǔn)輸出流,標(biāo)準(zhǔn)錯誤流,系統(tǒng)總是使用最小的可用值。

正常情況一個進(jìn)程會從fd[0]讀取數(shù)據(jù),將輸出寫入fd[1],將錯誤寫入fd[2]

每一個文件描述符都會對應(yīng)一個打開文件,同時不同的文件描述符也可以對應(yīng)同一個打開文件。這里的不同文件描述符既可以是同一個進(jìn)程下,也可以是不同進(jìn)程。

每一個打開文件也會對應(yīng)一個i-node條目,同時不同的文件也可以對應(yīng)同一個i-node條目。

光看對應(yīng)關(guān)系的結(jié)論有點亂,需要梳理每種對應(yīng)關(guān)系的場景,幫助我們加深理解。

詳解Android文件描述符

問題:如果有兩個不同的文件描述符且最終對應(yīng)一個i-node,這種情況下對應(yīng)一個打開文件和對應(yīng)多個打開文件有什么區(qū)別呢?

答:如果對一個打開文件,則會共享同一個文件偏移量。

舉個例子:

fd1和fd2對應(yīng)同一個打開文件句柄,fd3指向另外一個文件句柄,他們最終都指向一個i-node。

如果fd1先寫入“hello”,fd2再寫入“world”,那么文件寫入為“helloworld”。

fd2會在fd1偏移之后添加寫,fd3對應(yīng)的偏移量為0,所以直接從開始覆蓋寫。

三、Android中FD泄漏場景

上文介紹了 Linux 系統(tǒng)中文件描述符的含義以及工作原理,下面我們介紹在Android系統(tǒng)中常見的文件描述符泄漏類型。

3.1 HandlerThread泄漏

HandlerThread是Android提供的帶消息隊列的異步任務(wù)處理類,他實際是一個帶有Looper的Thread。正常的使用方法如下:

//初始化private void init(){ //init if(null != mHandlerThread){ mHandlerThread = new HandlerThread('fd-test'); mHandlerThread.start(); mHandler = new Handler(mHandlerThread.getLooper()); }} //釋放handlerThreadprivate void release(){ if(null != mHandler){ mHandler.removeCallbacksAndMessages(null); mHandler = null; } if(null != mHandlerThread){ mHandlerThread.quitSafely(); mHandlerThread = null; }}

HandlerThread在不需要使用的時候,需要調(diào)用上述代碼中的release方法來釋放資源,比如在Activity退出時。另外全局的HandlerThread可能存在被多次賦值的情況,需要做空判斷或者先釋放再賦值,也需要重點關(guān)注。

HandlerThread會泄漏文件描述符的原因是使用了Looper,所以如果普通Thread中使用了Looper,也會有這個問題。下面讓我們來分析一下Looper的代碼,查看到底是在哪里調(diào)用的文件操作。

HandlerThread在run方法中調(diào)用Looper.prepare();

public void run() { mTid = Process.myTid(); Looper.prepare(); synchronized (this) {mLooper = Looper.myLooper();notifyAll(); } Process.setThreadPriority(mPriority); onLooperPrepared(); Looper.loop(); mTid = -1;}

Looper在構(gòu)造方法中創(chuàng)建MessageQueue對象。

private Looper(boolean quitAllowed) { mQueue = new MessageQueue(quitAllowed); mThread = Thread.currentThread();}

MessageQueue,也就是我們在Handler學(xué)習(xí)中經(jīng)常提到的消息隊列,在構(gòu)造方法中調(diào)用了native層的初始化方法。

MessageQueue(boolean quitAllowed) { mQuitAllowed = quitAllowed; mPtr = nativeInit();//native層代碼}

MessageQueue對應(yīng)native代碼,這段代碼主要是初始化了一個NativeMessageQueue,然后返回一個long型到Java層。

static jlong android_os_MessageQueue_nativeInit(JNIEnv* env, jclass clazz) { NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue(); if (!nativeMessageQueue) {jniThrowRuntimeException(env, 'Unable to allocate native queue');return 0; } nativeMessageQueue->incStrong(env); return reinterpret_cast<jlong>(nativeMessageQueue);}

NativeMessageQueue初始化方法中會先判斷是否存在當(dāng)前線程的Native層的Looper,如果沒有的就創(chuàng)建一個新的Looper并保存。

NativeMessageQueue::NativeMessageQueue() :mPollEnv(NULL), mPollObj(NULL), mExceptionObj(NULL) { mLooper = Looper::getForThread(); if (mLooper == NULL) {mLooper = new Looper(false);Looper::setForThread(mLooper); }}

在Looper的構(gòu)造函數(shù)中,我們發(fā)現(xiàn)“eventfd”,這個很有文件描述符特征的方法。

Looper::Looper(bool allowNonCallbacks): mAllowNonCallbacks(allowNonCallbacks), mSendingMessage(false), mPolling(false), mEpollRebuildRequired(false), mNextRequestSeq(0), mResponseIndex(0), mNextMessageUptime(LLONG_MAX) { mWakeEventFd.reset(eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC));//eventfd LOG_ALWAYS_FATAL_IF(mWakeEventFd.get() < 0, 'Could not make wake event fd: %s', strerror(errno)); AutoMutex _l(mLock); rebuildEpollLocked();}

從C++代碼注釋中可以知道eventfd函數(shù)會返回一個新的文件描述符。

/** * [eventfd(2)](http://man7.org/linux/man-pages/man2/eventfd.2.html) creates a file descriptor * for event notification. * * Returns a new file descriptor on success, and returns -1 and sets `errno` on failure. */int eventfd(unsigned int __initial_value, int __flags);3.2 IO泄漏

IO操作是Android開發(fā)過程中常用的操作,如果沒有正確關(guān)閉流操作,除了可能會導(dǎo)致內(nèi)存泄漏,也會導(dǎo)致FD的泄漏。常見的問題代碼如下:

private void ioTest(){ try {File file = new File(getCacheDir(), 'testFdFile');file.createNewFile();FileOutputStream out = new FileOutputStream(file);//do somethingout.close(); }catch (Exception e){e.printStackTrace(); }}

如果在流操作過程中發(fā)生異常,就有可能導(dǎo)致泄漏。正確的寫法應(yīng)該是在final塊中關(guān)閉流。

private void ioTest() { FileOutputStream out = null; try {File file = new File(getCacheDir(), 'testFdFile');file.createNewFile();out = new FileOutputStream(file);//do somethingout.close(); } catch (Exception e) {e.printStackTrace(); } finally {if (null != out) { try {out.close(); } catch (IOException e) {e.printStackTrace(); }} }}

同樣,我們在從源碼中尋找流操作是如何創(chuàng)建文件描述符的。首先,查看 FileOutputStream 的構(gòu)造方法 ,可以發(fā)現(xiàn)會初始化一個名為fd的 FileDescriptor 變量,這個 FileDescriptor 對象是Java層對native文件描述符的封裝,其中只包含一個int類型的成員變量,這個變量的值就是native層創(chuàng)建的文件描述符的值。

public FileOutputStream(File file, boolean append) throws FileNotFoundException{ //...... this.fd = new FileDescriptor(); //...... open(name, append); //......}

open方法會直接調(diào)用jni方法open0.

/** * Opens a file, with the specified name, for overwriting or appending. * @param name name of file to be opened * @param append whether the file is to be opened in append mode */private native void open0(String name, boolean append) throws FileNotFoundException; private void open(String name, boolean append) throws FileNotFoundException { open0(name, append);}

Tips: 我們在看android源碼時常常遇到native方法,通過Android Studio無法跳轉(zhuǎn)查看,可以在 androidxref 網(wǎng)站,通過“Java類名_native方法名”的方法進(jìn)行搜索。例如,這可以搜索 FileOutputStream_open0 。

接下來,讓我們進(jìn)入native方法查看對應(yīng)實現(xiàn)。

JNIEXPORT void JNICALLFileOutputStream_open0(JNIEnv *env, jobject this, jstring path, jboolean append) { fileOpen(env, this, path, fos_fd, O_WRONLY | O_CREAT | (append ? O_APPEND : O_TRUNC));}

在fileOpen方法中,通過handleOpen生成native層的文件描述符(fd),這個fd就是這個所謂對面的文件描述符。

void fileOpen(JNIEnv *env, jobject this, jstring path, jfieldID fid, int flags){ WITH_PLATFORM_STRING(env, path, ps) {FD fd;//......fd = handleOpen(ps, flags, 0666);if (fd != -1) { SET_FD(this, fd, fid);} else { throwFileNotFoundException(env, path);} } END_PLATFORM_STRING(env, ps);} FD handleOpen(const char *path, int oflag, int mode) { FD fd; RESTARTABLE(open64(path, oflag, mode), fd);//調(diào)用open,獲取fd if (fd != -1) {//......if (result != -1) { //......} else { close(fd); fd = -1;} } return fd;}

到這里就結(jié)束了嗎?

回到開始,F(xiàn)ileOutputStream構(gòu)造方法中初始化了Java層的文件描述符類 FileDescriptor,目前這個對象中的文件描述符的值還是初始的-1,所以目前它還是一個無效的文件描述符,native層完成fd創(chuàng)建后,還需要把fd的值傳到 Java層。

我們再來看SET_FD這個宏的定義,在這個宏定義中,通過反射的方式給Java層對象的成員變量賦值。由于上文內(nèi)容可知,open0是對象的jni方法,所以宏中的this,就是初始創(chuàng)建的FileOutputStream在Java層的對象實例。

#define SET_FD(this, fd, fid) if ((*env)->GetObjectField(env, (this), (fid)) != NULL) (*env)->SetIntField(env, (*env)->GetObjectField(env, (this), (fid)),IO_fd_fdID, (fd))

而fid則會在native代碼中提前初始化好。

static void FileOutputStream_initIDs(JNIEnv *env) { jclass clazz = (*env)->FindClass(env, 'java/io/FileOutputStream'); fos_fd = (*env)->GetFieldID(env, clazz, 'fd', 'Ljava/io/FileDescriptor;');}

收,到這里FileOutputStream的初始化跟進(jìn)就完成了,我們已經(jīng)找到了底層fd初始化的路徑。Android的IO操作還有其他的流操作類,大致流程基本類似,這里不再細(xì)述。

并不是不關(guān)閉就一定會導(dǎo)致文件描述符泄漏,在流對象的析構(gòu)方法中會調(diào)用close方法,所以這個對象被回收時,理論上也是會釋放文件描述符。但是最好還是通過代碼控制釋放邏輯。

3.3 SQLite泄漏

在日常開發(fā)中如果使用數(shù)據(jù)庫SQLite管理本地數(shù)據(jù),在數(shù)據(jù)庫查詢的cursor使用完成后,亦需要調(diào)用close方法釋放資源,否則也有可能導(dǎo)致內(nèi)存和文件描述符的泄漏。

public void get() { db = ordersDBHelper.getReadableDatabase(); Cursor cursor = db.query(...); while (cursor.moveToNext()) { //...... } if(flag){ //某種原因?qū)е聄etrn return; } //不調(diào)用close,fd就會泄漏 cursor.close();}

按照理解query操作應(yīng)該會導(dǎo)致文件描述符泄漏,那我們就從query方法的實現(xiàn)開始分析。

然而,在query方法中并沒有發(fā)現(xiàn)文件描述符相關(guān)的代碼。

經(jīng)過測試發(fā)現(xiàn),moveToNext 調(diào)用后才會導(dǎo)致文件描述符增長。通過query方法可以獲取cursor的實現(xiàn)類SQLiteCursor。

public Cursor query(CursorFactory factory, String[] selectionArgs) { final SQLiteQuery query = new SQLiteQuery(mDatabase, mSql, mCancellationSignal); final Cursor cursor; //...... if (factory == null) { cursor = new SQLiteCursor(this, mEditTable, query); } else { cursor = factory.newCursor(mDatabase, this, mEditTable, query); } //......}

在SQLiteCursor的父類找到moveToNext的實現(xiàn)。getCount 是抽象方法,在子類SQLiteCursor實現(xiàn)。

@Overridepublic final boolean moveToNext() { return moveToPosition(mPos + 1);}public final boolean moveToPosition(int position) { // Make sure position isn’t past the end of the cursor final int count = getCount(); if (position >= count) {mPos = count;return false; } //......}

getCount 方法中對成員變量mCount做判斷,如果還是初始值,則會調(diào)用fillWindow方法。

@Overridepublic int getCount() { if (mCount == NO_COUNT) {fillWindow(0); } return mCount;}private void fillWindow(int requiredPos) { clearOrCreateWindow(getDatabase().getPath()); //......}

clearOrCreateWindow 實現(xiàn)又回到父類 AbstractWindowedCursor 中。

protected void clearOrCreateWindow(String name) { if (mWindow == null) {mWindow = new CursorWindow(name); } else {mWindow.clear(); }}

在CursorWindow的構(gòu)造方法中,通過nativeCreate方法調(diào)用到native層的初始化。

public CursorWindow(String name, @BytesLong long windowSizeBytes) { //...... mWindowPtr = nativeCreate(mName, (int) windowSizeBytes); //......}

在C++代碼中會繼續(xù)調(diào)用一個native層CursorWindow的create方法。

static jlong nativeCreate(JNIEnv* env, jclass clazz, jstring nameObj, jint cursorWindowSize) { //...... CursorWindow* window; status_t status = CursorWindow::create(name, cursorWindowSize, &window); //...... return reinterpret_cast<jlong>(window);}

在CursorWindow的create方法中,我們可以發(fā)現(xiàn)fd創(chuàng)建相關(guān)的代碼。

status_t CursorWindow::create(const String8& name, size_t size, CursorWindow** outCursorWindow) { String8 ashmemName('CursorWindow: '); ashmemName.append(name); status_t result; int ashmemFd = ashmem_create_region(ashmemName.string(), size); //......}

ashmem_create_region 方法最終會調(diào)用到open函數(shù)打開文件并返回系統(tǒng)創(chuàng)建的文件描述符。這部分代碼不在贅述,有興趣的可以自行查看 。

native完成初始化會把fd信息保存在CursorWindow中并會返回一個指針地址到Java層,Java層可以通過這個指針操作c++層對象從而也能獲取對應(yīng)的文件描述符。

3.4 InputChannel 導(dǎo)致的泄漏

WindowManager.addView

通過WindowManager反復(fù)添加view也會導(dǎo)致文件描述符增長,可以通過調(diào)用removeView釋放之前創(chuàng)建的FD。

private void addView() { View windowView = LayoutInflater.from(getApplication()).inflate(R.layout.layout_window, null); //重復(fù)調(diào)用 mWindowManager.addView(windowView, wmParams);}

WindowManagerImpl中的addView最終會走到ViewRootImpl的setView。

public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) { //...... root = new ViewRootImpl(view.getContext(), display); //...... root.setView(view, wparams, panelParentView);}

setView中會創(chuàng)建InputChannel,并通過Binder機(jī)制傳到服務(wù)端。

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) { //...... //創(chuàng)建inputchannel if ((mWindowAttributes.inputFeatures& WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {mInputChannel = new InputChannel(); } //遠(yuǎn)程服務(wù)接口 res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,getHostVisibility(), mDisplay.getDisplayId(), mWinFrame,mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel);//mInputChannel 作為參數(shù)傳過去 //...... if (mInputChannel != null) {if (mInputQueueCallback != null) { mInputQueue = new InputQueue(); mInputQueueCallback.onInputQueueCreated(mInputQueue);}//創(chuàng)建 WindowInputEventReceiver 對象mInputEventReceiver = new WindowInputEventReceiver(mInputChannel, Looper.myLooper()); }}

addToDisplay是一個AIDL方法,它的實現(xiàn)類是源碼中的Session。最終調(diào)用的是 WindowManagerService 的 addWIndow 方法。

public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,int viewVisibility, int displayId, Rect outFrame, Rect outContentInsets,Rect outStableInsets,DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel,InsetsState outInsetsState, InsetsSourceControl[] outActiveControls) { return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId, outFrame, outContentInsets, outStableInsets, outDisplayCutout, outInputChannel, outInsetsState, outActiveControls, UserHandle.getUserId(mUid));}

WMS在 addWindow 方法中創(chuàng)建 InputChannel 用于通訊。

public int addWindow(Session session, IWindow client, int seq,LayoutParams attrs, int viewVisibility, int displayId, Rect outFrame,Rect outContentInsets, Rect outStableInsets, Rect outOutsets,DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel) {//......final boolean openInputChannels = (outInputChannel != null&& (attrs.inputFeatures & INPUT_FEATURE_NO_INPUT_CHANNEL) == 0);if (openInputChannels) { win.openInputChannel(outInputChannel);}//......}

在 openInputChannel 中創(chuàng)建 InputChannel ,并把客戶端的傳回去。

void openInputChannel(InputChannel outInputChannel) { //...... InputChannel[] inputChannels = InputChannel.openInputChannelPair(name); mInputChannel = inputChannels[0]; mClientChannel = inputChannels[1]; //......}

InputChannel 的 openInputChannelPair 會調(diào)用native的 nativeOpenInputChannelPair ,在native中創(chuàng)建兩個帶有文件描述符的 socket 。

int socketpair(int domain, int type, int protocol, int sv[2]) { //創(chuàng)建一對匿名的已經(jīng)連接的套接字 int rc = __socketpair(domain, type, protocol, sv); if (rc == 0) {//跟蹤文件描述符FDTRACK_CREATE(sv[0]);FDTRACK_CREATE(sv[1]); } return rc;}

WindowManager 的分析涉及WMS,WMS內(nèi)容比較多,本文重點關(guān)注文件描述符相關(guān)的內(nèi)容。簡單的理解,就是進(jìn)程間通訊會創(chuàng)建socket,所以也會創(chuàng)建文件描述符,而且會在服務(wù)端進(jìn)程和客戶端進(jìn)程各創(chuàng)建一個。另外,如果系統(tǒng)進(jìn)程文件描述符過多,理論上會造成系統(tǒng)崩潰。

四、如何排查

如果你的應(yīng)用收到如下這些崩潰堆棧,恭喜你,你的應(yīng)用存在文件描述符泄漏。

abort message ’could not create instance too many files’ could not read input file descriptors from parcel socket failed:EMFILE (Too many open files) ...

文件描述符導(dǎo)致的崩潰往往無法通過堆棧直接分析。道理很簡單: 出問題的代碼在消耗文件描述符同時,正常的代碼邏輯可能也同樣在創(chuàng)建文件描述符,所以崩潰可能是被正常代碼觸發(fā)了。

4.1 打印當(dāng)前FD信息

遇到這類問題可以先嘗試本體復(fù)現(xiàn),通過命令 ‘ls -la /proc/$pid/fd’ 查看當(dāng)前進(jìn)程文件描述符的消耗情況。一般android應(yīng)用的文件描述符可以分為幾類,通過對比哪一類文件描述符數(shù)量過高,來縮小問題范圍。

詳解Android文件描述符

4.2 dump系統(tǒng)信息

通過dumpsys window ,查看是否有異常window。用于解決 InputChannel 相關(guān)的泄漏問題。

4.3 線上監(jiān)控

如果是本地?zé)o法復(fù)現(xiàn)問題,可以嘗試添加線上監(jiān)控代碼,定時輪詢當(dāng)前進(jìn)程使用的FD數(shù)量,在達(dá)到閾值時,讀取當(dāng)前FD的信息,并傳到后臺分析,獲取FD對應(yīng)文件信息的代碼如下。

if (Build.VERSION.SDK_INT >= VersionCodes.L) { linkTarget = Os.readlink(file.getAbsolutePath());} else { //通過 readlink 讀取文件描述符信息}4.4 排查循環(huán)打印的日志

除了直接對 FD相關(guān)的信息進(jìn)行分析,還需要關(guān)注logcat中是否有頻繁打印的信息,例如:socket創(chuàng)建失敗。

以上就是詳解Android 文件描述符的詳細(xì)內(nèi)容,更多關(guān)于Android文件描述符的資料請關(guān)注好吧啦網(wǎng)其它相關(guān)文章!

標(biāo)簽: Android
相關(guān)文章:
主站蜘蛛池模板: 国产乱肥老妇精品视频 | 成人网18免费网站 | 欧美激情久久久久久久大片 | 亚洲一区视频 | 免费一区二区三区四区五区 | 国产精自产拍久久久久久蜜 | 日韩a毛片免费全部播放完整 | 久草视频在线看 | 久草免费资源视频 | 免费高清一级欧美片在线观看 | 精品一区二区三区免费观看 | 日韩美女一级视频 | 亚洲国产成a人v在线 | 亚洲天堂男 | 精品国产一区二区三区国产馆 | 米奇精品一区二区三区在线观看 | 国产成人毛片视频不卡在线 | 国产精品视频久久久久 | 日韩精品免费一级视频 | 亚洲国产精品久久精品成人 | 亚洲1314| 国产女主播91 | 久久久久亚洲香蕉网 | 国产精品成人自拍 | 欧美aav| 91久久亚洲精品国产一区二区 | 亚洲精品久久久久久久久久久网站 | 91香蕉视频免费 | 99热碰 | 国产区亚洲区 | 99久久免费精品国产免费 | 精品欧美一区二区三区免费观看 | 欧美不卡在线视频 | 新体操真| 国产伦久视频免费观看视频 | 精品玖玖玖视频在线观看 | 国产精品久久久久一区二区三区 | 亚洲精品国产手机 | 99免费在线播放99久久免费 | 成人精品视频在线 | 欧美韩国日本 |