分享實際遇到的案例。
至於什麼是FD leak? 此文章已有詳細介紹https://juejin.cn/post/7058098018683191310,就不贅述。
可簡單理解成打開thread, cursor, 檔案, io等,皆會產生FD檔,當超過系統限制(基本是1024)時,便會造成錯誤。
偵測
要知道問題發生在哪,有沒有解決,需要每段時間去偵測FD數量變化。
參考https://blog.csdn.net/qunimaode/article/details/126996454
| 12
 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。
| 12
 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。
| 12
 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不因主執行緒做太多事而卡頓,改放至副執行緒。
| 12
 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操作要維持在主執行緒,業務邏輯就該放在副執行緒。