android 内存泄漏排查之旅

背景
壁纸模块中需要实现预览效果,支持拖动查看全图,滑动到图片边界拖动后自动切换到下一页,最终采用开源控件photoView,因为项目加载图片库采用里facebook fresco 开源库,所以对photoview进行了改造。代码如下。

public class PhotoView extends ImageView implements IPhotoView {
    private PhotoViewAttacher mAttacher;
    private ScaleType mPendingScaleType;
    private Context myContext;
    private CloseableReference<CloseableImage> imageReference = null;
    public DraweeHolder<GenericDraweeHierarchy> mDraweeHolder;
    private CloseableReference copyCloseableImageReference;
    private ImageLoadLintener imageLoadLintener;
    public void setImageLoadLintener(ImageLoadLintener imageLoadLintener) {
        this.imageLoadLintener = imageLoadLintener;
    }
    public PhotoView(Context context) {
        this(context, null);
    }
    public PhotoView(Context context, AttributeSet attr) {
        this(context, attr, 0);
    }

    public PhotoView(Context context, AttributeSet attr, int defStyle) {
        super(context, attr, defStyle);
        super.setScaleType(ScaleType.MATRIX);
        init();
        GenericDraweeHierarchy hierarchy = new GenericDraweeHierarchyBuilder(getResources()).build();
        // .setFadeDuration(3000)
        // .setFailureImage(getResources().getDrawable(R.drawable.error),
        // ScalingUtils.ScaleType.CENTER_CROP)
        // .set //一些通用设置可以在这里进行设置,此处设置一个空的hierarchy为了加载图片
        // .setPlaceholderImage(getResources().getDrawable(R.color.color_normal_gray_9),
        // ScalingUtils.ScaleType.CENTER_CROP)
        // .setBackground(getResources().getDrawable(R.color.color_normal_gray_9))
        mDraweeHolder = DraweeHolder.create(hierarchy, context);
    }

    protected void init() {

        if (null == mAttacher || null == mAttacher.getImageView()) {
            mAttacher = new PhotoViewAttacher(this);
        }
        if (null != mPendingScaleType) {
            setScaleType(mPendingScaleType);
            mPendingScaleType = null;
        }

    }

    @Override
    protected boolean verifyDrawable(Drawable dr) {
        if (dr == mDraweeHolder.getHierarchy().getTopLevelDrawable()) {
            return true;
        }
        return false;
    }

    @Override
    public void onStartTemporaryDetach() {
        super.onStartTemporaryDetach();
        mDraweeHolder.onDetach();
    }

    @Override
    protected void onAttachedToWindow() {
        init();
        super.onAttachedToWindow();

        mDraweeHolder.onAttach();
    }

    @Override
    public void onFinishTemporaryDetach() {
        super.onFinishTemporaryDetach();
        mDraweeHolder.onAttach();
    }

    protected void onDetachedFromWindow() {
        mAttacher.cleanup();
        mDraweeHolder.onDetach();
        if(copyCloseableImageReference!=null){
            copyCloseableImageReference.close();
            copyCloseableImageReference=null;
        }
        super.onDetachedFromWindow();
    }

    public void setImageUrl(String url, final int view_width_dp, int view_height_dp) {
        ImageRequest imageRequest = ImageRequestBuilder.newBuilderWithSource(Uri.parse(url)).setAutoRotateEnabled(true)
                .setResizeOptions(new ResizeOptions(view_width_dp, view_height_dp)).build();
        ImagePipeline imagePipeline = Fresco.getImagePipeline();
        final DataSource<CloseableReference<CloseableImage>> dataSource = imagePipeline.fetchDecodedImage(imageRequest,
                this);
        DraweeController controller = Fresco.newDraweeControllerBuilder()
                .setOldController(mDraweeHolder.getController()).setImageRequest(imageRequest)
                .setControllerListener(new BaseControllerListener<ImageInfo>() {
                    @Override
                    public void onFinalImageSet(String s, @Nullable ImageInfo imageInfo, @Nullable Animatable animatable) {
                        //Log.e("photeview", " ########## onFinalImageSet   " + s);
                        try {
                            imageReference = dataSource.getResult();
                            if (imageReference != null) {
                                copyCloseableImageReference = imageReference.clone();
                                CloseableBitmap closeableBitmap = (CloseableBitmap)copyCloseableImageReference.get();
                                if (closeableBitmap != null && closeableBitmap instanceof CloseableStaticBitmap) {
                                    CloseableStaticBitmap closeableStaticBitmap = (CloseableStaticBitmap) closeableBitmap;
                                    Bitmap bitmap = closeableStaticBitmap.getUnderlyingBitmap();
                                    if (bitmap != null&!bitmap.isRecycled()) {
                                        setImageBitmap(bitmap);
                                        if (null != imageLoadLintener) {
                                            imageLoadLintener.onFinalImageSet(s);
                                        }//Log.e("photeview", " ########## bitmap.getWidth=   " +       bitmap.getWidth());
                                    }else{
                                        if (null != imageLoadLintener) {
                                            imageLoadLintener.onFailure(s);
                                        }
                                    }
                                }
                            }
                        }finally {
                            dataSource.close();
                            CloseableReference.closeSafely(imageReference);
                        }
                    }

                    @Override
                    public void onSubmit(String id, Object callerContext) {
                        if (null != imageLoadLintener) {
                            imageLoadLintener.onSubmit(id);
                        }
                        //Log.e("photeview", " ########## onSubmit   " + id);
                    }

                    @Override
                    public void onRelease(String id) {
                        //Log.e("photeview", " ########## onRelease   " + id);
                        if (null != imageLoadLintener) {
                            imageLoadLintener.onRelease(id);
                        }
                    }

                    @Override
                    public void onFailure(String id, Throwable throwable) {
                        if (null != imageLoadLintener) {
                            imageLoadLintener.onFailure(id);
                        }
                        //Log.e("photeview", " ########## onFailure   " + id);
                    }

                    @Override
                    public void onIntermediateImageSet(String id, ImageInfo imageInfo) {
                        //Log.e("photeview", " ########## onIntermediateImageSet   " + id);
                    }

                }).setTapToRetryEnabled(true).build();
        mDraweeHolder.setController(controller);
    }

    /**
     * @deprecated use {@link #setRotationTo(float)}
     */
    @Override
    public void setPhotoViewRotation(float rotationDegree) {
        mAttacher.setRotationTo(rotationDegree);
    }

    @Override
    public void setRotationTo(float rotationDegree) {
        mAttacher.setRotationTo(rotationDegree);
    }

    @Override
    public void setRotationBy(float rotationDegree) {
        mAttacher.setRotationBy(rotationDegree);
    }

    @Override
    public boolean canZoom() {
        return mAttacher.canZoom();
    }

    @Override
    public RectF getDisplayRect() {
        return mAttacher.getDisplayRect();
    }

    @Override
    public Matrix getDisplayMatrix() {
        return mAttacher.getDisplayMatrix();
    }

    @Override
    public boolean setDisplayMatrix(Matrix finalRectangle) {
        return mAttacher.setDisplayMatrix(finalRectangle);
    }

    @Override
    @Deprecated
    public float getMinScale() {
        return getMinimumScale();
    }

    @Override
    public float getMinimumScale() {
        return mAttacher.getMinimumScale();
    }

    @Override
    @Deprecated
    public float getMidScale() {
        return getMediumScale();
    }

    @Override
    public float getMediumScale() {
        return mAttacher.getMediumScale();
    }

    @Override
    @Deprecated
    public float getMaxScale() {
        return getMaximumScale();
    }

    @Override
    public float getMaximumScale() {
        return mAttacher.getMaximumScale();
    }

    @Override
    public float getScale() {
        return mAttacher.getScale();
    }

    @Override
    public ScaleType getScaleType() {
        return mAttacher.getScaleType();
    }

    @Override
    public void setAllowParentInterceptOnEdge(boolean allow) {
        mAttacher.setAllowParentInterceptOnEdge(allow);
    }

    @Override
    @Deprecated
    public void setMinScale(float minScale) {
        setMinimumScale(minScale);
    }

    @Override
    public void setMinimumScale(float minimumScale) {
        mAttacher.setMinimumScale(minimumScale);
    }

    @Override
    @Deprecated
    public void setMidScale(float midScale) {
        setMediumScale(midScale);
    }

    @Override
    public void setMediumScale(float mediumScale) {
        mAttacher.setMediumScale(mediumScale);
    }

    @Override
    @Deprecated
    public void setMaxScale(float maxScale) {
        setMaximumScale(maxScale);
    }

    @Override
    public void setMaximumScale(float maximumScale) {
        mAttacher.setMaximumScale(maximumScale);
    }

    @Override
    public void setScaleLevels(float minimumScale, float mediumScale, float maximumScale) {
        mAttacher.setScaleLevels(minimumScale, mediumScale, maximumScale);
    }

    @Override
    // setImageBitmap calls through to this method
    public void setImageDrawable(Drawable drawable) {
        super.setImageDrawable(drawable);
        if (null != mAttacher) {
            mAttacher.update();
        }
    }

    @Override
    public void setImageResource(int resId) {
        super.setImageResource(resId);
        if (null != mAttacher) {
            mAttacher.update();
        }
    }

    @Override
    public void setImageURI(Uri uri) {
        super.setImageURI(uri);
        if (null != mAttacher) {
            mAttacher.update();
        }
    }

    @Override
    public void setOnMatrixChangeListener(OnMatrixChangedListener listener) {
        mAttacher.setOnMatrixChangeListener(listener);
    }

    @Override
    public void setOnLongClickListener(OnLongClickListener l) {
        mAttacher.setOnLongClickListener(l);
    }

    @Override
    public void setOnPhotoTapListener(OnPhotoTapListener listener) {
        mAttacher.setOnPhotoTapListener(listener);
    }

    @Override
    public OnPhotoTapListener getOnPhotoTapListener() {
        return mAttacher.getOnPhotoTapListener();
    }

    @Override
    public void setOnViewTapListener(OnViewTapListener listener) {
        mAttacher.setOnViewTapListener(listener);
    }

    @Override
    public OnViewTapListener getOnViewTapListener() {
        return mAttacher.getOnViewTapListener();
    }

    @Override
    public void setScale(float scale) {
        mAttacher.setScale(scale);
    }

    @Override
    public void setScale(float scale, boolean animate) {
        mAttacher.setScale(scale, animate);
    }

    @Override
    public void setScale(float scale, float focalX, float focalY, boolean animate) {
        mAttacher.setScale(scale, focalX, focalY, animate);
    }


    @Override
    protected void onDraw(Canvas canvas) {
        try {
            super.onDraw(canvas);
        }catch (Exception e){
            Log.e("PhotoView", " bitmap is recylce...");
            if (null != imageLoadLintener) {
                imageLoadLintener.onRelease("");
            }
            //WallPaperUtil.ToastUtils.showToast(ACContext.getContext(),getResources().getString(R.string.wallpaper_preview_failed));
        }
    }

    @Override
    public void setScaleType(ScaleType scaleType) {
        if (null != mAttacher) {
            mAttacher.setScaleType(scaleType);
        } else {
            mPendingScaleType = scaleType;
        }
    }

    @Override
    public void setZoomable(boolean zoomable) {
        mAttacher.setZoomable(zoomable);
    }

    @Override
    public Bitmap getVisibleRectangleBitmap() {
        return mAttacher.getVisibleRectangleBitmap();
    }

    @Override
    public void setZoomTransitionDuration(int milliseconds) {
        mAttacher.setZoomTransitionDuration(milliseconds);
    }

    @Override
    public IPhotoView getIPhotoViewImplementation() {
        return mAttacher;
    }

    @Override
    public void setOnDoubleTapListener(GestureDetector.OnDoubleTapListener newOnDoubleTapListener) {
        mAttacher.setOnDoubleTapListener(newOnDoubleTapListener);
    }

    @Override
    public void setOnScaleChangeListener(PhotoViewAttacher.OnScaleChangeListener onScaleChangeListener) {
        mAttacher.setOnScaleChangeListener(onScaleChangeListener);
    }

    public void cleanup(){
        if(mAttacher!=null){
            mAttacher.cleanup();
        }

        if(copyCloseableImageReference!=null){
            copyCloseableImageReference.close();
            copyCloseableImageReference=null;
        }
    }

}

至此完成需求,后期测试跑monkey,发现此界面存在内容泄漏,通过mat 对hprof文件分享,发现确实存在内存泄漏。
主要以下几处(mac markdown在线编辑器上传图失败,这里只能描述下引用链了)
1.Actvity -> Gesturedetector
2.Activty —> photoView - fresco
3.Activity -> PhotoView
4.Activity -> AudioManage

解决问题
Actvity -> Gesturedetector
一开始是以为是photoview 中 onDetachedFromWindow() 中
mAttacher.cleanup();未调用成功所致,后来改为photoview销毁前,手动调用,反复测试后发现然并卵,然后开始转换查找问题方向,去代码中查看GestureDetector 初始化及构造方法,最后发现他是在 PhotoViewAttacher 这么构造出来的
mGestureDetector = new GestureDetector(imageView.getContext(),
new GestureDetector.SimpleOnGestureListener() {
// forward long click listener
@Override
public void onLongPress(MotionEvent e) {
if (null != mLongClickListener) {
mLongClickListener.onLongClick(getImageView());
}
}
});
最后改为
mGestureDetector = new GestureDetector(imageView.getContext().getApplicationContext(),
new GestureDetector.SimpleOnGestureListener() {
// forward long click listener
@Override
public void onLongPress(MotionEvent e) {
if (null != mLongClickListener) {
mLongClickListener.onLongClick(getImageView());
}
}
});
ps: 在android某些版本上前一种并不会造成泄漏,但是安全起见最好是用后一种。
2.Activty —> photoView - fresco
photoview显示图片我们是通过 从frsco中 imagePipeline 直接拷贝一份图片内存缓存,通过如下方式
final DataSource `< CloseableReference < CloseableImage>> dataSource = imagePipeline.fetchDecodedImage(imageRequest,
this);
这个过程中把photoview作为 object 传给了frsco,造成了内存泄漏。跟着源码发现 这个object 只是作为一个标记,frsco拿它并无大用,直接改以下这张
final DataSource `< CloseableReference < CloseableImage>> dataSource = imagePipeline.fetchDecodedImage(imageRequest,
null);
问题解决。
3.Activity -> PhotoView
这个是排查后发现是个低级错误,photoView 中某个listener进行了set,但是在activity销毁的时候,并没有针对每个photoview 进行注销listener。

4.Activity -> AudioManage
这个莫名奇妙,完全没有用到这个类,或者相关的import,最后查看引用链,发现是context 引起的泄漏,然后排查所有用到的content,最后现在这么一句,在设置壁纸时,初始化了WallpaperManager

WallpaperManager wallpaperManager = WallpaperManager.getInstance(mContext);

除了这里其他并没有引用了,所以改为这样

 WallpaperManager wallpaperManager = WallpaperManager.getInstance(mContext.getApplicationContext());

跑monkey测试发现,没有泄漏了,问题解决了。

此次monkey 测试还发现其他界面的一些内存泄漏,这里坐下总结
总结
容易引起内存泄漏的有以下一些
1. 匿名内部类
匿名内部类中做异步网络请求时,通过传入当前Activity,使用弱引用的方 式。如:
WeakReference `< WallPaperSpecialActivity` > refActivity;
public WallpaperSpecialAsyncTask(WallPaperSpecialActivity activity, String pType, int OffSet) {
this.refActivity = new WeakReference (activity);
this.OffSet = OffSet;
this.pType = pType;
}
2. 未注销的引用
3. context
4. AsyncTask
针对这些时一定要慎之又慎。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值