class MyHandler extends Handler {
MyHandler() {
}
@Override
public void handleMessage(Message msg) {
// to do your job
}
}
MyHandler myHandler = new MyHandler();
如上,在Activity内部如果声明一个这样的Handler,那么myHandler就默认持有Activity引用,假设Activity退出了,但是可能这时候才有myHandler的任务post,那么Activity是无法被回收的,可以采用以下方式解决:
static class MyHandler extends Handler {
WeakReference<Activity> mActivityReference;
MyHandler(Activity activity) {
mActivityReference = new WeakReference<Activity>(activity);
}
@Override
public void handleMessage(Message msg) {
final Activity activity = mActivityReference.get();
if (activity != null) {
if (msg.what == 1 && isJumpToHomePage) {
Intent intent = new Intent(activity, HomePageActivity.class);
// intent.putExtra("themeType", themeType);
// LogUtil.d("themeType == " + themeType);
activity.startActivity(intent);
activity.finish();
}
}
}
}
这里面是把MyHandler是一个内部静态类,静态类在java虚拟机加载的时候就是独立加载到内存中的,不会依赖于任何其他类,而且这里面是把activity以弱引用的方式传到MyHandler中,即便是静态MyHandler类对象一直存在,但是由于它持有的是activity弱引用,在gc回收的时候activity对象是可以被回收的,另外注意一点,对于Handler的使用如果有sendEmptyMessageDelayed()来延迟任务执行的话最好在Activity的onDestroy里面把Handler的任务都移除(removeCallbacks(null)),activity在退出后,就是应该在onDestroy方法里面把一些任务取消掉,做一些清理的操作
private static class RecorderTimeListener implements TimeCallback {
WeakReference<ChatActivity> target;
RecorderTimeListener(ChatActivity activity) {
target = new WeakReference<>(activity);
}
@Override
public void onCountDown(final int time) {
if (target == null || target.get() == null) {
return;
}
final ChatActivity activity = target.get();
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
activity.volumeView.setResetTime(time);
}
});
}
@Override
public void onMaxTime() {
if (target == null || target.get() == null) {
return;
}
final ChatActivity activity = target.get();
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
activity.isMaxTime = true;
activity.stopRecord();
}
});
}
}
private class StartRecorderListener implements StartCallback {
@Override
public boolean onWait() {
cancelRecord();
return true;
}
@Override
public void onStarted() {
if (playerManager.isPlaying()) {
playerManager.stop();
}
recordWaveView.setVisibility(View.VISIBLE);
animation = (AnimationDrawable) recordWaveView.getBackground();
animation.start();
volumeView.showMoveCancelView();
volumeDialog.show();
viewHandler.postDelayed(volumeRunnable, 100);
}
@Override
public void onFailed(int errorCode) {
if (errorCode == RecorderManager.ERROR_START_FAIL) {
showHintDialog(R.string.chat_permission_dialog_title, R.string.chat_permission_dialog_message);
}
}
}
private void startRecord() {
SystemDateUtil.init(this);
LogUtil.i(ChatKey.TAG_INFO, "--------------------------录音开始--------------------------");
final long startSendTime = SystemDateUtil.getCurrentDate().getTime();
sliceSender = dialogMsgService.createSliceSender(
AccountUtil.getCurrentFamilyChatDialogId(),
AccountUtil.getCurrentImAccountId(), new DialogMsgService.OnSendVoiceMsgListener() {
@Override
public void onSuccess() {
LogUtil.d(TAG, "录音上传成功");
sendBigData(sliceSender.getGroupId(), ChatMsgBeh.MsgType.EMOJI,
SystemDateUtil.getCurrentDate().getTime() - startSendTime, SendMsgEvent.CODE_SEND_SUCCESS);
}
@Override
public void onFailure() {
sendBigData(sliceSender.getGroupId(), ChatMsgBeh.MsgType.EMOJI,
SystemDateUtil.getCurrentDate().getTime() - startSendTime, SendMsgEvent.CODE_SEND_FAILURE);
LogUtil.d(TAG, "录音上传失败");
}
});
RecorderManager.getInstance(this).startRecorder(sliceSender, new StartRecorderListener(), new RecorderTimeListener(this));
LogUtil.i(ChatKey.TAG_INFO, "groupId:" + sliceSender.getGroupId());
}
如上StartRecorderListener是内部类,RecorderTimeListener是静态内部类并传入Activity弱引用,如果把StartRecorderListener的实现改成RecorderTimeListener的实现,那么Activity内存泄漏就不存在了
自定义控件ImageButton中:
public void start(float startAngle, float endAngle) {
setStop(false);
final AnimatorSet as = new AnimatorSet();
final ObjectAnimator oa = ObjectAnimator.ofFloat(this, "progress",
startAngle, endAngle);
oa.setDuration(duration);
oa.setInterpolator(new DecelerateInterpolator(1.1f));
oa.setRepeatCount(count);
// oa.setRepeatMode(ObjectAnimator.INFINITE);
oa.addUpdateListener(new AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animator) {
if (stop && as.isRunning()) {
as.cancel();
// oa.removeAllListeners();
} else {
float p = (float) animator.getAnimatedValue();
setProgress(p);
}
}
});
as.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
}
});
as.play(oa);
as.start();
}
public void cancel() {
setStop(true);
}
public void setStop(boolean stop) {
this.stop = stop;
if (stop) {
setProgress(0.0f);
}
}
如上如果不cancel掉属性动画就会一直运行并且一直去执行控件的onDraw方法,那么ImageButton持有了Activity对象,而属性动画ObjectAnimator持有了ImageButton,ObjectAnimator一直在运行,那么Activity对象也就不能被释放了
private static ValueAnimator valueAnimator;
private void startValueAnimator() {
int displayTime2Show = displayTime - 1;
if (displayTime2Show > 1) {
valueAnimator = ValueAnimator.ofInt(displayTime2Show, 1);
valueAnimator.setDuration(displayTime2Show * 1000);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
tvStartPageTime.setText(animation.getAnimatedValue().toString());
}
});
valueAnimator.start();
}
}
protected void onPause() {
if (valueAnimator != null && valueAnimator.isRunning()) {
valueAnimator.cancel();
valueAnimator = null;
}
super.onPause();
}
即便是在activity退出后cancel掉动画,activity依然无法被释放,为什么?因为valueAnimator是静态的,而且添加了动画属性改变的监听addUpdateListener,在监听回调里面有tvStartPageTime(TextView)控件,默认持有Activity对象,因此即便Activity退出,动画cancel掉也无法释放持有的引用,修改方法有两种,一种是把valueAnimator的static修饰去掉,另一中国是:
protected void onPause() {
valueAnimator.removeAllUpdateListeners();
if (valueAnimator != null && valueAnimator.isRunning()) {
valueAnimator.cancel();
valueAnimator = null;
}
super.onPause();
}
加一句监听器的移除代码removeAllUpdateListeners()
这里写图片描述 问题的根源应该就是这:
loginPasswdEt.setTransformationMethod(PasswordTransformationMethod.getInstance());
loginPasswdEt.setTransformationMethod(HideReturnsTransformationMethod.getInstance());
而PasswordTransformationMethod和HideReturnsTransformationMethod分别都是一个单例:
private static PasswordTransformationMethod sInstance;
private static HideReturnsTransformationMethod sInstance;
PasswordTransformationMethod
public CharSequence getTransformation(CharSequence source, View view) {
if (source instanceof Spannable) {
Spannable sp = (Spannable) source;
/*
* Remove any references to other views that may still be
* attached. This will happen when you flip the screen
* while a password field is showing; there will still
* be references to the old EditText in the text.
*/
ViewReference[] vr = sp.getSpans(0, sp.length(),
ViewReference.class);
for (int i = 0; i < vr.length; i++) {
sp.removeSpan(vr[i]);
}
removeVisibleSpans(sp);
sp.setSpan(new ViewReference(view), 0, 0,
Spannable.SPAN_POINT_POINT);
}
return new PasswordCharSequence(source);
}
private static class ViewReference extends WeakReference<View>
implements NoCopySpan {
public ViewReference(View v) {
super(v);
}
}
上面是5.0系统的源码,里面已经用ViewReference来包装view设置到Spannable中了,所以是把view的弱引用传进去了,因此可以被gc回收,而在4.0android系统上,很可能就不是这么做的,所以4.0系统上面就是View对象被PasswordTransformationMethod和HideReturnsTransformationMethod单例长期持有,而View又持有Activity对象,所以针对4.0系统我们只需要释放这两个单例对象即可:
private void releaseMemoryLeak() {
int sdk = Build.VERSION.SDK_INT;
if (sdk >= Build.VERSION_CODES.LOLLIPOP) {
return;
}
try {
Field field1 = PasswordTransformationMethod.class.getDeclaredField("sInstance");
if (field1 != null) {
field1.setAccessible(true);
field1.set(PasswordTransformationMethod.class, null);
}
Field field2 = HideReturnsTransformationMethod.class.getDeclaredField("sInstance");
if (field2 != null) {
field2.setAccessible(true);
field2.set(HideReturnsTransformationMethod.class, null);
}
} catch (NoSuchFieldException e) {
SyncLogUtil.e(e);
} catch (IllegalAccessException e) {
SyncLogUtil.e(e);
}
}
加上上述代码后验证发现内存不再泄漏,搞定。
private static Drawable sBackground;
@Override
protected void onCreate(Bundle state) {
super.onCreate(state);
TextView label = new TextView(this);
label.setText("Leaks are bad");
if (sBackground == null) {
sBackground = getDrawable(R.drawable.large_bitmap);
}
label.setBackgroundDrawable(sBackground);
setContentView(label);
}
因为在View的setBackgroundDrawable方法里面有一句:
public void setBackgroundDrawable(Drawable background) {
......省略很多代码
background.setCallback(this);
mBackground = background;
}
Drawable对象把View对象作为回调保存起来了,不过在4.0系统以后引入回调来保存View对象了,所以已经不会造成内存泄漏问题了:
public final void setCallback(Callback cb) {
mCallback = new WeakReference<Callback>(cb);
}
这里依然要举例子出来是想说明不恰当的使用static来修饰变量很有可能导致对象无法被回收
扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有