Diycode开源项目 如何解决InputMethodManager造成的内存泄漏问题
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.调用方法
Diycode开源项目 如何解决InputMethodManager造成的内存泄漏问题的更多相关文章
- DiyCode开源项目 BaseActivity 分析
1.首先将这个项目的BaseActivity源码拷贝过来. /* * Copyright 2017 GcsSloop * * Licensed under the Apache License, Ve ...
- Diycode开源项目 搭建可以具有下拉刷新和上拉加载的Fragment
1.效果预览 1.1.这个首页就是一个Fragment碎片,本文讲述的就是这个碎片的搭建方式. 下拉会有一个旋转的刷新圈,上拉会刷新数据. 1.2.整体结构 首先底层的是BaseFragment 然后 ...
- Diycode开源项目 BaseApplication分析+LeakCanary第三方+CrashHandler自定义异常处理
1.BaseApplication整个应用的开始 1.1.看一下代码 /* * Copyright 2017 GcsSloop * * Licensed under the Apache Licens ...
- Diycode开源项目 TopicContentActivity分析
1.效果预览以及布局分析 1.1.实际效果预览 左侧话题列表的布局是通过TopicProvider来实现的,所以当初分析话题列表就没有看到布局. 这里的话题内容不是一个ListView,故要自己布局. ...
- 深入解析开源项目之Universal-Image-Loader(二)内存---缓存篇
珍惜作者劳动成果,如需转载,请注明出处. http://blog.csdn.net/zhengzechuan91/article/details/50292871 Universal-Image-Lo ...
- 解决NSTimer存在的内存泄漏的问题
创建定时器会在一定的间隔后执行某些操作,一般大家会这样创建定时器,这样创建的定时,self对定时器有个引用,定时器对self也有个引用,造成了循环引用,最终造成了内存泄漏,如果定时器在做下载的操作就会 ...
- Diycode开源项目 磁盘图片缓存+自定义webViewClient+图片点击js方法
1.磁盘图片缓存器DiskImageCache 1.1.这个类很多情况都可能用的到,耦合性很低,所以分开讲. 源代码: /* * Copyright 2017 GcsSloop * * License ...
- Diycode开源项目 ImageActivity分析
1.首先看一下效果 1.1做成了一个GIF 1.2.我用格式工厂有点问题,大小无法调到手机这样的大小,目前还没有解决方案. 1.3.网上有免费的MP4->GIF,参考一下这个网站吧. 1.4.讲 ...
- Diycode开源项目 Glide图片加载分析
1.使用Glide前的准备 1.1.首先要build.gradle中添加 github原地址点击我. 参考博客:Glide-开始! 参考博客:android图片加载库Glide的使用介绍. 参考博 ...
随机推荐
- 【起航计划 010】2015 起航计划 Android APIDemo的魔鬼步伐 09 App->Activity->Redirection 根据shared preferences是否有值决定是否redirect
Redirection示例涉及到三个Acitivity: RedirectEnter, RedirectMain,RedirectGetter. 示例的主Activity为 RedirectEnter ...
- 日志滚动与cron调度
日志滚动与cron调度 1.日志滚动 nginx默认日志不能进行滚动,始终写入到一个文件中,即access.log.编写日志滚动的shell脚本,并使用linux的cron定时调度周期性进行日志文件滚 ...
- 关于Linux主流框架运维工作剖析
LINUX是开源的,这也是最主要的原因,想学Windows,Unix对不起,没有源代码.也正是因为这样,LINUX才能够像雪球一样越滚越大,发展到现在这种规模.今天将为大家带来关于Linux主流框架运 ...
- 带来全新的网络格局---html5
自从HTML5诞生之后,就是开始建立了一个标准的原则,那就是所有的技术它必须是面向开放,并不能有专利的一个存在,在整个期间Opera捐献了css技术,而google的话则是给开发者提供了视频的webM ...
- 使用Python命令创建jenkins的job
目的:通过调用jenkins的命令,动态创建jenkins的job 如何使用,使用Python的脚本,更多API可以进入到官网去查看,http://jenkinsapi.readthedocs.io/ ...
- Android(java)学习笔记73:Intent启动Activity
1. Intent启动Activity案例 (1)首先是main.xml和other.xml文件如下: main.xml文件: <?xml version="1.0" enc ...
- ARM是CPU体系结构
https://zhidao.baidu.com/question/680620766286548532.html ARM是一种使用精简指令(RISC)的CPU,有别于英特尔的复杂指令(CISC) x ...
- 高精度水题(POJ2109)
题目链接:http://poj.org/problem?id=2109 double 可以虽然可以表示10^-307~~~10^208,但是精确度只有16位,这个题有bug. #include < ...
- css术语和概念
.vocabulary{ height:99px; color:transparent; } 属性 上面示意css代码中的height和color就是属性. 值 上面的99px就是值 整数值: ...
- phpmyadmin 打开数据表较多,数据量较大的数据库时出现超时的解决办法
用phpmyadmin打开数据表较多,数据量较大的数据库时,会出现超时,或者等半天打开了说数据库没有表.并且即便打开了,再进行其他浏览,编辑,sql等操作,页面也是相当慢的,慢等几乎无法忍受.这里慢也 ...