1.内存泄漏的状况及原因
1.1.利用LeakCanary查看内存泄漏的状况
1.2.内存泄漏怎么产生的呢?
InputMethodManager.mServicedView持有一个最后聚焦View的引用
直到另外的一个View聚焦后才会释放当前的View
当发生GC是mServicedView(GCRoot)持有的View的引用不会被回收
导致了内存泄漏
因为这个问题出现的频率比较高,LeakCanary上经常有这个泄漏的弹窗。
2.新建一个自定义类LMMleaks
2.1.自定义类LMMleaks介绍
这个类主要用于解决内存泄漏而建立的。
所以当前只是某一个原因的内存泄漏,所以今后很多的时候,往里面添加函数即可。
我自认为是这样。
2.2.源代码
package com.gcssloop.diycode.hackpatch; import android.app.Activity;
import android.app.Application;
import android.content.Context;
import android.content.ContextWrapper;
import android.os.Build;
import android.os.Bundle;
import android.os.Looper;
import android.os.MessageQueue;
import android.support.annotation.RequiresApi;
import android.util.Log;
import android.view.View;
import android.view.ViewTreeObserver;
import android.view.inputmethod.InputMethodManager;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; import static android.content.Context.INPUT_METHOD_SERVICE;
import static android.os.Build.VERSION.SDK_INT;
import static android.os.Build.VERSION_CODES.KITKAT; public class IMMLeaks { static class ReferenceCleaner implements
MessageQueue.IdleHandler, View.OnAttachStateChangeListener, ViewTreeObserver.OnGlobalFocusChangeListener { private final InputMethodManager inputMethodManager;
private final Field mHField;
private final Field mServedViewField;
private final Method finishInputLockedMethod; ReferenceCleaner(InputMethodManager inputMethodManager, Field mHField, Field mServedViewField,
Method finishInputLockedMethod) {
this.inputMethodManager = inputMethodManager;
this.mHField = mHField;
this.mServedViewField = mServedViewField;
this.finishInputLockedMethod = finishInputLockedMethod;
} @Override public void onGlobalFocusChanged(View oldFocus, View newFocus) {
if (newFocus == null) {
return;
}
if (oldFocus != null) {
oldFocus.removeOnAttachStateChangeListener(this);
}
Looper.myQueue().removeIdleHandler(this);
newFocus.addOnAttachStateChangeListener(this);
} @Override public void onViewAttachedToWindow(View v) {
} @Override public void onViewDetachedFromWindow(View v) {
v.removeOnAttachStateChangeListener(this);
Looper.myQueue().removeIdleHandler(this);
Looper.myQueue().addIdleHandler(this);
} @RequiresApi(api = Build.VERSION_CODES.KITKAT)
@Override public boolean queueIdle() {
clearInputMethodManagerLeak();
return false;
} @RequiresApi(api = Build.VERSION_CODES.KITKAT)
private void clearInputMethodManagerLeak() {
try {
Object lock = mHField.get(inputMethodManager);
// This is highly dependent on the InputMethodManager implementation.
synchronized (lock) {
View servedView = (View) mServedViewField.get(inputMethodManager);
if (servedView != null) { boolean servedViewAttached = servedView.getWindowVisibility() != View.GONE; if (servedViewAttached) {
// The view held by the IMM was replaced without a global focus change. Let's make
// sure we get notified when that view detaches. // Avoid double registration.
servedView.removeOnAttachStateChangeListener(this);
servedView.addOnAttachStateChangeListener(this);
} else {
// servedView is not attached. InputMethodManager is being stupid!
Activity activity = extractActivity(servedView.getContext());
if (activity == null || activity.getWindow() == null) {
// Unlikely case. Let's finish the input anyways.
finishInputLockedMethod.invoke(inputMethodManager);
} else {
View decorView = activity.getWindow().peekDecorView();
boolean windowAttached = decorView.getWindowVisibility() != View.GONE;
if (!windowAttached) {
finishInputLockedMethod.invoke(inputMethodManager);
} else {
decorView.requestFocusFromTouch();
}
}
}
}
}
} catch (IllegalAccessException | InvocationTargetException unexpected) {
Log.e("IMMLeaks", "Unexpected reflection exception", unexpected);
}
} private Activity extractActivity(Context context) {
while (true) {
if (context instanceof Application) {
return null;
} else if (context instanceof Activity) {
return (Activity) context;
} else if (context instanceof ContextWrapper) {
Context baseContext = ((ContextWrapper) context).getBaseContext();
// Prevent Stack Overflow.
if (baseContext == context) {
return null;
}
context = baseContext;
} else {
return null;
}
}
}
} /**
* Fix for https://code.google.com/p/android/issues/detail?id=171190 .
*
* When a view that has focus gets detached, we wait for the main thread to be idle and then
* check if the InputMethodManager is leaking a view. If yes, we tell it that the decor view got
* focus, which is what happens if you press home and come back from recent apps. This replaces
* the reference to the detached view with a reference to the decor view.
*
* Should be called from {@link Activity#onCreate(android.os.Bundle)} )}.
*/
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
public static void fixFocusedViewLeak(Application application) { // Don't know about other versions yet.
if (SDK_INT < KITKAT || SDK_INT > 22) {
return;
} final InputMethodManager inputMethodManager =
(InputMethodManager) application.getSystemService(INPUT_METHOD_SERVICE); final Field mServedViewField;
final Field mHField;
final Method finishInputLockedMethod;
final Method focusInMethod;
try {
mServedViewField = InputMethodManager.class.getDeclaredField("mServedView");
mServedViewField.setAccessible(true);
mHField = InputMethodManager.class.getDeclaredField("mServedView");
mHField.setAccessible(true);
finishInputLockedMethod = InputMethodManager.class.getDeclaredMethod("finishInputLocked");
finishInputLockedMethod.setAccessible(true);
focusInMethod = InputMethodManager.class.getDeclaredMethod("focusIn", View.class);
focusInMethod.setAccessible(true);
} catch (NoSuchMethodException | NoSuchFieldException unexpected) {
Log.e("IMMLeaks", "Unexpected reflection exception", unexpected);
return;
} application.registerActivityLifecycleCallbacks(new LifecycleCallbacksAdapter() {
@Override public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
ReferenceCleaner cleaner =
new ReferenceCleaner(inputMethodManager, mHField, mServedViewField,
finishInputLockedMethod);
View rootView = activity.getWindow().getDecorView().getRootView();
ViewTreeObserver viewTreeObserver = rootView.getViewTreeObserver();
viewTreeObserver.addOnGlobalFocusChangeListener(cleaner);
}
});
}
}
2.3.内部类ReferenceCleaner
实现了MessageQueue.IdleHandler,View.OnAttachStateChangeListener,
ViewTreeObserver.OnGlobalFocusChangeListener
2.4.实现第一个抽象接口函数queueIdle
然后具体的clearInputMethodManagerLeak()函数为:
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
private void clearInputMethodManagerLeak() {
try {
Object lock = mHField.get(inputMethodManager);
// This is highly dependent on the InputMethodManager implementation.
synchronized (lock) {
View servedView = (View) mServedViewField.get(inputMethodManager);
if (servedView != null) { boolean servedViewAttached = servedView.getWindowVisibility() != View.GONE; if (servedViewAttached) {
// The view held by the IMM was replaced without a global focus change. Let's make
// sure we get notified when that view detaches. // Avoid double registration.
servedView.removeOnAttachStateChangeListener(this);
servedView.addOnAttachStateChangeListener(this);
} else {
// servedView is not attached. InputMethodManager is being stupid!
Activity activity = extractActivity(servedView.getContext());
if (activity == null || activity.getWindow() == null) {
// Unlikely case. Let's finish the input anyways.
finishInputLockedMethod.invoke(inputMethodManager);
} else {
View decorView = activity.getWindow().peekDecorView();
boolean windowAttached = decorView.getWindowVisibility() != View.GONE;
if (!windowAttached) {
finishInputLockedMethod.invoke(inputMethodManager);
} else {
decorView.requestFocusFromTouch();
}
}
}
}
}
} catch (IllegalAccessException | InvocationTargetException unexpected) {
Log.e("IMMLeaks", "Unexpected reflection exception", unexpected);
}
}
这里面还有一个函数,用来提取活动
2.5.实现第二个抽象接口函数View.OnAttachStateChangeListener
2.6.实现第三个抽象接口函数ViewTreeObserver.OnGlobalFocusChangeListener
2.7.似乎忘记了构造函数呢!
需要4个参数
inputMethodManager,mHField,mServedViewField,finishInputLockedMethod
2.8.以上都是内部类中的方法,然后有一个修复内存泄漏的方法
这里才是核心点。
这里调用了一个LifecycleCallbacksAdapter
这是一个继承Application.ActivityLifecycleCallbacks接口的一个方法
2.9.LifecycleCallbacksAdapter具体方法
这里面只是定义了声明周期中的方法
没有做任何具体的方法
意思就是全部为空函数
3.所有流程归纳
3.1.新建一个IMMLeaks类
里面有一个内部类+一个方法
package com.gcssloop.diycode.hackpatch; import android.app.Activity;
import android.app.Application;
import android.content.Context;
import android.content.ContextWrapper;
import android.os.Build;
import android.os.Bundle;
import android.os.Looper;
import android.os.MessageQueue;
import android.support.annotation.RequiresApi;
import android.util.Log;
import android.view.View;
import android.view.ViewTreeObserver;
import android.view.inputmethod.InputMethodManager;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; import static android.content.Context.INPUT_METHOD_SERVICE;
import static android.os.Build.VERSION.SDK_INT;
import static android.os.Build.VERSION_CODES.KITKAT; public class IMMLeaks { static class ReferenceCleaner implements
MessageQueue.IdleHandler,
View.OnAttachStateChangeListener,
ViewTreeObserver.OnGlobalFocusChangeListener { private final InputMethodManager inputMethodManager;
private final Field mHField;
private final Field mServedViewField;
private final Method finishInputLockedMethod; ReferenceCleaner(InputMethodManager inputMethodManager, Field mHField, Field mServedViewField,
Method finishInputLockedMethod) {
this.inputMethodManager = inputMethodManager;
this.mHField = mHField;
this.mServedViewField = mServedViewField;
this.finishInputLockedMethod = finishInputLockedMethod;
} @Override public void onGlobalFocusChanged(View oldFocus, View newFocus) {
if (newFocus == null) {
return;
}
if (oldFocus != null) {
oldFocus.removeOnAttachStateChangeListener(this);
}
Looper.myQueue().removeIdleHandler(this);
newFocus.addOnAttachStateChangeListener(this);
} @Override public void onViewAttachedToWindow(View v) {
} @Override public void onViewDetachedFromWindow(View v) {
v.removeOnAttachStateChangeListener(this);
Looper.myQueue().removeIdleHandler(this);
Looper.myQueue().addIdleHandler(this);
} @RequiresApi(api = Build.VERSION_CODES.KITKAT)
@Override public boolean queueIdle() {
clearInputMethodManagerLeak();
return false;
} @RequiresApi(api = Build.VERSION_CODES.KITKAT)
private void clearInputMethodManagerLeak() {
try {
Object lock = mHField.get(inputMethodManager);
// This is highly dependent on the InputMethodManager implementation.
synchronized (lock) {
View servedView = (View) mServedViewField.get(inputMethodManager);
if (servedView != null) { boolean servedViewAttached = servedView.getWindowVisibility() != View.GONE; if (servedViewAttached) {
// The view held by the IMM was replaced without a global focus change. Let's make
// sure we get notified when that view detaches. // Avoid double registration.
servedView.removeOnAttachStateChangeListener(this);
servedView.addOnAttachStateChangeListener(this);
} else {
// servedView is not attached. InputMethodManager is being stupid!
Activity activity = extractActivity(servedView.getContext());
if (activity == null || activity.getWindow() == null) {
// Unlikely case. Let's finish the input anyways.
finishInputLockedMethod.invoke(inputMethodManager);
} else {
View decorView = activity.getWindow().peekDecorView();
boolean windowAttached = decorView.getWindowVisibility() != View.GONE;
if (!windowAttached) {
finishInputLockedMethod.invoke(inputMethodManager);
} else {
decorView.requestFocusFromTouch();
}
}
}
}
}
} catch (IllegalAccessException | InvocationTargetException unexpected) {
Log.e("IMMLeaks", "Unexpected reflection exception", unexpected);
}
} private Activity extractActivity(Context context) {
while (true) {
if (context instanceof Application) {
return null;
} else if (context instanceof Activity) {
return (Activity) context;
} else if (context instanceof ContextWrapper) {
Context baseContext = ((ContextWrapper) context).getBaseContext();
// Prevent Stack Overflow.
if (baseContext == context) {
return null;
}
context = baseContext;
} else {
return null;
}
}
} } /**
* Fix for https://code.google.com/p/android/issues/detail?id=171190 .
*
* When a view that has focus gets detached, we wait for the main thread to be idle and then
* check if the InputMethodManager is leaking a view. If yes, we tell it that the decor view got
* focus, which is what happens if you press home and come back from recent apps. This replaces
* the reference to the detached view with a reference to the decor view.
*
* Should be called from {@link Activity#onCreate(android.os.Bundle)} )}.
*/
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
public static void fixFocusedViewLeak(Application application) { // Don't know about other versions yet.
if (SDK_INT < KITKAT || SDK_INT > 22) {
return;
} final InputMethodManager inputMethodManager =
(InputMethodManager) application.getSystemService(INPUT_METHOD_SERVICE); final Field mServedViewField;
final Field mHField;
final Method finishInputLockedMethod;
final Method focusInMethod;
try {
mServedViewField = InputMethodManager.class.getDeclaredField("mServedView");
mServedViewField.setAccessible(true);
mHField = InputMethodManager.class.getDeclaredField("mServedView");
mHField.setAccessible(true);
finishInputLockedMethod = InputMethodManager.class.getDeclaredMethod("finishInputLocked");
finishInputLockedMethod.setAccessible(true);
focusInMethod = InputMethodManager.class.getDeclaredMethod("focusIn", View.class);
focusInMethod.setAccessible(true);
} catch (NoSuchMethodException | NoSuchFieldException unexpected) {
Log.e("IMMLeaks", "Unexpected reflection exception", unexpected);
return;
} application.registerActivityLifecycleCallbacks(new LifecycleCallbacksAdapter() {
@Override public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
ReferenceCleaner cleaner =
new ReferenceCleaner(inputMethodManager, mHField, mServedViewField,
finishInputLockedMethod);
View rootView = activity.getWindow().getDecorView().getRootView();
ViewTreeObserver viewTreeObserver = rootView.getViewTreeObserver();
viewTreeObserver.addOnGlobalFocusChangeListener(cleaner);
}
});
}
}
3.2.新建一个LifecycleCallbacksAdapter类
里面有几个生命周期的空函数
/*
* Copyright 2017 GcsSloop
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Last modified 2017-03-17 20:31:21
*
* GitHub: https://github.com/GcsSloop
* Website: http://www.gcssloop.com
* Weibo: http://weibo.com/GcsSloop
*/ package com.gcssloop.diycode.hackpatch; import android.app.Activity;
import android.app.Application;
import android.os.Bundle; /** Helper to avoid implementing all lifecycle callback methods. */
public class LifecycleCallbacksAdapter implements Application.ActivityLifecycleCallbacks {
@Override public void onActivityCreated(Activity activity, Bundle savedInstanceState) { } @Override public void onActivityStarted(Activity activity) { } @Override public void onActivityResumed(Activity activity) { } @Override public void onActivityPaused(Activity activity) { } @Override public void onActivityStopped(Activity activity) { } @Override public void onActivitySaveInstanceState(Activity activity, Bundle outState) { } @Override public void onActivityDestroyed(Activity activity) { }
}
3.3.调用方法