個人信息泄露處理方法:csdn泄露數據

對於眾多 Android 程序員而言,在需求與應用性能之間,主要精力都會放到新需求的開發。隨着項目複雜度的增加,應用性能越來越低,出現各種問題。程序員們奔波於各種“救火現場”,疲於奔命。本文作者 Aritra Roy 分享了自己在 Android 應用程序開發過程中所遇以及思考,針對內存泄漏提煉出一套可以應用於開發中的方法論。也許會讓你的開發效率事半功倍。

火速收藏!Android 開發者必會的內存泄漏指南

作者 | Aritra Roy,Android 開發者

譯者 | 羅昭成,責編 | 唐小引

封圖 | CSDN 付費下載自東方 IC

出品 | CSDN(ID:CSDNnews)

以下為譯文:

我們都知道,寫一個 Android 的應用很容易,但是要寫一個高性能的應用可就不容易了。以我的個人經驗來說,在 App 的開發過程中,主要的精力都會放在新功能、新模塊、新組件的開發上。

開發過程中,看得見的 UI 比看不見的性能更能吸引我們的目光。所以我強制自己將“優化應用程序(如內存泄漏)”的優先級提高,並養成習慣。

長期以來,不關注性能,帶來了很多的技術債。經過一年多的努力調整, 比起一年前,我有很多的心得體會。

對於很多開發者來說,內存泄漏都是一個老大難的問題。關於處理內存泄漏,你有可能會覺得太難,又或是太費時,又或者是覺得完全沒有意義。但我要告訴你的是,事實並非如此。當你開始處理這些問題的時候,你會發現,這感覺超級棒。

在本篇文章中,我會以儘可能簡單的方式講解這些問題,即使你是一個初學者,也可以學習到如何構建一個高質量、高性能的應用。

火速收藏!Android 開發者必會的內存泄漏指南

垃圾回收

Java 是一個非常強大的語言。在 Android 中,我們幾乎不會像 C / C++ 那樣,手動分配和釋放內存。因為 Java 會自動清理內存。

讓我們來思考一個問題,如果 Java 內建的垃圾回收系統可以在我們不需要的時候自動回收內存,那我們為什麼還需要關心內存呢?是因為垃圾回收機制不夠完善嗎?

當然不是,Java 垃圾回收機制當然是完善的。垃圾回收機制是可以正常工作,但是,如果我們的應用程序出現 Bug, 導致垃圾回收器不能正常檢查出不需要的內存塊,就會導致問題。

總體來說,是我們自己的代碼錯誤導致垃圾回收不可用。不可否認,垃圾回收機制是 Java 最偉大的設計之一。

火速收藏!Android 開發者必會的內存泄漏指南

關於垃圾回收器

在處理內存問題之前,你需要了解垃圾回收器的工作原理。它的概念非常簡單,但在它背後,有着極其複雜的邏輯。但是你也別擔心,我們也只關心一些簡單的概念。

火速收藏!Android 開發者必會的內存泄漏指南

如圖所示,Android 或者 Java 應用程序都有一個起點,從對象的初始化,並且調用方法。我們可以認為,這個點就是圖中的 “GC Roots”。有一些對象引用被 GC Roots 直接持有,剩下的則由它們自己創建並持有。

如此,整個引用鏈構成了內存樹。垃圾回收器從 GC roots 開始,直接或間接的鏈接着其它的對象。當整個遍歷結束,還有一些不能被訪問到的對象,就是變成了垃圾,這些對象就會被垃圾回收器回收。

火速收藏!Android 開發者必會的內存泄漏指南

內存泄漏

到現在,你已經知道了垃圾回收的概念,也知道了垃圾回收在 Android 中是如何管理內存的。下面,我們將深入研究一下內存泄漏。

簡單來說,內存泄漏是指你的對象已經使用結束,但是它卻不能被釋放掉。每個對象在完成它自己的生命周期過後,都需要從內存中清理出來。但是如果一個對象被另一個對象直接或間接的持有,垃圾回收器不能檢查出它已經使用結束。朋友們,這樣子就導致了內存泄漏。

值得慶幸的是,我們並不需要太擔心所有的內存泄漏,因為並不是所有的內存泄漏都是有害的。有一些內存泄漏是無關痛癢(只泄漏幾 KB 的內存),並且,在 Android Framwork 層也會有一些內存泄漏,但是你並不需要去修復,因為它們對 App 的性能影響微乎其微,你可以忽略。

但是有一些會引起 App 崩潰, 或者嚴重卡頓。這些都是需要你時刻注意的。

火速收藏!Android 開發者必會的內存泄漏指南

為什麼要解決內存泄漏?

沒有人會想使用一個又慢又占內存的應用。如果使用一段時間就會崩潰,你的用戶也會“崩潰”掉,如果長時間出現這樣子的問題,你的用戶會毫不猶豫的卸載掉你的應用,並且再也不會使用它。

火速收藏!Android 開發者必會的內存泄漏指南

如果你的應用中存在內存泄漏,垃圾回收器不能回收不使用的內存,隨着用戶使用時間的增長,內存的佔用會越來越多。如此下去,當系統不能在給它分配更多內存的時候,就會導致 OutOfMemoryError, 然後應用程序會崩潰掉。

垃圾回收有利有弊,垃圾回收是一龐大的系統,在應用中,儘可能少的讓垃圾回收器運行,這樣對應用體驗會更好。

隨着你的應用使用的堆內存逐漸增加,Short GC 就會觸發,來保證立即清理無用對象。現在這些快速清理內存的 GC 運行在不同的線程中,這些 GC 不會導致你的應用變慢。

但是如果你的應用中存在嚴重的內存泄漏,Short GC 沒有辦法回收內存,並且佔用內存持續增加,這將會導致 Larger GC 被觸發。它會將整個應用程序掛起,阻塞大概 50~100ms,這會導致應用程序變慢並且有可能不能使用。

修復內存泄漏,減少對 App 的影響,給用戶提供更好的體驗。

火速收藏!Android 開發者必會的內存泄漏指南

如何發現內存泄漏?

現在,你已經認識到,你需要修復隱藏在你 App 中的內存泄漏。但是,我們如何才能找到它們呢?

Android Studio 為我們提供了一個非常強大的工具:Monitors。

通過它,你能看到網絡、CPU、GPU、內存的使用情況。

火速收藏!Android 開發者必會的內存泄漏指南

在調試運行 App 的時候,要密切關注內存監視器。內存泄漏的第一個現象就是,在使用的過程中,內存一直增加,不能減少,即使你把 APP 退到後台也不能釋放。內存分配監視器能夠清楚的看到不同對象所佔用的內存,可以清楚的知道哪個對象佔用內存較多,需要處理。

但是,它本身還不夠,它需要你指定時間,然後轉存出對應的內存堆。這是一個很無趣的工作。

幸運的是,我們現在已經有更好的方式來實現。LeakCanary, 一個和 App 一起運行的庫,它會在內存泄漏的時候,轉存出內存信息,然後給我們發送一個通知並給我們一個有用的棧信息。

火速收藏!Android 開發者必會的內存泄漏指南

常見的內存泄漏

從我的經驗來看,有很多相似且經常出現內存泄漏的問題,你在你每天的開發中,都有可能會遇到它們。一但你清楚了它們發生的時間、地點、原因 ,你就可以很輕鬆的修復它們。

  • 未取消的 Listener

很多時候,你在 Activity/Fragment 中註冊了一個 Listener, 但是忘記取消註冊了。如果你的運氣不好,它很可能會引起一個嚴重的內存泄漏問題。一般來說,這些 Listener 的 註冊與取消註冊是同步出現的,在你使用的時候需要註冊,在不使用的時候需要取消註冊。

舉個例子,當我們的應用程序需要獲取定位的時候,需要使用 LocationManager,你會從系統服務中拿到它,並且給其設置一個地理位置更新的回調:

private void registerLocationUpdats {
mManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);mManager.requestLocationUpdates(LocationManager.GPS_PROVIDER,TimeUnit.MINUTES.toMillis(1),
100,
this);
}

在代碼中,可以看出來,使用了 Activity 自己來實現了地理位置更新的回調。LocationManager 會持有這個回調的引用。當你退出了這個頁面,Android 系統會調用 onDestory ,但是垃圾回收器並不能清理掉它,因為 LocationManager 持有它的強引用。

當然,解決方案也很簡單,就是在 onDestory 方法中,取消註冊就可以了。

@Override
public voidonDestroy {
super.onDestroy;
if (mManager != ) {
mManager.removeUpdates(this);
}}
  • 內部類

內部類在 Java 和 Android 開發中經常用到,非常簡單,但是如果使用不當,也會造成嚴重的內存泄漏。讓我們先來看一個簡單的例子:

public class BadActivity extends Activity {
private TextView mMessageView;
@Override
protected voidonCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.layout_bad_activity);mMessageView = (TextView) findViewById(R.id.messageView);new LongRunningTask.execute;
}private class LongRunningTask extends AsyncTask<Void, Void, String> {
@Override
protected String doInBackground(Void... params) {
// 做一些耗時操作
return "Am finally done!";
}
@Override
protected voidonPostExecute(String result) {
mMessageView.setText(result);
}
}
}

這是一個很簡單的 Activity 頁面,在頁面啟動的時候,在後台啟動了一個耗時的任務(比如說,複雜的數據庫查詢或者是很慢的網絡)。等到任務執行結束,把拿到的結果顯示到頁面上。看起來,這樣做並沒有問題。事實上,非靜態的內部類會隱式的持有外部類的引用(在這裡,就是 Activity)。如果在耗時任務執行完之前,你旋轉屏幕或者退出這個頁面,垃圾回收器就不能從內存中清理掉 Activity 的實例。這個簡單的問題會導致很嚴重的內存泄漏問題。

當然,解決方案也非常地簡單,如下:

public class GoodActivity extends Activity {
private AsyncTask mLongRunningTask;
private TextView mMessageView;
@Override
protected voidonCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.layout_good_activity);mMessageView = (TextView) findViewById(R.id.messageView);mLongRunningTask = new LongRunningTask(mMessageView).execute;
}@Override
protected voidonDestroy {
super.onDestroy;
mLongRunningTask.cancel(true);
}private static class LongRunningTask extends AsyncTask<Void, Void, String> {
private final WeakReference<TextView> messageViewReference;
publicLongRunningTask(TextView messageView) {
this.messageViewReference = new WeakReference<>(messageView);
}@Override
protected String doInBackground(Void... params) {
String message = ;if (!isCancelled) {
message = "I am finally done!";
}return message;
}@Override
protected voidonPostExecute(String result) {
TextView view = messageViewReference.get;if (view != ) {
view.setText(result);}}}}

正如你看到的代碼,首先我將非靜態內部類改成了靜態內部類,這樣它就不會持有外部類的引用了。當然,使用靜態的內部類,非靜態的變量就不能訪問了。所以我們需要將 TextView 通過構造方法把它傳過去。

在這裡,我強烈推薦使用 WeakReference ,它能更好的避免引起內存泄漏。你應該去學習 Java 中關於不同引用類型的知識:

http://javarevisited.blogspot.in/2014/03/difference-between-weakreference-vs-softreference-phantom-strong-reference-java.html

  • 匿名內部類

匿名內部類也是在開發過程中經常使用到的一個東西,它的定義和使用都非常的簡潔。但以我的經驗來看,匿名內部類造成了大量的內存泄漏的問題。

匿名內部類與非靜態內部類相似,造成內部類的原因也和上面說的一樣。你有可能在好多地方都使用了匿名內部類,如果使用不當,會嚴重影響 App 的性能。

public class MoviesActivity extends Activity {
private TextView mNoOfMoviesThisWeek;
@Override
protected voidonCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.layout_movies_activity);mNoOfMoviesThisWeek = (TextView) findViewById(R.id.no_of_movies_text_view);MoviesRepository repository = ((MoviesApp) getApplication).getRepository;repository.getMoviesThisWeek.enqueue(new Callback<List<Movie>> {
@Override
public voidonResponse(Call<List<Movie>> call,
Response<List<Movie>> response) {int numberOfMovies = response.body.size;
mNoOfMoviesThisWeek.setText("No of movies this week: " + String.valueOf(numberOfMovies));
}@Override
public voidonFailure(Call<List<Movie>> call, Throwable t) {
// Oops.
}
});
}
}

上面的例子中,我使用一個常用的網絡庫 Retrofit 發送了一個網絡請求,然後在 TextView 中顯示返回的結果。很明顯,那個 Callback 對象持有 Activity 的引用。如果現在網絡很慢,在網絡響應回來之前,頁面旋轉或者關閉,就會導致 Activity 泄漏。

我強烈建議,在需要的時候,盡量使用靜態的內部類,而非匿名內部類。當然,我的意思不是不在使用匿名內部類,如果你需要使用匿名內部類,你需要注意引起內存泄漏的問題,保證不會出現問題。

  • Bitmaps

在應用中,你看到的所有圖片都是 Bitmap 對象,包含了所有的像素數據。現在這些 Bitmap 數據非常的大,一個處理不好,就會引起 OOM, 造成 APP 崩潰。在 APP 中使用的圖片資源生成的 Bitmap 會由系統進行管理,但是如果你需要自己處理 Bitmap ,要記住,使用完過後要調用 bitmap.recycle 來釋放資源。

原創文章,作者:投稿專員,如若轉載,請註明出處:https://www.506064.com/zh-hant/n/220537.html

(0)
打賞 微信掃一掃 微信掃一掃 支付寶掃一掃 支付寶掃一掃
投稿專員的頭像投稿專員
上一篇 2024-12-09 11:27
下一篇 2024-12-09 11:27

相關推薦

發表回復

登錄後才能評論