分享實際遇到的案例。
至於什麼是FD leak? 此文章已有詳細介紹https://juejin.cn/post/7058098018683191310,就不贅述。
可簡單理解成打開thread, cursor, 檔案, io等,皆會產生FD檔,當超過系統限制(基本是1024)時,便會造成錯誤。
偵測
要知道問題發生在哪,有沒有解決,需要每段時間去偵測FD數量變化。
參考https://blog.csdn.net/qunimaode/article/details/126996454
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| public static void logFdCount() { int count = 0; try { Process process = Runtime.getRuntime().exec("ls /proc/" + android.os.Process.myPid() + "/fd"); BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(process.getErrorStream())); String s = bufferedReader.readLine(); while (s != null) { s = bufferedReader.readLine(); count++; } bufferedReader.close();
bufferedReader = new BufferedReader(new InputStreamReader(process.getInputStream())); s = bufferedReader.readLine(); while (s != null) { s = bufferedReader.readLine(); count++; } bufferedReader.close(); process.destroy(); } catch (IOException ex) { Log.e("Mike", "Exception: " + ex.getLocalizedMessage(), ex); } Log.d("Mike", "fd count=" + count); }
|
Thread
有用Executors.newSingleThreadExecutor(),但對於每次使用MyService時,都建一次新的,是沒有達成共用的。當api呼叫的量達到一定程度,便會造成FD leak。
1 2 3 4 5 6 7 8 9 10 11
| public static MyService getMyService(String baseUrl) { OkHttpClient.Builder httpBuilder = new OkHttpClient.Builder(); Retrofit retrofit = new Retrofit.Builder() .baseUrl(baseUrl) .client(httpBuilder.build()) .addConverterFactory(GsonConverterFactory.create()) .callbackExecutor(Executors.newSingleThreadExecutor()) .build();
return retrofit.create(MyService.class); }
|
當時的解法是共用threadPool,這樣即便有打向不同服務的api,也可共用和重複利用thread。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| public static final Executor THREAD_POOL_EXECUTOR = Executors.newCachedThreadPool(new ThreadFactory() { private final AtomicInteger mCount = new AtomicInteger(1);
@Override public Thread newThread(@NonNull Runnable runnable) { return new Thread(runnable, "work-" + mCount.getAndIncrement()); } });
public static MyService getMyService(String baseUrl) { OkHttpClient.Builder httpBuilder = new OkHttpClient.Builder(); Retrofit retrofit = new Retrofit.Builder() .baseUrl(baseUrl) .client(httpBuilder.build()) .addConverterFactory(GsonConverterFactory.create()) .callbackExecutor(THREAD_POOL_EXECUTOR) .build();
return retrofit.create(MyService.class); }
|
Thread + Looper
為了讓ProgressDialog不因主執行緒做太多事而卡頓,改放至副執行緒。
1 2 3 4 5 6 7 8 9 10
| public static void showLoadingDialog(Activity activity) { new Thread(new Runnable() { @Override public void run() { Looper.prepare(); progressDialog = ProgressDialog.show(activity, null, "Loading..."); Looper.loop(); } }).start(); }
|
當呼叫Looper.loop()便會讓執行緒卡著,持續等待接收message,就算事後dismiss ProgressDialog,一樣不會終止。要避免這樣的寫法。
通則是UI操作要維持在主執行緒,業務邏輯就該放在副執行緒。