Android開發中比較常見的內存泄漏問題

轉載自https://www.jianshu.com/p/ab4a7e353076java

 

若是一個無用對象(不須要再使用的對象)仍然被其餘對象持有引用,形成該對象沒法被系統回收,以至該對象在堆中所佔用的內存單元沒法被釋放而形成內存空間浪費,這中狀況就是內存泄露。android

在Android開發中,一些很差的編程習慣會致使咱們的開發的app存在內存泄露的狀況。下面介紹一些在Android開發中常見的內存泄露場景及優化方案。編程

單例致使內存泄露

單例模式在Android開發中會常常用到,可是若是使用不當就會致使內存泄露。由於單例的靜態特性使得它的生命週期同應用的生命週期同樣長,若是一個對象已經沒有用處了,可是單例還持有它的引用,那麼在整個應用程序的生命週期它都不能正常被回收,從而致使內存泄露。api

public class AppSettings {

    private static AppSettings sInstance;
    private Context mContext;

    private AppSettings(Context context) {
        this.mContext = context;
    }

    public static AppSettings getInstance(Context context) {
        if (sInstance == null) {
            sInstance = new AppSettings(context);
        }
        return sInstance;
    }
}

像上面代碼中這樣的單例,若是咱們在調用getInstance(Context context)方法的時候傳入的context參數是ActivityService等上下文,就會致使內存泄露。網絡

Activity爲例,當咱們啓動一個Activity,並調用getInstance(Context context)方法去獲取AppSettings的單例,傳入Activity.this做爲context,這樣AppSettings類的單例sInstance就持有了Activity的引用,當咱們退出Activity時,該Activity就沒有用了,可是由於sIntance做爲靜態單例(在應用程序的整個生命週期中存在)會繼續持有這個Activity的引用,致使這個Activity對象沒法被回收釋放,這就形成了內存泄露。app

爲了不這樣單例致使內存泄露,咱們能夠將context參數改成全局的上下文:異步

private AppSettings(Context context) {
    this.mContext = context.getApplicationContext();
}

全局的上下文Application Context就是應用程序的上下文,和單例的生命週期同樣長,這樣就避免了內存泄漏。ide

單例模式對應應用程序的生命週期,因此咱們在構造單例的時候儘可能避免使用Activity的上下文,而是使用Application的上下文。oop

靜態變量致使內存泄露

靜態變量存儲在方法區,它的生命週期從類加載開始,到整個進程結束。一旦靜態變量初始化後,它所持有的引用只有等到進程結束纔會釋放。學習

好比下面這樣的狀況,在Activity中爲了不重複的建立info,將sInfo做爲靜態變量:

public class MainActivity extends AppCompatActivity {

    private static Info sInfo;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        if (sInfo != null) {
            sInfo = new Info(this);
        }
    }
}

class Info {
    public Info(Activity activity) {
    }
}

Info做爲Activity的靜態成員,而且持有Activity的引用,可是sInfo做爲靜態變量,生命週期確定比Activity長。因此當Activity退出後,sInfo仍然引用了ActivityActivity不能被回收,這就致使了內存泄露。

在Android開發中,靜態持有不少時候都有可能由於其使用的生命週期不一致而致使內存泄露,因此咱們在新建靜態持有的變量的時候須要多考慮一下各個成員之間的引用關係,而且儘可能少地使用靜態持有的變量,以免發生內存泄露。固然,咱們也能夠在適當的時候講靜態量重置爲null,使其再也不持有引用,這樣也能夠避免內存泄露。

非靜態內部類致使內存泄露

非靜態內部類(包括匿名內部類)默認就會持有外部類的引用,當非靜態內部類對象的生命週期比外部類對象的生命週期長時,就會致使內存泄露。

非靜態內部類致使的內存泄露在Android開發中有一種典型的場景就是使用Handler,不少開發者在使用Handler是這樣寫的:

public class MainActivity extends AppCompatActivity {
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        start();
    }

    private void start() {
        Message msg = Message.obtain();
        msg.what = 1;
        mHandler.sendMessage(msg);
    }

    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            if (msg.what == 1) {
                // 作相應邏輯
            }
        }
    };
}

也許有人會說,mHandler並未做爲靜態變量持有Activity引用,生命週期可能不會比Activity長,應該不必定會致使內存泄露呢,顯然不是這樣的!

熟悉Handler消息機制的都知道,mHandler會做爲成員變量保存在發送的消息msg中,即msg持有mHandler的引用,而mHandlerActivity的非靜態內部類實例,即mHandler持有Activity的引用,那麼咱們就能夠理解爲msg間接持有Activity的引用。msg被髮送後先放到消息隊列MessageQueue中,而後等待Looper的輪詢處理(MessageQueueLooper都是與線程相關聯的,MessageQueueLooper引用的成員變量,而Looper是保存在ThreadLocal中的)。那麼當Activity退出後,msg可能仍然存在於消息對列MessageQueue中未處理或者正在處理,那麼這樣就會致使Activity沒法被回收,以至發生Activity的內存泄露。

一般在Android開發中若是要使用內部類,但又要規避內存泄露,通常都會採用靜態內部類+弱引用的方式。

public class MainActivity extends AppCompatActivity {

    private Handler mHandler;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mHandler = new MyHandler(this);
        start();
    }

    private void start() {
        Message msg = Message.obtain();
        msg.what = 1;
        mHandler.sendMessage(msg);
    }

    private static class MyHandler extends Handler {

        private WeakReference<MainActivity> activityWeakReference;

        public MyHandler(MainActivity activity) {
            activityWeakReference = new WeakReference<>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            MainActivity activity = activityWeakReference.get();
            if (activity != null) {
                if (msg.what == 1) {
                    // 作相應邏輯
                }
            }
        }
    }
}

mHandler經過弱引用的方式持有Activity,當GC執行垃圾回收時,遇到Activity就會回收並釋放所佔據的內存單元。這樣就不會發生內存泄露了。

上面的作法確實避免了Activity致使的內存泄露,發送的msg再也不已經沒有持有Activity的引用了,可是msg仍是有可能存在消息隊列MessageQueue中,因此更好的是在Activity銷燬時就將mHandler的回調和發送的消息給移除掉。

@Override
protected void onDestroy() {
    super.onDestroy();
    mHandler.removeCallbacksAndMessages(null);
}

非靜態內部類形成內存泄露還有一種狀況就是使用Thread或者AsyncTask

好比在Activity中直接new一個子線程Thread

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        new Thread(new Runnable() {
            @Override
            public void run() {
                // 模擬相應耗時邏輯
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
}

或者直接新建AsyncTask異步任務:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        new AsyncTask<Void, Void, Void>() {
            @Override
            protected Void doInBackground(Void... params) {
                // 模擬相應耗時邏輯
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                return null;
            }
        }.execute();
    }
}

不少初學者都會像上面這樣新建線程和異步任務,卻不知這樣的寫法很是地不友好,這種方式新建的子線程ThreadAsyncTask都是匿名內部類對象,默認就隱式的持有外部Activity的引用,致使Activity內存泄露。要避免內存泄露的話仍是須要像上面Handler同樣使用靜態內部類+弱應用的方式(代碼就不列了,參考上面Hanlder的正確寫法)。

未取消註冊或回調致使內存泄露

好比咱們在Activity中註冊廣播,若是在Activity銷燬後不取消註冊,那麼這個剛播會一直存在系統中,同上面所說的非靜態內部類同樣持有Activity引用,致使內存泄露。所以註冊廣播後在Activity銷燬後必定要取消註冊。

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        this.registerReceiver(mReceiver, new IntentFilter());
    }

    private BroadcastReceiver mReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            // 接收到廣播須要作的邏輯
        }
    };

    @Override
    protected void onDestroy() {
        super.onDestroy();
        this.unregisterReceiver(mReceiver);
    }
}

在註冊觀察則模式的時候,若是不及時取消也會形成內存泄露。好比使用Retrofit+RxJava註冊網絡請求的觀察者回調,一樣做爲匿名內部類持有外部引用,因此須要記得在不用或者銷燬的時候取消註冊。

Timer和TimerTask致使內存泄露

TimerTimerTask在Android中一般會被用來作一些計時或循環任務,好比實現無限輪播的ViewPager

public class MainActivity extends AppCompatActivity {

    private ViewPager mViewPager;
    private PagerAdapter mAdapter;
    private Timer mTimer;
    private TimerTask mTimerTask;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        init();
        mTimer.schedule(mTimerTask, 3000, 3000);
    }

    private void init() {
        mViewPager = (ViewPager) findViewById(R.id.view_pager);
        mAdapter = new ViewPagerAdapter();
        mViewPager.setAdapter(mAdapter);

        mTimer = new Timer();
        mTimerTask = new TimerTask() {
            @Override
            public void run() {
                MainActivity.this.runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        loopViewpager();
                    }
                });
            }
        };
    }

    private void loopViewpager() {
        if (mAdapter.getCount() > 0) {
            int curPos = mViewPager.getCurrentItem();
            curPos = (++curPos) % mAdapter.getCount();
            mViewPager.setCurrentItem(curPos);
        }
    }

    private void stopLoopViewPager() {
        if (mTimer != null) {
            mTimer.cancel();
            mTimer.purge();
            mTimer = null;
        }
        if (mTimerTask != null) {
            mTimerTask.cancel();
            mTimerTask = null;
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        stopLoopViewPager();
    }
}

當咱們Activity銷燬的時,有可能Timer還在繼續等待執行TimerTask,它持有Activity的引用不能被回收,所以當咱們Activity銷燬的時候要當即cancelTimerTimerTask,以免發生內存泄漏。

集合中的對象未清理形成內存泄露

這個比較好理解,若是一個對象放入到ArrayListHashMap等集合中,這個集合就會持有該對象的引用。當咱們再也不須要這個對象時,也並無將它從集合中移除,這樣只要集合還在使用(而此對象已經無用了),這個對象就形成了內存泄露。而且若是集合被靜態引用的話,集合裏面那些沒有用的對象更會形成內存泄露了。因此在使用集合時要及時將不用的對象從集合remove,或者clear集合,以免內存泄漏。

資源未關閉或釋放致使內存泄露

在使用IOFile流或者SqliteCursor等資源時要及時關閉。這些資源在進行讀寫操做時一般都使用了緩衝,若是及時不關閉,這些緩衝對象就會一直被佔用而得不到釋放,以至發生內存泄露。所以咱們在不須要使用它們的時候就及時關閉,以便緩衝能及時獲得釋放,從而避免內存泄露。

屬性動畫形成內存泄露

動畫一樣是一個耗時任務,好比在Activity中啓動了屬性動畫(ObjectAnimator),可是在銷燬的時候,沒有調用cancle方法,雖然咱們看不到動畫了,可是這個動畫依然會不斷地播放下去,動畫引用所在的控件,所在的控件引用Activity,這就形成Activity沒法正常釋放。所以一樣要在Activity銷燬的時候cancel掉屬性動畫,避免發生內存泄漏。

@Override
protected void onDestroy() {
    super.onDestroy();
    mAnimator.cancel();
}
WebView形成內存泄露

關於WebView的內存泄露,由於WebView在加載網頁後會長期佔用內存而不能被釋放,所以咱們在Activity銷燬後要調用它的destory()方法來銷燬它以釋放內存。

另外在查閱WebView內存泄露相關資料時看到這種狀況:

Webview下面的Callback持有Activity引用,形成Webview內存沒法釋放,即便是調用了Webview.destory()等方法都沒法解決問題(Android5.1以後)。

最終的解決方案是:在銷燬WebView以前須要先將WebView從父容器中移除,而後在銷燬WebView。詳細分析過程請參考這篇文章:WebView內存泄漏解決方法

@Override
protected void onDestroy() {
    super.onDestroy();
    // 先從父控件中移除WebView
    mWebViewContainer.removeView(mWebView);
    mWebView.stopLoading();
    mWebView.getSettings().setJavaScriptEnabled(false);
    mWebView.clearHistory();
    mWebView.removeAllViews();
    mWebView.destroy();
}
總結

內存泄露在Android內存優化是一個比較重要的一個方面,不少時候程序中發生了內存泄露咱們不必定就能注意到,全部在編碼的過程要養成良好的習慣。總結下來只要作到如下這幾點就能避免大多數狀況的內存泄漏:

構造單例的時候儘可能別用Activity的引用;
靜態引用時注意應用對象的置空或者少用靜態引用;
使用靜態內部類+軟引用代替非靜態內部類;
及時取消廣播或者觀察者註冊;
耗時任務、屬性動畫在Activity銷燬時記得cancel
文件流、Cursor等資源及時關閉;
Activity銷燬時WebView的移除和銷燬。



做者:xiaoyanger
連接:https://www.jianshu.com/p/ab4a7e353076
來源:簡書
簡書著做權歸做者全部,任何形式的轉載都請聯繫做者得到受權並註明出處。

象持有它的引用從而致使它不能被回收,這就致使本該被回收的對象不能被回收而停留在堆內存中,內存泄漏就產生了。

內存泄漏有什麼影響呢?它是形成應用程序OOM的主要緣由之一。因爲Android系統爲每一個應用程序分配的內存有限,當一個應用中產生的內存泄漏比較多時,就不免會致使應用所須要的內存超過這個系統分配的內存限額,這就形成了內存溢出而致使應用Crash。

瞭解了內存泄漏的緣由及影響後,咱們須要作的就是掌握常見的內存泄漏,並在之後的Android程序開發中,儘可能避免它。下面小編蒐羅了5個Android開發中比較常見的內存泄漏問題及解決辦法,分享給你們,一塊兒來看看吧。

1、單例形成的內存泄漏

Android的單例模式很是受開發者的喜好,不過使用的不恰當的話也會形成內存泄漏。由於單例的靜態特性使得單例的生命週期和應用的生命週期同樣長,這就說明了若是一個對象已經不須要使用了,而單例對象還持有該對象的引用,那麼這個對象將不能被正常回收,這就致使了內存泄漏。

以下這個典例:

1
2
3
4
5
6
7
8
9
10
11
12
13
public  class  AppManager {
     private  static  AppManager instance;
     private  Context context;
     private  AppManager(Context context) {
         this .context = context;
     }
     public  static  AppManager getInstance(Context context) {
         if  (instance !=  null ) {
             instance =  new  AppManager(context);
         }
         return  instance;
     }
}

這是一個普通的單例模式,當建立這個單例的時候,因爲須要傳入一個Context,因此這個Context的生命週期的長短相當重要:

一、傳入的是Application的Context:這將沒有任何問題,由於單例的生命週期和Application的同樣長 ;

二、傳入的是Activity的Context:當這個Context所對應的Activity退出時,因爲該Context和Activity的生命週期同樣長(Activity間接繼承於Context),因此當前Activity退出時它的內存並不會被回收,由於單例對象持有該Activity的引用。

因此正確的單例應該修改成下面這種方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
public  class  AppManager {
     private  static  AppManager instance;
     private  Context context;
     private  AppManager(Context context) {
         this .context = context.getApplicationContext();
     }
     public  static  AppManager getInstance(Context context) {
         if  (instance !=  null ) {
             instance =  new  AppManager(context);
         }
         return  instance;
     }
}

這樣無論傳入什麼Context最終將使用Application的Context,而單例的生命週期和應用的同樣長,這樣就防止了內存泄漏。

2、非靜態內部類建立靜態實例形成的內存泄漏

有的時候咱們可能會在啓動頻繁的Activity中,爲了不重複建立相同的數據資源,會出現這種寫法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public  class  MainActivity  extends  AppCompatActivity {
     private  static  TestResource mResource =  null ;
     @Override
     protected  void  onCreate(Bundle savedInstanceState) {
         super .onCreate(savedInstanceState);
         setContentView(R.layout.activity_main);
         if (mManager ==  null ){
             mManager =  new  TestResource();
         }
         //...
     }
     class  TestResource {
         //...
     }
}

這樣就在Activity內部建立了一個非靜態內部類的單例,每次啓動Activity時都會使用該單例的數據,這樣雖然避免了資源的重複建立,不過這種寫法卻會形成內存泄漏,由於非靜態內部類默認會持有外部類的引用,而又使用了該非靜態內部類建立了一個靜態的實例,該實例的生命週期和應用的同樣長,這就致使了該靜態實例一直會持有該Activity的引用,致使Activity的內存資源不能正常回收。正確的作法爲:

將該內部類設爲靜態內部類或將該內部類抽取出來封裝成一個單例,若是須要使用Context,請使用ApplicationContext 。

3、Handler形成的內存泄漏

Handler的使用形成的內存泄漏問題應該說最爲常見了,平時在處理網絡任務或者封裝一些請求回調等api都應該會藉助Handler來處理,對於Handler的使用代碼編寫一不規範即有可能形成內存泄漏,以下示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public  class  MainActivity  extends  AppCompatActivity {
     private  Handler mHandler =  new  Handler() {
         @Override
         public  void  handleMessage(Message msg) {
             //...
         }
     };
     @Override
     protected  void  onCreate(Bundle savedInstanceState) {
         super .onCreate(savedInstanceState);
         setContentView(R.layout.activity_main);
         loadData();
     }
     private  void  loadData(){
         //...request
         Message message = Message.obtain();
         mHandler.sendMessage(message);
     }
}

這種建立Handler的方式會形成內存泄漏,因爲mHandler是Handler的非靜態匿名內部類的實例,因此它持有外部類Activity的引用,咱們知道消息隊列是在一個Looper線程中不斷輪詢處理消息,那麼當這個Activity退出時消息隊列中還有未處理的消息或者正在處理消息,而消息隊列中的Message持有mHandler實例的引用,mHandler又持有Activity的引用,因此致使該Activity的內存資源沒法及時回收,引起內存泄漏,因此另一種作法爲:

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
26
27
28
29
30
31
public  class  MainActivity  extends  AppCompatActivity {
     private  MyHandler mHandler =  new  MyHandler( this );
     private  TextView mTextView ;
     private  static  class  MyHandler  extends  Handler {
         private  WeakReference<Context> reference;
         public  MyHandler(Context context) {
             reference =  new  WeakReference<>(context);
         }
         @Override
         public  void  handleMessage(Message msg) {
             MainActivity activity = (MainActivity) reference.get();
             if (activity !=  null ){
                 activity.mTextView.setText( "" );
             }
         }
     }
  
     @Override
     protected  void  onCreate(Bundle savedInstanceState) {
         super .onCreate(savedInstanceState);
         setContentView(R.layout.activity_main);
         mTextView = (TextView)findViewById(R.id.textview);
         loadData();
     }
  
     private  void  loadData() {
         //...request
         Message message = Message.obtain();
         mHandler.sendMessage(message);
     }
}

建立一個靜態Handler內部類,而後對Handler持有的對象使用弱引用,這樣在回收時也能夠回收Handler持有的對象,這樣雖然避免了Activity泄漏,不過Looper線程的消息隊列中仍是可能會有待處理的消息,因此咱們在Activity的Destroy時或者Stop時應該移除消息隊列中的消息,更準確的作法以下:

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
26
27
28
29
30
31
32
33
34
35
36
37
public  class  MainActivity  extends  AppCompatActivity {
     private  MyHandler mHandler =  new  MyHandler( this );
     private  TextView mTextView ;
     private  static  class  MyHandler  extends  Handler {
         private  WeakReference<Context> reference;
         public  MyHandler(Context context) {
             reference =  new  WeakReference<>(context);
         }
         @Override
         public  void  handleMessage(Message msg) {
             MainActivity activity = (MainActivity) reference.get();
             if (activity !=  null ){
                 activity.mTextView.setText( "" );
             }
         }
     }
  
     @Override
     protected  void  onCreate(Bundle savedInstanceState) {
         super .onCreate(savedInstanceState);
         setContentView(R.layout.activity_main);
         mTextView = (TextView)findViewById(R.id.textview);
         loadData();
     }
  
     private  void  loadData() {
         //...request
         Message message = Message.obtain();
         mHandler.sendMessage(message);
     }
  
     @Override
     protected  void  onDestroy() {
         super .onDestroy();
         mHandler.removeCallbacksAndMessages( null );
     }
}

使用mHandler.removeCallbacksAndMessages(null);是移除消息隊列中全部消息和全部的Runnable。固然也可使用mHandler.removeCallbacks();或mHandler.removeMessages();來移除指定的Runnable和Message。

4、線程形成的內存泄漏

對於線程形成的內存泄漏,也是平時比較常見的,以下這兩個示例可能每一個人都這樣寫過:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//——————test1
         new  AsyncTask<Void, Void, Void>() {
             @Override
             protected  Void doInBackground(Void... params) {
                 SystemClock.sleep( 10000 );
                 return  null ;
             }
         }.execute();
//——————test2
         new  Thread( new  Runnable() {
             @Override
             public  void  run() {
                 SystemClock.sleep( 10000 );
             }
         }).start();

上面的異步任務和Runnable都是一個匿名內部類,所以它們對當前Activity都有一個隱式引用。若是Activity在銷燬以前,任務還未完成, 那麼將致使Activity的內存資源沒法回收,形成內存泄漏。正確的作法仍是使用靜態內部類的方式,以下:

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
26
27
28
29
30
31
static  class  MyAsyncTask  extends  AsyncTask<Void, Void, Void> {
         private  WeakReference<Context> weakReference;
  
         public  MyAsyncTask(Context context) {
             weakReference =  new  WeakReference<>(context);
         }
  
         @Override
         protected  Void doInBackground(Void... params) {
             SystemClock.sleep( 10000 );
             return  null ;
         }
  
         @Override
         protected  void  onPostExecute(Void aVoid) {
             super .onPostExecute(aVoid);
             MainActivity activity = (MainActivity) weakReference.get();
             if  (activity !=  null ) {
                 //...
             }
         }
     }
     static  class  MyRunnable  implements  Runnable{
         @Override
         public  void  run() {
             SystemClock.sleep( 10000 );
         }
     }
//——————
     new  Thread( new  MyRunnable()).start();
     new  MyAsyncTask( this ).execute();

這樣就避免了Activity的內存資源泄漏,固然在Activity銷燬時候也應該取消相應的任務AsyncTask::cancel(),避免任務在後臺執行浪費資源。

5、資源未關閉形成的內存泄漏

對於使用了BraodcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap等資源的使用,應該在Activity銷燬時及時關閉或者註銷,不然這些資源將不會被回收,形成內存泄漏。

以上就是android編程中,常見的5大內存泄漏問題及相應的解決辦法,若是你們在編程中遇到了上述泄漏問題,不妨能夠試試對應的方法。若是你們還有什麼疑問,能夠去「學習問答」版塊直接提出。

相關文章
相關標籤/搜索