日本不卡不码高清免费观看,久久国产精品久久w女人spa,黄色aa久久,三上悠亚国产精品一区二区三区

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

詳解Android文件描述符

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

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

一、什么是文件描述符?

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

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

詳解Android文件描述符

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

詳解Android文件描述符

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

詳解Android文件描述符

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

詳解Android文件描述符

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

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

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

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

/* * Open file table structure */struct files_struct { /* * read mostly part */ atomic_t count;//自動(dòng)增量 bool resize_in_progress; wait_queue_head_t resize_wait; struct fdtable __rcu *fdt; //fdtable類型指針 struct fdtable fdtab; //fdtable變量實(shí)例 /* * 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時(shí)需要關(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)的文件描述符長(zhǎng)度};

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

Linux 在2.6.14版本開(kāi)始通過(guò)引入struct fdtable作為file_struct的間接成員,file_struct中會(huì)包含一個(gè)struct fdtable的變量實(shí)例和一個(gè)struct fdtable的類型指針。

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

在file_struct初始化創(chuàng)建時(shí),fdt指針指向的其實(shí)就是當(dāng)前的的變量fdtab。當(dāng)打開(kāi)文件數(shù)超過(guò)初始設(shè)置的大小時(shí),file_struct發(fā)生擴(kuò)容,擴(kuò)容后fdt指針會(huì)指向新分配的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主要針對(duì)的數(shù)據(jù)對(duì)象是鏈表,目的是提高遍歷讀取數(shù)據(jù)的效率,為了達(dá)到目的使用RCU機(jī)制讀取數(shù)據(jù)的時(shí)候不對(duì)鏈表進(jìn)行耗時(shí)的加鎖操作。這樣在同一時(shí)間可以有多個(gè)線程同時(shí)讀取該鏈表,并且允許一個(gè)線程對(duì)鏈表進(jìn)行修改(修改的時(shí)候,需要加鎖)。

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

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

struct file { union {struct llist_node fu_llist; //用于通用文件對(duì)象鏈表的指針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)的目錄項(xiàng)對(duì)象 struct inode*f_inode; /* cached value */ const struct file_operations *f_op;//文件操作,當(dāng)進(jìn)程打開(kāi)文件的時(shí)候,這個(gè)文件的關(guān)聯(lián)inode中的i_fop文件操作會(huì)初始化這個(gè)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; //引用計(jì)數(shù) unsigned intf_flags; //打開(kāi)文件時(shí)候指定的標(biāo)識(shí),對(duì)應(yīng)系統(tǒng)調(diào)用open的int flags參數(shù)。驅(qū)動(dòng)程序?yàn)榱酥С址亲枞筒僮餍枰獧z查這個(gè)標(biāo)志 fmode_t f_mode;//對(duì)文件的讀寫(xiě)模式,對(duì)應(yīng)系統(tǒng)調(diào)用open的mod_t mode參數(shù)。如果驅(qū)動(dòng)程序需要這個(gè)值,可以直接讀取這個(gè)字段 struct mutexf_pos_lock; loff_t f_pos; //目前文件的相對(duì)開(kāi)頭的偏移 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ù)的三個(gè)數(shù)據(jù)結(jié)構(gòu)。

詳解Android文件描述符

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

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

詳解Android文件描述符

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

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

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

每一個(gè)打開(kāi)文件也會(huì)對(duì)應(yīng)一個(gè)i-node條目,同時(shí)不同的文件也可以對(duì)應(yīng)同一個(gè)i-node條目。

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

詳解Android文件描述符

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

答:如果對(duì)一個(gè)打開(kāi)文件,則會(huì)共享同一個(gè)文件偏移量。

舉個(gè)例子:

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

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

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

三、Android中FD泄漏場(chǎng)景

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

3.1 HandlerThread泄漏

HandlerThread是Android提供的帶消息隊(duì)列的異步任務(wù)處理類,他實(shí)際是一個(gè)帶有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在不需要使用的時(shí)候,需要調(diào)用上述代碼中的release方法來(lái)釋放資源,比如在Activity退出時(shí)。另外全局的HandlerThread可能存在被多次賦值的情況,需要做空判斷或者先釋放再賦值,也需要重點(diǎn)關(guān)注。

HandlerThread會(huì)泄漏文件描述符的原因是使用了Looper,所以如果普通Thread中使用了Looper,也會(huì)有這個(gè)問(wèn)題。下面讓我們來(lái)分析一下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對(duì)象。

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

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

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

MessageQueue對(duì)應(yīng)native代碼,這段代碼主要是初始化了一個(gè)NativeMessageQueue,然后返回一個(gè)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初始化方法中會(huì)先判斷是否存在當(dāng)前線程的Native層的Looper,如果沒(méi)有的就創(chuàng)建一個(gè)新的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”,這個(gè)很有文件描述符特征的方法。

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ù)會(huì)返回一個(gè)新的文件描述符。

/** * [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開(kāi)發(fā)過(guò)程中常用的操作,如果沒(méi)有正確關(guān)閉流操作,除了可能會(huì)導(dǎo)致內(nèi)存泄漏,也會(huì)導(dǎo)致FD的泄漏。常見(jiàn)的問(wèn)題代碼如下:

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(); }}

如果在流操作過(guò)程中發(fā)生異常,就有可能導(dǎo)致泄漏。正確的寫(xiě)法應(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(); }} }}

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

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

open方法會(huì)直接調(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: 我們?cè)诳碼ndroid源碼時(shí)常常遇到native方法,通過(guò)Android Studio無(wú)法跳轉(zhuǎn)查看,可以在 androidxref 網(wǎng)站,通過(guò)“Java類名_native方法名”的方法進(jìn)行搜索。例如,這可以搜索 FileOutputStream_open0 。

接下來(lái),讓我們進(jìn)入native方法查看對(duì)應(yīng)實(shí)現(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方法中,通過(guò)handleOpen生成native層的文件描述符(fd),這個(gè)fd就是這個(gè)所謂對(duì)面的文件描述符。

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é)束了嗎?

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

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

#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則會(huì)在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)閉就一定會(huì)導(dǎo)致文件描述符泄漏,在流對(duì)象的析構(gòu)方法中會(huì)調(diào)用close方法,所以這個(gè)對(duì)象被回收時(shí),理論上也是會(huì)釋放文件描述符。但是最好還是通過(guò)代碼控制釋放邏輯。

3.3 SQLite泄漏

在日常開(kāi)發(fā)中如果使用數(shù)據(jù)庫(kù)SQLite管理本地?cái)?shù)據(jù),在數(shù)據(jù)庫(kù)查詢的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就會(huì)泄漏 cursor.close();}

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

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

經(jīng)過(guò)測(cè)試發(fā)現(xiàn),moveToNext 調(diào)用后才會(huì)導(dǎo)致文件描述符增長(zhǎng)。通過(guò)query方法可以獲取cursor的實(shí)現(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的實(shí)現(xiàn)。getCount 是抽象方法,在子類SQLiteCursor實(shí)現(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 方法中對(duì)成員變量mCount做判斷,如果還是初始值,則會(huì)調(diào)用fillWindow方法。

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

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

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

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

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

在C++代碼中會(huì)繼續(xù)調(diào)用一個(gè)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 方法最終會(huì)調(diào)用到open函數(shù)打開(kāi)文件并返回系統(tǒng)創(chuàng)建的文件描述符。這部分代碼不在贅述,有興趣的可以自行查看 。

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

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

WindowManager.addView

通過(guò)WindowManager反復(fù)添加view也會(huì)導(dǎo)致文件描述符增長(zhǎng),可以通過(guò)調(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最終會(huì)走到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中會(huì)創(chuàng)建InputChannel,并通過(guò)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ù)傳過(guò)去 //...... if (mInputChannel != null) {if (mInputQueueCallback != null) { mInputQueue = new InputQueue(); mInputQueueCallback.onInputQueueCreated(mInputQueue);}//創(chuàng)建 WindowInputEventReceiver 對(duì)象mInputEventReceiver = new WindowInputEventReceiver(mInputChannel, Looper.myLooper()); }}

addToDisplay是一個(gè)AIDL方法,它的實(shí)現(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 會(huì)調(diào)用native的 nativeOpenInputChannelPair ,在native中創(chuàng)建兩個(gè)帶有文件描述符的 socket 。

int socketpair(int domain, int type, int protocol, int sv[2]) { //創(chuàng)建一對(duì)匿名的已經(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)容比較多,本文重點(diǎn)關(guān)注文件描述符相關(guān)的內(nèi)容。簡(jiǎn)單的理解,就是進(jìn)程間通訊會(huì)創(chuàng)建socket,所以也會(huì)創(chuàng)建文件描述符,而且會(huì)在服務(wù)端進(jìn)程和客戶端進(jìn)程各創(chuàng)建一個(gè)。另外,如果系統(tǒng)進(jìn)程文件描述符過(guò)多,理論上會(huì)造成系統(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)致的崩潰往往無(wú)法通過(guò)堆棧直接分析。道理很簡(jiǎn)單: 出問(wèn)題的代碼在消耗文件描述符同時(shí),正常的代碼邏輯可能也同樣在創(chuàng)建文件描述符,所以崩潰可能是被正常代碼觸發(fā)了。

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

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

詳解Android文件描述符

4.2 dump系統(tǒng)信息

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

4.3 線上監(jiān)控

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

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

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

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

標(biāo)簽: Android
相關(guān)文章:
日本不卡不码高清免费观看,久久国产精品久久w女人spa,黄色aa久久,三上悠亚国产精品一区二区三区
亚洲少妇在线| 国产日韩三级| 国产精品免费大片| 国产一卡不卡| 日本黄色精品| www.51av欧美视频| 欧美不卡高清一区二区三区| 欧美91精品| 一区二区视频欧美| 老牛影视一区二区三区| 久久精品97| 成人在线丰满少妇av| 久久精品成人| 国产精品美女久久久浪潮软件| 日本不卡视频在线观看| 亚洲影视一区| 亚洲深夜视频| 国产伦理久久久久久妇女| 日韩在线一二三区| 久久伊人久久| 激情婷婷综合| 亚洲区第一页| 国产精品66| 欧美成人基地| 天堂成人国产精品一区| 日韩视频一区二区三区在线播放免费观看| 国产美女精品| 麻豆久久一区| 在线中文字幕播放| 日韩精品亚洲专区在线观看| 久久精品国产免费| 亚洲一区二区三区四区电影 | 国产精品老牛| 日韩av成人高清| 久久精品系列| 老牛影视一区二区三区| 日韩高清中文字幕一区二区| 中文字幕av一区二区三区四区| 精品一区二区三区视频在线播放| xxxxx性欧美特大| 欧美一级二区| 亚洲自啪免费| 成人久久一区| 精品日韩在线| 日韩精品视频在线看| 999久久久国产精品| 国内精品麻豆美女在线播放视频| 亚洲精品视频一二三区| 91成人精品视频| 日韩中文首页| 久久久久网站| 色老板在线视频一区二区| 国模大尺度视频一区二区| 国产精品一区毛片| 亚洲天堂日韩在线| 亚洲精品激情| 国产日韩欧美三区| 久久香蕉网站| 福利在线一区| 你懂的亚洲视频| 亚洲免费专区| 午夜精品免费| 福利一区二区免费视频 | 国产综合色产| 久久久久.com| 欧美不卡高清一区二区三区| 成人综合一区| 久久一区欧美| 精品久久中文| 欧美三级精品| 欧美亚洲国产激情| 99国内精品| 亚洲视频国产精品| 97精品国产99久久久久久免费| 亚洲欧美专区| 国产精品欧美在线观看| 国产精品久久乐| 国产日韩欧美一区在线| 久久99久久人婷婷精品综合| 精品91福利视频| 久久国产中文字幕| 亚洲制服少妇| 亚洲久草在线| 免费在线欧美黄色| 日韩欧美一区免费| 久久午夜精品一区二区| 久久国内精品自在自线400部| 视频一区日韩精品| 国产精品视频3p| 99久久99视频只有精品| 视频在线在亚洲| 美女久久精品| 999精品色在线播放| 亚洲乱码视频| 婷婷激情一区| 五月亚洲婷婷 | 亚洲激情五月| 日本特黄久久久高潮| 精品国产乱码久久久久久1区2匹| 99视频精品视频高清免费| 一区二区精彩视频| 老色鬼精品视频在线观看播放| 人人精品亚洲| 亚洲一二av| 韩国精品主播一区二区在线观看 | 91精品国产自产观看在线| 精品五月天堂| 中文字幕一区二区av| 日韩欧美网址| 国产探花一区在线观看| 国产精品毛片一区二区三区| 国产精品自拍区| 男女男精品视频网| 日韩和的一区二在线| 欧美片网站免费| 国产视频一区三区| 久久免费高清| 国产伦久视频在线观看| 国产免费播放一区二区| 乱人伦精品视频在线观看| 日韩欧美二区| 国产中文在线播放| 久久久久九九精品影院| 国产亚洲高清在线观看| 日本精品另类| 日本a级不卡| 日韩精品欧美精品| 亚洲欧美视频一区二区三区| 国内亚洲精品| 蜜臀久久精品| 秋霞影视一区二区三区| 日韩福利一区| 久久国产影院| 久久天堂精品| 亚洲性视频h| 午夜在线视频一区二区区别| 99在线精品免费视频九九视 | 欧美羞羞视频| 成人羞羞视频播放网站| 精品福利久久久| 精品久久久亚洲| 久久电影tv| 99在线精品视频在线观看 | 日韩精选在线| 欧美日韩亚洲一区| 国产一区二区三区91| 91精品一区二区三区综合| 欧洲毛片在线视频免费观看| 伊人久久亚洲美女图片| 亚洲日本欧美| 国产极品一区| www.51av欧美视频| 1024精品久久久久久久久| 久久大逼视频| 欧美日韩一视频区二区| 国产传媒av在线| 男女精品网站| 成人在线视频区| 国产一区亚洲| 国产精品一区亚洲| 日韩在线短视频| 蜜桃久久久久久| 欧美国产免费| 亚洲国产日韩欧美在线| 国产日韩欧美三级| 日韩成人高清| 国产麻豆精品久久| 91九色精品| 国产精品香蕉| 99国产精品| 日韩高清欧美| 亚洲精品福利| 亚洲午夜av| 久久中文精品| 亚洲ww精品| 午夜欧美精品久久久久久久| 国产精品地址| 免费不卡在线视频| 中国字幕a在线看韩国电影| 日韩av网站在线免费观看| 亚洲精品一区二区在线看| 卡一精品卡二卡三网站乱码| 久久国产福利| 亚洲啊v在线| 精品午夜av| 日韩国产欧美在线视频| 夜夜精品视频| 欧美sss在线视频| 中文字幕在线视频久| 国产精品久久久久毛片大屁完整版| 蜜臀精品久久久久久蜜臀| 99久久精品国产亚洲精品| 一区二区三区四区日本视频| 国产精品久久久久久av公交车| 免费在线观看日韩欧美| 蜜桃视频欧美| 不卡中文一二三区| 久久精品国语| 国产传媒在线| 日韩中文在线电影|