Android
स्म्रति से रिसाव
खोज…
आम स्मृति लीक और उन्हें कैसे ठीक करें
1. अपने संदर्भों को ठीक करें:
उपयुक्त संदर्भ का उपयोग करने का प्रयास करें: उदाहरण के लिए चूंकि टोस्ट को केवल एक के बजाय कई गतिविधियों में देखा जा सकता है, टोस्ट के लिए getApplicationContext()
उपयोग करें, और चूंकि सेवाएं चालू रह सकती हैं, हालांकि एक गतिविधि के साथ एक सेवा शुरू हो गई है:
Intent myService = new Intent(getApplicationContext(), MyService.class);
इस तालिका का उपयोग किस संदर्भ के लिए एक त्वरित मार्गदर्शिका के रूप में करें:
मूल लेख यहाँ संदर्भ पर ।
2. प्रसंग का स्थिर संदर्भ
एक गंभीर मेमोरी लीक गलती View
को स्थिर संदर्भ रख रही है। हर View
के लिए एक आंतरिक संदर्भ है Context
। जिसका मतलब है कि एक पुरानी गतिविधि जिसके पूरे दृश्य के साथ पदानुक्रम कचरा एकत्र नहीं किया जाएगा, जब तक कि ऐप समाप्त नहीं हो जाता। स्क्रीन को घुमाते समय आपके पास आपका ऐप दो बार मेमोरी में होगा।
सुनिश्चित करें कि दृश्य, संदर्भ या उनके किसी वंशज का कोई स्थिर संदर्भ नहीं है।
3. जांचें कि आप वास्तव में अपनी सेवाएं समाप्त कर रहे हैं।
उदाहरण के लिए, मेरा एक इरादा सेवा है जो Google स्थान सेवा API का उपयोग करता है। और मैं googleApiClient.disconnect();
कॉल करना भूल गया googleApiClient.disconnect();
:
//Disconnect from API onDestroy()
if (googleApiClient.isConnected()) {
LocationServices.FusedLocationApi.removeLocationUpdates(googleApiClient, GoogleLocationService.this);
googleApiClient.disconnect();
}
4. जाँच छवि और बिटमैप उपयोग:
यदि आप स्क्वायर की लाइब्रेरी पिकासो का उपयोग कर रहे हैं, तो मैंने पाया कि मैं .fit()
का उपयोग करके मेमोरी को लीक नहीं कर रहा था, जिसने औसतन मेरी मेमोरी पदचिह्न को 50 एमबी से कम करके औसतन 19 एमबी से कम कर दिया था:
Picasso.with(ActivityExample.this) //Activity context
.load(object.getImageUrl())
.fit() //This avoided the OutOfMemoryError
.centerCrop() //makes image to not stretch
.into(imageView);
5. यदि आप प्रसारण रिसीवर का उपयोग कर रहे हैं तो उन्हें अनरजिस्टर्ड करें।
6. यदि आप java.util.Observer
(ऑब्जर्वर पैटर्न) का उपयोग कर रहे हैं:
deleteObserver(observer);
का उपयोग करना सुनिश्चित करें deleteObserver(observer);
AsyncTask के साथ गतिविधियों को लीक करने से बचें
सावधानी का एक शब्द : AsyncTask में यहाँ वर्णित मेमोरी लीक के अलावा कई गेटा हैं। तो इस एपीआई के साथ सावधान रहें, या यदि आप पूरी तरह से निहितार्थ नहीं समझते हैं तो इसे पूरी तरह से बचें। कई विकल्प हैं (थ्रेड, EventBus, RxAndroid, आदि)।
AsyncTask
साथ एक सामान्य गलती मेजबान Activity
(या Fragment
) के लिए एक मजबूत संदर्भ पर कब्जा करना है:
class MyActivity extends Activity {
private AsyncTask<Void, Void, Void> myTask = new AsyncTask<Void, Void, Void>() {
// Don't do this! Inner classes implicitly keep a pointer to their
// parent, which in this case is the Activity!
}
}
यह एक समस्या है क्योंकि AsyncTask
आसानी से मूल Activity
को AsyncTask
कर सकता है, उदाहरण के लिए यदि कार्य चल रहा है तो कॉन्फ़िगरेशन परिवर्तन होता है।
ऐसा करने का सही तरीका यह है कि आप अपने कार्य को एक static
वर्ग बनाएं, जो माता-पिता को पकड़ नहीं पाता है, और मेजबान Activity
लिए एक कमजोर संदर्भ रखता है:
class MyActivity extends Activity {
static class MyTask extends AsyncTask<Void, Void, Void> {
// Weak references will still allow the Activity to be garbage-collected
private final WeakReference<MyActivity> weakActivity;
MyTask(MyActivity myActivity) {
this.weakActivity = new WeakReference<>(myActivity);
}
@Override
public Void doInBackground(Void... params) {
// do async stuff here
}
@Override
public void onPostExecute(Void result) {
// Re-acquire a strong reference to the activity, and verify
// that it still exists and is active.
MyActivity activity = weakActivity.get();
if (activity == null
|| activity.isFinishing()
|| activity.isDestroyed()) {
// activity is no longer valid, don't do anything!
return;
}
// The activity is still valid, do main-thread stuff here
}
}
}
गतिविधियों में अनाम कॉलबैक
हर बार जब आप एक अनाम वर्ग बनाते हैं, तो यह अपने मूल वर्ग के लिए एक अंतर्निहित संदर्भ रखता है। इसलिए जब आप लिखते हैं:
public class LeakyActivity extends Activity
{
...
foo.registerCallback(new BarCallback()
{
@Override
public void onBar()
{
// do something
}
});
}
आप वास्तव में फू को अपने LeakyActivity उदाहरण के लिए एक संदर्भ भेज रहे हैं। जब उपयोगकर्ता आपकी LeakyActivity से दूर हो जाता है, तो यह संदर्भ LeakyActivity के उदाहरण को एकत्रित होने से रोक सकता है। यह एक गंभीर लीक है क्योंकि गतिविधियां उनके पूरे दृश्य पदानुक्रम के संदर्भ में हैं और इसलिए स्मृति में बड़ी वस्तुएं हैं।
इस रिसाव से कैसे बचें:
आप निश्चित रूप से पूरी तरह से गतिविधियों में अनाम कॉलबैक का उपयोग करने से बच सकते हैं। आप गतिविधि जीवनचक्र के संबंध में अपनी सभी कॉलबैक को अपंजीकृत भी कर सकते हैं। इस तरह:
public class NonLeakyActivity extends Activity
{
private final BarCallback mBarCallback = new BarCallback()
{
@Override
public void onBar()
{
// do something
}
});
@Override
protected void onResume()
{
super.onResume();
foo.registerCallback(mBarCallback);
}
@Override
protected void onPause()
{
super.onPause();
foo.unregisterCallback(mBarCallback);
}
}
स्थिर कक्षाओं में गतिविधि का संदर्भ
अक्सर आप उपयोगिता कक्षाओं का उपयोग करने के लिए एंड्रॉइड के कुछ वर्गों को आसानी से लपेटना चाहेंगे। उन उपयोगिता वर्गों को अक्सर एंड्रॉइड ओएस या आपके ऐप्स के संसाधनों तक पहुंचने के लिए एक संदर्भ की आवश्यकता होती है। इसका एक सामान्य उदाहरण शेयर्डपिरिफेंस क्लास के लिए एक आवरण है। Android की साझा प्राथमिकताओं तक पहुँचने के लिए किसी को लिखना होगा:
context.getSharedPreferences(prefsName, mode);
और इसलिए निम्न वर्ग बनाने के लिए एक परीक्षा हो सकती है:
public class LeakySharedPrefsWrapper
{
private static Context sContext;
public static void init(Context context)
{
sContext = context;
}
public int getInt(String name,int defValue)
{
return sContext.getSharedPreferences("a name", Context.MODE_PRIVATE).getInt(name,defValue);
}
}
अब, यदि आप अपनी गतिविधि के संदर्भ में init()
कहते हैं, तो LeakySaredPrefsWrapper आपकी गतिविधि का एक संदर्भ बनाए रखेगा, इसे कचरा एकत्र होने से रोकता है।
कैसे बचें:
स्टेटिक हेल्पर फ़ंक्शन को कॉल करने पर, आप संदर्भ. context.getApplicationContext();
का उपयोग करके एप्लिकेशन संदर्भ में भेज सकते हैं context.getApplicationContext();
स्थैतिक सहायक फ़ंक्शन बनाते समय, आप दिए गए संदर्भ से एप्लिकेशन संदर्भ निकाल सकते हैं (एप्लिकेशन संदर्भ पर कॉलिंग getApplicationContext () कॉलिंग संदर्भ देता है)। तो हमारे रैपर को ठीक करना सरल है:
public static void init(Context context)
{
sContext = context.getApplicationContext();
}
यदि एप्लिकेशन संदर्भ आपके उपयोग के मामले के लिए उपयुक्त नहीं है, तो आप प्रत्येक उपयोगिता फ़ंक्शन में एक संदर्भ पैरामीटर शामिल कर सकते हैं, आपको इन संदर्भ मापदंडों के संदर्भ रखने से बचना चाहिए। इस मामले में समाधान ऐसा लगेगा:
public int getInt(Context context,String name,int defValue)
{
// do not keep a reference of context to avoid potential leaks.
return context.getSharedPreferences("a name", Context.MODE_PRIVATE).getInt(name,defValue);
}
LeakCanary लाइब्रेरी के साथ मेमोरी लीक का पता लगाएं
LeakCanary आपके डिबग बिल्ड में मेमोरी लीक का पता लगाने के लिए एक ओपन सोर्स जावा लाइब्रेरी है।
बस build.gradle
में निर्भरता जोड़ें:
dependencies {
debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5.1'
releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.1'
testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.1'
}
फिर अपने Application
वर्ग में:
public class ExampleApplication extends Application {
@Override public void onCreate() {
super.onCreate();
if (LeakCanary.isInAnalyzerProcess(this)) {
// This process is dedicated to LeakCanary for heap analysis.
// You should not init your app in this process.
return;
}
LeakCanary.install(this);
}
}
अब आपके डिबग बिल्ड में एक गतिविधि मेमोरी लीक का पता चलने पर LeakCanary स्वतः ही एक अधिसूचना दिखाएगा।
नोट: रिलीज़ कोड में leakcanary-android-no-op
निर्भरता में मौजूद दो खाली वर्गों के अलावा लीककैनरी का कोई संदर्भ नहीं होगा।
श्रोताओं के साथ गतिविधियों को लीक करने से बचें
यदि आप किसी गतिविधि में एक श्रोता को लागू करते हैं या बनाते हैं, तो हमेशा उस वस्तु के जीवनचक्र पर ध्यान दें, जिस पर श्रोता पंजीकृत है।
किसी ऐसे एप्लिकेशन पर विचार करें, जिसमें उपयोगकर्ता द्वारा लॉग इन या आउट होने पर रुचि रखने वाली कई गतिविधियाँ / टुकड़े हों। ऐसा करने के लिए एक के एक सिंगलटन उदाहरण के लिए होगा का एक तरीका यह UserController
इसी क्रम में करने के लिए सदस्यता जा सकती है, सूचना पाने के लिए जब उपयोगकर्ता की स्थिति में परिवर्तन:
public class UserController {
private static UserController instance;
private List<StateListener> listeners;
public static synchronized UserController getInstance() {
if (instance == null) {
instance = new UserController();
}
return instance;
}
private UserController() {
// Init
}
public void registerUserStateChangeListener(StateListener listener) {
listeners.add(listener);
}
public void logout() {
for (StateListener listener : listeners) {
listener.userLoggedOut();
}
}
public void login() {
for (StateListener listener : listeners) {
listener.userLoggedIn();
}
}
public interface StateListener {
void userLoggedIn();
void userLoggedOut();
}
}
फिर दो गतिविधियाँ होती हैं, SignInActivity
:
public class SignInActivity extends Activity implements UserController.StateListener{
UserController userController;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.userController = UserController.getInstance();
this.userController.registerUserStateChangeListener(this);
}
@Override
public void userLoggedIn() {
startMainActivity();
}
@Override
public void userLoggedOut() {
showLoginForm();
}
...
public void onLoginClicked(View v) {
userController.login();
}
}
और MainActivity
:
public class MainActivity extends Activity implements UserController.StateListener{
UserController userController;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.userController = UserController.getInstance();
this.userController.registerUserStateChangeListener(this);
}
@Override
public void userLoggedIn() {
showUserAccount();
}
@Override
public void userLoggedOut() {
finish();
}
...
public void onLogoutClicked(View v) {
userController.logout();
}
}
इस उदाहरण के साथ क्या होता है कि हर बार उपयोगकर्ता लॉग इन करता है और फिर लॉग आउट करता है, एक MainActivity
इंस्टेंस लीक हो जाता है। रिसाव इसलिए होता है क्योंकि UserController#listeners
की गतिविधि का एक संदर्भ है।
कृपया ध्यान दें: यहां तक कि अगर हम एक श्रोता के रूप में एक अनाम आंतरिक वर्ग का उपयोग करते हैं, तो गतिविधि अभी भी लीक होगी:
...
this.userController.registerUserStateChangeListener(new UserController.StateListener() {
@Override
public void userLoggedIn() {
showUserAccount();
}
@Override
public void userLoggedOut() {
finish();
}
});
...
गतिविधि अभी भी लीक होगी, क्योंकि अनाम आंतरिक वर्ग में बाहरी वर्ग (इस मामले में गतिविधि) का एक अंतर्निहित संदर्भ है। यही कारण है कि आंतरिक वर्ग से बाहरी कक्षा में उदाहरण के तरीकों को कॉल करना संभव है। वास्तव में, एकमात्र प्रकार की आंतरिक कक्षाएं जिनके पास बाहरी वर्ग का संदर्भ नहीं है वे स्थिर आंतरिक वर्ग हैं।
संक्षेप में, गैर-स्थिर आंतरिक वर्गों के सभी उदाहरणों ने बाहरी वर्ग के उदाहरण के लिए एक अंतर्निहित संदर्भ प्रदान किया है जिसने उन्हें बनाया है।
इसे हल करने के लिए दो मुख्य दृष्टिकोण हैं, या तो UserController#listeners
से एक श्रोता को हटाने के लिए एक विधि जोड़कर या UserController#listeners
के संदर्भ को रखने के लिए एक WeakReference
संदर्भ का उपयोग कर।
वैकल्पिक 1: श्रोताओं को हटाना
आइए हम एक नई विधि को removeUserStateChangeListener(StateListener listener)
करें
public class UserController {
...
public void registerUserStateChangeListener(StateListener listener) {
listeners.add(listener);
}
public void removeUserStateChangeListener(StateListener listener) {
listeners.remove(listener);
}
...
}
तो फिर हम गतिविधि के onDestroy
विधि में इस विधि को कहते हैं:
public class MainActivity extends Activity implements UserController.StateListener{
...
@Override
protected void onDestroy() {
super.onDestroy();
userController.removeUserStateChangeListener(this);
}
}
जब उपयोगकर्ता लॉग इन और आउट करते हैं तो इस संशोधन के साथ MainActivity
की MainActivity
अब लीक नहीं हुई हैं। हालाँकि, यदि दस्तावेज़ स्पष्ट नहीं है, तो संभावना है कि अगला डेवलपर जो UserController
का उपयोग करना शुरू करता है, वह यह याद कर सकता है कि गतिविधि के नष्ट होने पर श्रोता को अपंजीकृत करना आवश्यक है, जो हमें इस प्रकार के लीक से बचने की दूसरी विधि की ओर ले जाता है।
वैकल्पिक 2: कमजोर संदर्भों का उपयोग करना
सबसे पहले, आइए हम यह बताकर शुरू करें कि एक कमजोर संदर्भ क्या है। एक कमजोर संदर्भ, जैसा कि नाम से पता चलता है, एक वस्तु के लिए एक कमजोर संदर्भ रखता है। एक सामान्य उदाहरण क्षेत्र की तुलना में, जो एक मजबूत संदर्भ है, एक कमजोर संदर्भ वस्तुओं को हटाने से कचरा कलेक्टर, जीसी को रोक नहीं पाता है। इस ऊपर के उदाहरण में अनुमति होगी MainActivity
कचरा-एकत्र होने के बाद यह नष्ट कर दिया गया है, तो होने के लिए UserController
इस्तेमाल किया WeakReference
संदर्भ के लिए श्रोताओं।
संक्षेप में, एक कमजोर संदर्भ जीसी को बता रहा है कि यदि किसी और के पास इस ऑब्जेक्ट का एक मजबूत संदर्भ नहीं है, तो आगे बढ़ें और इसे हटा दें।
हमें यह सुनने वालों का ध्यान रखने के लिए WeakReference
की एक सूची का उपयोग करने के लिए UserController
संशोधित करें:
public class UserController {
...
private List<WeakReference<StateListener>> listeners;
...
public void registerUserStateChangeListener(StateListener listener) {
listeners.add(new WeakReference<>(listener));
}
public void removeUserStateChangeListener(StateListener listenerToRemove) {
WeakReference referencesToRemove = null;
for (WeakReference<StateListener> listenerRef : listeners) {
StateListener listener = listenerRef.get();
if (listener != null && listener == listenerToRemove) {
referencesToRemove = listenerRef;
break;
}
}
listeners.remove(referencesToRemove);
}
public void logout() {
List referencesToRemove = new LinkedList();
for (WeakReference<StateListener> listenerRef : listeners) {
StateListener listener = listenerRef.get();
if (listener != null) {
listener.userLoggedOut();
} else {
referencesToRemove.add(listenerRef);
}
}
}
public void login() {
List referencesToRemove = new LinkedList();
for (WeakReference<StateListener> listenerRef : listeners) {
StateListener listener = listenerRef.get();
if (listener != null) {
listener.userLoggedIn();
} else {
referencesToRemove.add(listenerRef);
}
}
}
...
}
इस संशोधन के साथ यह बात मायने नहीं रखती कि श्रोता हटाए गए हैं या नहीं, क्योंकि UserController
किसी भी श्रोता के लिए कोई मजबूत संदर्भ नहीं रखता है। हालाँकि, हर बार यह बॉयलरप्लेट कोड लिखना बोझिल होता है। इसलिए, हमें WeakCollection
नामक एक सामान्य वर्ग WeakCollection
:
public class WeakCollection<T> {
private LinkedList<WeakReference<T>> list;
public WeakCollection() {
this.list = new LinkedList<>();
}
public void put(T item){
//Make sure that we don't re add an item if we already have the reference.
List<T> currentList = get();
for(T oldItem : currentList){
if(item == oldItem){
return;
}
}
list.add(new WeakReference<T>(item));
}
public List<T> get() {
List<T> ret = new ArrayList<>(list.size());
List<WeakReference<T>> itemsToRemove = new LinkedList<>();
for (WeakReference<T> ref : list) {
T item = ref.get();
if (item == null) {
itemsToRemove.add(ref);
} else {
ret.add(item);
}
}
for (WeakReference ref : itemsToRemove) {
this.list.remove(ref);
}
return ret;
}
public void remove(T listener) {
WeakReference<T> refToRemove = null;
for (WeakReference<T> ref : list) {
T item = ref.get();
if (item == listener) {
refToRemove = ref;
}
}
if(refToRemove != null){
list.remove(refToRemove);
}
}
}
अब हम इसके बजाय WeakCollection<T>
का उपयोग करने के लिए UserController
को फिर से लिखते हैं:
public class UserController {
...
private WeakCollection<StateListener> listenerRefs;
...
public void registerUserStateChangeListener(StateListener listener) {
listenerRefs.put(listener);
}
public void removeUserStateChangeListener(StateListener listenerToRemove) {
listenerRefs.remove(listenerToRemove);
}
public void logout() {
for (StateListener listener : listenerRefs.get()) {
listener.userLoggedOut();
}
}
public void login() {
for (StateListener listener : listenerRefs.get()) {
listener.userLoggedIn();
}
}
...
}
जैसा कि ऊपर दिए गए कोड उदाहरण में दिखाया गया है, WeakCollection<T>
सामान्य सूची के बजाय WeakReference
का उपयोग करने के लिए आवश्यक बॉयलरप्लेट कोड को हटा देता है। यह सब बंद करने के लिए: यदि UserController#removeUserStateChangeListener(StateListener)
से कोई कॉल छूट जाती है, तो श्रोता, और सभी ऑब्जेक्ट जिसका वह संदर्भ ले रहा है, लीक नहीं होगा।
बेनामी क्लास, हैंडलर, टाइमर टास्क, थ्रेड के साथ मेमोरी लीक से बचें
Android में, प्रत्येक डेवलपर किसी प्रोजेक्ट में कम से कम एक बार Anonymous Class
(रननेबल) का उपयोग करता है। किसी भी Anonymous Class
पास अपने माता-पिता (गतिविधि) का संदर्भ होता है। यदि हम लंबे समय तक चलने वाला कार्य करते हैं, तो कार्य समाप्त होने तक मूल गतिविधि नष्ट नहीं होगी।
उदाहरण हैंडलर और बेनामी Runnable
वर्ग का उपयोग करता है। Runnable
समाप्त होने से पहले जब हम गतिविधि छोड़ते हैं तो मेमोरी लीक हो जाएगी।
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
// do abc long 5s or so
}
}, 10000); // run "do abc" after 10s. It same as timer, thread...
हम इसे कैसे हल करेंगे?
- किसी भी लंबे समय तक
Anonymous Class
साथ काम न करें या हमें इसके लिए एकStatic class
जरूरत है और इसमेंWeakReference
(जैसे गतिविधि, दृश्य ...) पास करें।Anonymous Class
साथThread
समान है। - गतिविधि नष्ट होने पर
Handler
,Timer
रद्द करें।