久久福利_99r_国产日韩在线视频_直接看av的网站_中文欧美日韩_久久一

您的位置:首頁技術文章
文章詳情頁

詳解Android文件描述符

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

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

一、什么是文件描述符?

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

可以通過命令 ls -la /proc/$pid/fd 查看當前進程文件描述符使用信息。

詳解Android文件描述符

上圖中 箭頭前的數組部分是文件描述符,箭頭指向的部分是對應的文件信息。

詳解Android文件描述符

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

詳解Android文件描述符

當然也可以查看進程的允許打開的最大文件描述符數量。Linux默認進程最大文件描述符數量是1024,但是較新款的Android設置這個值被改為32768。

詳解Android文件描述符

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

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

struct task_struct{// 進程狀態long state;// 虛擬內存結構體struct mm_struct *mm;// 進程號pid_t pid;// 指向父進程的指針struct task_struct*parent;// 子進程列表struct list_head children;// 存放文件系統信息的指針struct fs_struct* fs;// 存放該進程打開的文件指針數組struct files_struct *files;};

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

/* * 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];//執行exec時需要關閉的文件描述符初值結合(從主進程中fork出子進程) unsigned long open_fds_init[1];//todo 含義補充 unsigned long full_fds_bits_init[1];//todo 含義補充 struct file __rcu * fd_array[NR_OPEN_DEFAULT];//默認的文件描述符長度};

一般情況,“文件描述符”指的就是文件指針數組 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; //指向文件對象指針數組的指針 unsigned long *close_on_exec; unsigned long *open_fds; //指向打開文件描述符的指針 unsigned long *full_fds_bits; struct rcu_head rcu;};

在file_struct初始化創建時,fdt指針指向的其實就是當前的的變量fdtab。當打開文件數超過初始設置的大小時,file_struct發生擴容,擴容后fdt指針會指向新分配的fdtable變量。

struct files_struct init_files = { .count = ATOMIC_INIT(1), .fdt= &init_files.fdtab,//指向當前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)是數據同步的一種方式,在當前的Linux內核中發揮著重要的作用。

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

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

struct file 處于內核空間,是內核在打開文件時創建,其中保存了文件偏移量,文件的inode等與文件相關的信息,在 Linux 內核中,file結構表示打開的文件描述符,而inode結構表示具體的文件。在文件的所有實例都關閉后,內核釋放這個數據結構。

struct file { union {struct llist_node fu_llist; //用于通用文件對象鏈表的指針struct rcu_head fu_rcuhead;//RCU(Read-Copy Update)是Linux 2.6內核中新的鎖機制 } f_u; struct path f_path;//path結構體,包含vfsmount:指出該文件的已安裝的文件系統,dentry:與文件相關的目錄項對象 struct inode*f_inode; /* cached value */ const struct file_operations *f_op;//文件操作,當進程打開文件的時候,這個文件的關聯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; //引用計數 unsigned intf_flags; //打開文件時候指定的標識,對應系統調用open的int flags參數。驅動程序為了支持非阻塞型操作需要檢查這個標志 fmode_t f_mode;//對文件的讀寫模式,對應系統調用open的mod_t mode參數。如果驅動程序需要這個值,可以直接讀取這個字段 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 */}

整體的數據結構示意圖如下:

詳解Android文件描述符

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

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

上文介紹了文件描述符的概念和部分源碼,如果要進一步理解文件描述符的工作原理,需要查看由內核維護的三個數據結構。

詳解Android文件描述符

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

三個表的關系如下:

詳解Android文件描述符

進程的文件描述符表為進程私有,該表的值是從0開始,在進程創建時會把前三位填入默認值,分別指向 標準輸入流,標準輸出流,標準錯誤流,系統總是使用最小的可用值。

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

每一個文件描述符都會對應一個打開文件,同時不同的文件描述符也可以對應同一個打開文件。這里的不同文件描述符既可以是同一個進程下,也可以是不同進程。

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

光看對應關系的結論有點亂,需要梳理每種對應關系的場景,幫助我們加深理解。

詳解Android文件描述符

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

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

舉個例子:

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

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

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

三、Android中FD泄漏場景

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

3.1 HandlerThread泄漏

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

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

HandlerThread在run方法中調用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在構造方法中創建MessageQueue對象。

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

MessageQueue,也就是我們在Handler學習中經常提到的消息隊列,在構造方法中調用了native層的初始化方法。

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

MessageQueue對應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初始化方法中會先判斷是否存在當前線程的Native層的Looper,如果沒有的就創建一個新的Looper并保存。

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

在Looper的構造函數中,我們發現“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函數會返回一個新的文件描述符。

/** * [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開發過程中常用的操作,如果沒有正確關閉流操作,除了可能會導致內存泄漏,也會導致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(); }}

如果在流操作過程中發生異常,就有可能導致泄漏。正確的寫法應該是在final塊中關閉流。

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

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

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

open方法會直接調用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無法跳轉查看,可以在 androidxref 網站,通過“Java類名_native方法名”的方法進行搜索。例如,這可以搜索 FileOutputStream_open0 。

接下來,讓我們進入native方法查看對應實現。

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);//調用open,獲取fd if (fd != -1) {//......if (result != -1) { //......} else { close(fd); fd = -1;} } return fd;}

到這里就結束了嗎?

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

我們再來看SET_FD這個宏的定義,在這個宏定義中,通過反射的方式給Java層對象的成員變量賦值。由于上文內容可知,open0是對象的jni方法,所以宏中的this,就是初始創建的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的初始化跟進就完成了,我們已經找到了底層fd初始化的路徑。Android的IO操作還有其他的流操作類,大致流程基本類似,這里不再細述。

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

3.3 SQLite泄漏

在日常開發中如果使用數據庫SQLite管理本地數據,在數據庫查詢的cursor使用完成后,亦需要調用close方法釋放資源,否則也有可能導致內存和文件描述符的泄漏。

public void get() { db = ordersDBHelper.getReadableDatabase(); Cursor cursor = db.query(...); while (cursor.moveToNext()) { //...... } if(flag){ //某種原因導致retrn return; } //不調用close,fd就會泄漏 cursor.close();}

按照理解query操作應該會導致文件描述符泄漏,那我們就從query方法的實現開始分析。

然而,在query方法中并沒有發現文件描述符相關的代碼。

經過測試發現,moveToNext 調用后才會導致文件描述符增長。通過query方法可以獲取cursor的實現類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的實現。getCount 是抽象方法,在子類SQLiteCursor實現。

@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做判斷,如果還是初始值,則會調用fillWindow方法。

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

clearOrCreateWindow 實現又回到父類 AbstractWindowedCursor 中。

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

在CursorWindow的構造方法中,通過nativeCreate方法調用到native層的初始化。

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

在C++代碼中會繼續調用一個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方法中,我們可以發現fd創建相關的代碼。

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 方法最終會調用到open函數打開文件并返回系統創建的文件描述符。這部分代碼不在贅述,有興趣的可以自行查看 。

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

3.4 InputChannel 導致的泄漏

WindowManager.addView

通過WindowManager反復添加view也會導致文件描述符增長,可以通過調用removeView釋放之前創建的FD。

private void addView() { View windowView = LayoutInflater.from(getApplication()).inflate(R.layout.layout_window, null); //重復調用 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中會創建InputChannel,并通過Binder機制傳到服務端。

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

addToDisplay是一個AIDL方法,它的實現類是源碼中的Session。最終調用的是 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 方法中創建 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 中創建 InputChannel ,并把客戶端的傳回去。

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

InputChannel 的 openInputChannelPair 會調用native的 nativeOpenInputChannelPair ,在native中創建兩個帶有文件描述符的 socket 。

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

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

四、如何排查

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

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

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

4.1 打印當前FD信息

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

詳解Android文件描述符

4.2 dump系統信息

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

4.3 線上監控

如果是本地無法復現問題,可以嘗試添加線上監控代碼,定時輪詢當前進程使用的FD數量,在達到閾值時,讀取當前FD的信息,并傳到后臺分析,獲取FD對應文件信息的代碼如下。

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

除了直接對 FD相關的信息進行分析,還需要關注logcat中是否有頻繁打印的信息,例如:socket創建失敗。

以上就是詳解Android 文件描述符的詳細內容,更多關于Android文件描述符的資料請關注好吧啦網其它相關文章!

標簽: Android
相關文章:
主站蜘蛛池模板: 日韩视频国产 | 欧美一级网站 | 有码在线| 国产一级一级毛片女人精品 | 成人一区二区在线 | 天堂成人国产精品一区 | 精品久久久久久久 | 亚洲精品国产第一综合99久久 | 久久久一区二区 | www.91在线| 国产丝袜一区二区三区免费视频 | 国产欧美精品一区二区三区四区 | 国产成人在线一区二区 | 91人人爽人人爽人人精88v | 欧美综合国产精品久久丁香 | 福利在线看 | 欧美黄色一区 | 欧美视频在线播放 | 午夜看片 | 成人a级网站 | 国产精品一品二区三区的使用体验 | 亚洲黄色一区二区 | a久久| 91精品在线观看入口 | 日日摸夜夜添夜夜添高潮视频 | 可以在线观看的av网站 | 九九热热九九 | 国产免费一级特黄录像 | 国产中文字幕在线观看 | 黄网站免费在线 | 欧洲精品久久久 | 真实国产露脸乱 | 欧美在线a | 国产精品二区三区 | 一级片在线观看 | av一区二区在线观看 | 美女视频一区 | 欧美日韩国产一区二区三区 | 亚洲国产成人久久 | 亚洲一区二区日韩 | 欧美一区二区三区久久精品 | 五月天婷婷精品 | 日韩99| www.久久精品| 欧美色视 | 波多野结衣一区二区三区中文字幕 | 中文字幕高清av | 午夜免费视频 | 日韩成人免费av | 色视频在线免费观看 | 乳色吐息在线观看 | 国产精品一区二区三区久久 | 日韩超碰| 亚洲一区二区三区观看 | 国产黄色在线观看 | 99视频网站 | 国产精品福利一区 | 97超碰自拍 | 在线免费观看羞羞视频 | 欧美视频精品 | 国产一区二区精品 | 成年无码av片在线 | 国产成人精品一区二区三区四区 | 亚洲成人精品在线 | 超碰一区二区三区 | 国产精品女教师av久久 | 一区二区亚洲视频 | 亚洲一区二区三区在线视频 | 天堂一区二区三区四区 | 日韩综合一区 | 国产福利91精品一区二区 | 国产成人在线看 | www.黄网| 国产精品国产精品国产 | 黄色精品视频 | 青青久久网 | 在线看片日韩 | 男人的天堂在线视频 | 91在线视频观看 | 成人欧美一区二区三区黑人孕妇 | 久久久久一区二区 | 国产人免费人成免费视频 | 日韩精品99久久久久中文字幕 | 日本一二三视频 | 人妖一区| 国产情侣自拍啪啪 | 国产三级视频 | 亚洲国产成人av好男人在线观看 | 亚洲综合在线播放 | 亚洲男人网 | 亚洲国产青草 | 精品一区二区视频 | 成人亚洲精品777777大片 | 国产精品成人一区二区三区夜夜夜 | 午夜影院普通用户体验区 | 国产一区二区三区四区五区加勒比 | 日本黄色影片在线观看 | av天天操| 欧美激情精品久久久久 | 日韩在线观看视频一区二区三区 | 久久艹99| 色欧美综合 | 日韩精品一区二区三区 | 欧洲一级毛片 | 欧美日韩一区二区视频在线观看 | 午夜精品久久久久久久久久久久久 | 欧美成人精品一区二区三区在线看 | 久久国内精品 | 国产精品一区二区久久 | 欧美 日韩 国产 一区 | 精品欧美乱码久久久久久 | 二区三区| 欧美精品综合 | 蜜桃av一区| 一区二区三区在线观看国产 | 国产伦精品一区二区三区在线 | 欧美精品久久久久 | 日韩av一区在线 | 91免费视频观看 | 国产视频精品视频 | 视频羞羞| 国产一区二区在线视频 | 视频一区 中文字幕 | 一本久久a久久精品亚洲 | 日本人做爰大片免费观看一老师 | 成人毛片在线视频 | 日日操av| 色婷婷亚洲一区二区三区 | 午夜影院黄色 | 在线一区| 日韩三级视频 | 欧美一区二区三区免费 | 欧美精品在线一区 | 激情999| 亚洲网站免费观看 | www.久久久.com | 成人免费视频网 | 国产一区二区视频在线观看 | 国产一区二区三区四区 | 精品久久99| 三区视频 | 天堂一区二区三区 | 亚洲天堂一区二区三区 | 91亚洲精品乱码久久久久久蜜桃 | 天天干狠狠干 | 国产精品久久免费视频 | 国产精品久久久久久久久久久久久 | 国产精品久久a | 欧美精产国品一二三区 | 中文字幕一区二区三区四区 | 超碰在线99| 亚洲欧洲久久 | 日韩精品一区二区三区第95 | 97视频久久 | av中文字幕网 | 久久e久久| 波多野结衣一二三四区 | 亚洲一区中文字幕在线观看 | 成人1区2区 | 国产一区二区在线播放 | 久久亚洲一区二区 | 亚洲精品在线国产 | 久久久一 | a免费观看 | 久久99精品视频在线观看 | 中文字幕乱码亚洲精品一区 | 精品久久久中文字幕 | 亚洲成人精品一区 | 一区二区三区在线免费观看 | 国产精品精品 | h免费在线观看 | 国产精品夜夜春夜夜爽久久电影 | 免费啪啪网站 | 日本三级做a全过程在线观看 | www.avtt天堂网 | 日韩aaa久久蜜桃av | 欧美精品一区二区在线观看 | 成人av片在线观看 | 91九色视频在线 | 99精品电影| 999视频 | 免费在线h| 日韩一区二区三区在线观看 | 久久国产精品视频 | 伊人网在线视频免费观看 | 精品伊人久久 | 国产精品一区二区三区在线 | 国产情侣激情 | 午夜av电影| 亚洲一区二区中文字幕在线观看 | 一级做a爰性色毛片免费1 | cao视频| 欧美日韩在线一 | 久久久久久久久国产成人免费 | 成人久久久久久久久 | 亚洲精品乱码久久观看网 | 一级毛片免费看 | 国产亚洲精品精品国产亚洲综合 | 91精品久久久久久久久中文字幕 | 色黄网站| 久国久产久精永久网页 | 正在播放国产一区 | 国产1区在线观看 | 狠狠狠干 | 国产精品女教师av久久 | 国产一区二区三区免费看 | 欧洲另类二三四区 | 天天插天天干 | 欧美福利专区 | 亚洲国产精品99久久久久久久久 | 久久久久国产 | 欧美午夜精品一区二区三区电影 | 国产精品成人观看视频国产奇米 | 中文字幕综合 | 天天干人人 | 亚洲欧洲在线观看 | 国产一区二区自拍视频 | 日韩不卡一区 | 国产精品一区二区无线 | 夜本色 | 中文字幕不卡 | 午夜av电影 | 欧洲一级毛片 | 伊人网影院 | 亚洲一区日韩 | 欧美国产综合一区 | av成人免费| 欧美日韩精品一二区 | 二区三区在线观看 | 日日撸| 日韩精品免费视频 | 美女午夜影院 | 在线观看av网站永久 | av男人的天堂在线 | 91精品一区二区三区久久久久久 | 日韩精品一区二区三区四区视频 | 亚洲欧美日韩在线 | 中文字幕在线观看av | 国产成人精品一区二 | 在线免费黄色 | 欧美一区二区三区精品 | 久草成人网 | 刺激网| 91国内精品久久 | av一二| 91高清视频 | 午夜欧美精品久久久久 | 国产精品成人一区二区 | 久久91久久久久麻豆精品 | 亚洲成人av在线 | a一级毛片 | 在线视频a | 久久一区二区三区四区五区 | 日本一区二区三区免费观看 | 男女视频网站 | 亚洲国产成人在线 | 日本一区二区视频 | 日韩欧美国产一区二区 | 亚洲tv国产 | 日本私人网站在线观看 | 国产女人和拘做受在线视频 | 国产在线观看一区二区 | 91电影在线 | 亚洲激情一区 | 51国产午夜精品免费视频 | 一区二区视频免费 | 久久99久久久久 | 艳妇荡乳豪妇荡淫 | 欧美精品久久一区 | 一区二区免费视频 | 亚洲精选久久 | 欧美日本亚洲 | 久久成人国产 | 中文字幕视频网站 | 91一级 | 91亚洲视频 | 欧美日韩中文字幕 | 日韩一区二区三区在线观看 | 国产成人精品一区二区三区四区 | 黄色一级电影免费观看 | 午夜理伦三级 | 日本精品一区二区三区在线观看 | 久久这里只有精品首页 | 久久久久久久久久影院 | 欧美喷潮久久久xxxxx | 免费一区二区三区 | 呦一呦二在线精品视频 | 日本a视频 | 亚洲第一精品在线 | 婷婷综合 | 日日做夜夜爱 | 国产精品久久久久久久久软件 | 色综合久久久久 | 久久久91| 国产在线一区二区三区 | 精品一区二区在线观看 | 九九免费观看全部免费视频 | 亚洲国产精品99久久久久久久久 | 亚洲第一天堂无码专区 | 国产精品夜间视频香蕉 | 日本久久免费 | 亚洲欧美日韩另类精品一区二区三区 | 四虎影音| 日韩精品一区二区三区中文在线 | 亚洲小视频网站 | 91短视频版在线观看www免费 | 中文字幕亚洲一区二区va在线 | 天天干天天去 | 一区在线免费 | 国产亚洲欧美一区二区 | 永久在线观看 | 国产一区二区三区久久 | 九九热精品在线 | 91九色porny首页最多播放 | 亚洲日本欧美日韩高观看 | 91视频.com| 亚洲人成人一区二区在线观看 | 国产精品一区二区三区久久 | 在线国产一区 | 麻豆网址 | 中文字幕一区二区三区乱码在线 | 99精品国产高清一区二区麻豆 | 国产精品午夜电影 | 亚洲视频在线观看 | 久久综合九色综合欧美狠狠 | 精品中文字幕在线观看 | 中文字幕精品一区 | 91久久精品国产91久久 | av中文字幕在线播放 | av免费网站 | 亚洲精品乱码 | 免费在线a | 美女天天操| 国产一区久久精品 | 久久久精品高清 | 午夜精品一区 | 999在线观看精品免费不卡网站 | 日韩中文字 | 欧美a在线| 日韩中文字幕免费观看 | 日韩成人在线一区 | 亚洲午夜精品a | 国产欧美日韩精品在线 | 欧美一级网站 | 91久久夜色精品国产网站 | 久久兔费看a级 | ririsao亚洲国产中文 | 欧美一区二区视频 | 久久婷婷欧美 | 欧美成人免费 | 日韩一级二级三级 | 91夜夜蜜桃臀一区二区三区 | 久久综合av | 男人的天堂视频网站 | 免费观看的黄色 | 9久久婷婷国产综合精品性色 | 91精品国产91久久久久久吃药 | 在线观看日韩 | 国产精品日韩精品 | 免费av在线网站 | 99视频免费看 | 精品亚洲永久免费精品 | 日韩国产在线观看 | 日韩久久久久久 | 欧美一级在线观看 | 久久一日本道色综合久久 | 青青草一区 | 懂色av色香蕉一区二区蜜桃 | 亚洲视频一区二区三区 | 国产小视频在线播放 | 永久精品 | 欧美日韩精品一区 | 国产在线精品一区二区三区 | 成人午夜精品一区二区三区 | 免费观看羞羞视频网站 | 中文字幕在线观 | 亚洲精品视频免费 | 国产午夜精品美女视频明星a级 | 一区二区三区在线 | 国产在线成人 | 久在草视频 | 精品一区二区在线观看 | 在线一区 | 9久久婷婷国产综合精品性色 | 亚洲不卡视频 | 日本不卡视频 | 99久久久 | h片在线免费观看 | 亚洲精品中文视频 | 中文字幕一区二区三区四区 | 国产一区二区视频精品 | 欧美a级成人淫片免费看 | 亚洲一区二区三区四区五区中文 | 欧美日韩一区二区不卡 | 韩国成人精品a∨在线观看 国产伊人av | 亚洲成人二区 | 无套内谢孕妇毛片免费看红桃影视 | 免费午夜电影 | 亚洲高清视频在线观看 | www.久久精品视频 | 国产一区二区三区在线免费观看 | 久久精品久久综合 | 亚洲高清视频一区 | 亚洲欧美激情另类 | 成人在线网址 | 91xx在线观看| 婷婷色国产偷v国产偷v小说 | 欧美精品自拍 | 欧美精品tv | 欧美区亚洲区 | 亚洲乱码二区 | 国产3区| 亚洲色图综合 | 成人免费视频网站在线观看 | 欧美日韩亚洲视频 | 国产高清在线精品 | 亚洲欧洲精品视频 | 91久久精品国产亚洲a∨麻豆 | www国产成人免费观看视频,深夜成人网 | 青青草在线视频免费观看 | 亚洲国产精品va在线看黑人 | 中文无吗 | 成人国产电影 | 日本高清中文字幕 | av一区二区在线观看 | 91精品午夜 | 图片区 国产 欧美 另类 在线 | 国产在线观看一区二区三区 | 国产精品永久免费 | 国产精品美女久久久久久免费 | 久久亚洲综合 | av三级| 91久久 | 欧美一区二区三区免费观看视频 | 裸体的日本在线观看 | 午夜精品导航 | 国产精品片aa在线观看 | 亚洲精品日本 | 国产成人综合在线 | 一级毛片网 | 国产99久久 | 日韩在线不卡一区 | 亚洲精品在线免费 | 成人免毛片 | 在线视频中文字幕 | 日本在线黄色 | 亚洲国产精品一区二区三区 | 亚洲欧美一区二区三区在线 | 中文字幕在线电影 | 久久人人爽人人爽人人片av不 | 欧美福利一区二区三区 | 黄色毛片在线播放 | 天天操天天干视频 | 青青久久北条麻妃 | av毛片| 国产精品久久久久一区二区三区 | 亚洲看片网站 | 精品欧美乱码久久久久久 | 99色资源 | 欧美日韩在线综合 | 天堂精品久久 | 看羞羞视频免费 | 欧美日韩国产免费一区二区三区 | 一级毛片在线免费看 | 2012中文版免费观看 | 国产99久久久精品视频 | 久操国产 | 午夜成年人| 欧美日一级片 | 欧美视频免费在线 | 日本成人中文字幕 | 国产精品久久久久久久久 | 日本一区二区不卡视频 | 国产专区在线视频 | 欧美日韩在线看 | 成人国产精品久久久 | 伊人久久视频 | 国产2区 | 精品国产一区二区三区成人影院 | 国产在线h | 综合久久久久 | 欧美一区二区三区在线观看视频 | 国产高清不卡在线 | 性视频网站免费 | 亚洲 中文 欧美 日韩 在线观看 | 亚洲国产91 | 久久亚洲一区二区 | 亚洲aⅴ网站 | 日本黄色a视频 | 国产成人免费视频网站视频社区 | 免费看国产片在线观看 | 精品国产一区二区三区久久影院 | 婷婷国产精品 | 免费在线一区二区三区 | 欧美一级免费 | 青青草草 | 中文字幕亚洲视频 | 久久99国产精品久久99果冻传媒 | 久久国产一区 | 日韩综合| 欧美日韩一二区 | 日韩精品一区二 | 色综合天天综合网国产成人网 | 日韩久久一区 | 最新国产视频 | 99国产精品一区 | 91视频在线免费观看 | 婷婷桃色网 | 国产精品96久久久久久久 | 久久一区二区三区四区五区 | 中文精品一区二区三区 | 蜜桃comaaa| 亚洲精品欧美视频 | 综合久久综合久久 | 中文字幕在线视频观看 | 在线观看毛片网站 | 国产一区中文字幕 | 久久一日本道色综合久久 | 成人午夜视频在线观看 | 国产一区二区在线看 | 精品久久久久久 | 色视频网站在线观看 | 中文字幕在线影院 | 日日日操 | 成人黄色电影在线观看 | 欧美在线一区二区 | 久久亚洲国产精品 | 亚洲午夜精品一区二区三区他趣 | www.99热这里只有精品 | 色先锋av资源中文字幕 | 黄色在线观看 | 天天操天天拍 | 日韩一区二区免费视频 | 理论片免费在线观看 | 91视频8mav| 精品三区| 国产剧情一区二区 | 91久色| 国产精品污www在线观看 | 91久久精品日日躁夜夜躁国产 | 国产精品视频一区二区三区 | 精品视频网站 | 超碰日韩在线 | 国产精品夜色一区二区三区 | 超碰在线看 | 亚洲国产成人av | 久久激情视频 | 日韩精品av一区二区三区 | av看片网| 久久久国产精品入口麻豆 | 日韩欧美在线免费观看 | 国产激情99| 精品亚洲一区二区三区 | 亚洲天堂免费 | 成人午夜在线视频 | 久久四色| 久久精品手机视频 | 精品久久久蜜桃 | 米奇成人网 | 亚洲成人免费在线观看 | caoporn国产精品免费公开 | 国产精品精品视频一区二区三区 | 91高清在线 | 日韩毛片免费视频一级特黄 | 精精国产 | 欧美视频精品在线观看 | 91视频电影| 国产成人av网站 | av免费网站| 免费在线看a | 国产精品www | 成人免费淫片aa视频免费 | 欧美视频在线观看不卡 | 99精品一级欧美片免费播放 | 一区二区精品在线 | 日本一二三区视频 | 四虎影院在线免费播放 | 一色一黄视频 | 国产精品爱久久久久久久 | 午夜激情视频免费 | 伊人网一区 | 成人av免费观看 | 精品国产高清一区二区三区 | 精品视频一区二区三区 | 成人h视频在线观看 | av网站在线免费观看 | 亚洲国产精品成人 | 精品久久久久久久久久久久 | 操操操操操操操 | 午夜精品一区二区三区在线 | 国产成人99久久亚洲综合精品 | 成人免费视频网站在线观看 | 四虎影音 | 国产人成精品一区二区三 | 91社影院在线观看 | 密桃av| 午夜视频免费 | 国产麻豆乱码精品一区二区三区 | 国产乱码精品一区二区三 | 国产精品成人国产乱一区 | 日日人人 | 色爱区综合 | 精品一区二区av | 亚洲欧美视频 | 国产成人精品a视频一区www | 欧美精品网站 | 亚洲第一福利视频 | 午夜影院黄色 | 久久久www成人免费精品 | 日本成人高清视频 | 久在线 | 午夜影院在线观看视频 | 一级片在线免费看 | 最近中文字幕免费观看 | 玖玖操 |