RemoteViews 内存溢出解决方法

RemoteViews 内存溢出

1、RemoteViews内存溢出原因

    我们使用大致是这几种方法来设置图片。

Android\Sdk\sources\android-25\android\widget\RemoteViews.java

/**
     * Equivalent to calling ImageView.setImageResource
     *
     * @param viewId The id of the view whose drawable should change
     * @param srcId The new resource id for the drawable
     */
    public void setImageViewResource(int viewId, int srcId) {
        setInt(viewId, "setImageResource", srcId);
    }

    /**
     * Equivalent to calling ImageView.setImageURI
     *
     * @param viewId The id of the view whose drawable should change
     * @param uri The Uri for the image
     */
    public void setImageViewUri(int viewId, Uri uri) {
        setUri(viewId, "setImageURI", uri);
    }

    /**
     * Equivalent to calling ImageView.setImageBitmap
     *
     * @param viewId The id of the view whose bitmap should change
     * @param bitmap The new Bitmap for the drawable
     */
    public void setImageViewBitmap(int viewId, Bitmap bitmap) {
        setBitmap(viewId, "setImageBitmap", bitmap);
    }

    /**
     * Equivalent to calling ImageView.setImageIcon
     *
     * @param viewId The id of the view whose bitmap should change
     * @param icon The new Icon for the ImageView
     */
    public void setImageViewIcon(int viewId, Icon icon) {
        setIcon(viewId, "setImageIcon", icon);
    }

    /**
     * Equivalent to calling AdapterView.setEmptyView
     *
     * @param viewId The id of the view on which to set the empty view
     * @param emptyViewId The view id of the empty view
     */
    public void setEmptyView(int viewId, int emptyViewId) {
        addAction(new SetEmptyView(viewId, emptyViewId));
    }

    单个RemoteViews对象通过setImageViewBitmap()方法设置图片不是替换,而是不断add。而且没有remove的方法,当这个list被add满之后,就会出现溢出的问题,甚至达到一定限额后就会让你的应用crash。

2、解决办法

所以解决办法是什么?
    那就是不要复用RemoteViews,每次需要跟新RemoteViews的时候,new一个新的,然后该设置PendingIntent就再设置一遍,这是一个新的RemoteViews,当然得重新设置一遍。自己压力测试过了,没问题,回收的问题,开始复用RemoteViews的时候还手动recycle掉bitmap,结果报使用recycled bitmap。。。。其实没必要,new一个新的RemoteViews,然后交给垃圾回收器GC的时候回收就行了。

2.1贴一下我的代码

    @Override
    public void onCreate() {
        super.onCreate();
        Log.i(TAG,"音乐服务onCreate");

        remoteViews = new RemoteViews(getPackageName(), R.layout.view_remote);
        initNotificationSon();
        //省略其他代码
    }

   private void initNotificationSon() {
        NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(MusicApplication.getContext());

        mBuilder.setSmallIcon(R.drawable.play_background02); // 设置顶部图标
        mBuilder.setOngoing(true);

        notification = mBuilder.build();//构建通知
        setNotification();
        notification.contentView = remoteViews; // 设置下拉图标
        notification.bigContentView = remoteViews; // 防止显示不完全,需要添加apiSupport
        notification.flags = Notification.FLAG_ONGOING_EVENT;
        notification.icon = R.drawable.anim_log;
        startForeground(123, notification);//启动为前台服务
    }

    /**
     * PendingIntent是一种特殊的intent,设置之后并不会马上使用,而是在真正点击后只会调用。
     */
    private void setNotification(){
        Log.d(TAG, "MediaUtils.currentState == " + MediaUtils.currentState);
        if (MediaUtils.currentState == MediaStateCode.PLAY_PAUSE ||
                MediaUtils.currentState == MediaStateCode.PLAY_STOP) {
            /*play -》 pause*/
            remoteViews.setImageViewResource(R.id.notification_play_pause, R.drawable.ic_pause_black);
        }else {
            remoteViews.setImageViewResource(R.id.notification_play_pause, R.drawable.ic_play_black);
        }
        // 点击音乐image跳转到主界面
        Intent intentGo = new Intent(this, LocalListActivity.class);
        PendingIntent pendingIntentGo = PendingIntent.getActivity(
                this, 0, intentGo, PendingIntent.FLAG_CANCEL_CURRENT);
        remoteViews.setOnClickPendingIntent(R.id.notification_album, pendingIntentGo);

        // 关闭
        Intent intentClose = new Intent(MediaStateCode.ACTION_CLOSE);
        PendingIntent pendingIntentClose = PendingIntent.getBroadcast(
                this, 0, intentClose, PendingIntent.FLAG_CANCEL_CURRENT);
        remoteViews.setOnClickPendingIntent(R.id.notification_close_this, pendingIntentClose);

        // 设置上一曲
        Intent intentLast = new Intent(MediaStateCode.ACTION_LAST);
        PendingIntent pendingIntentLast = PendingIntent.getBroadcast(
                this, 0, intentLast, PendingIntent.FLAG_CANCEL_CURRENT);
        remoteViews.setOnClickPendingIntent(R.id.notification_last, pendingIntentLast);

        // 设置播放&暂停
        Intent intentPlayOrPause = new Intent(MediaStateCode.ACTION_PLAY_OR_PAUSE);
        PendingIntent pendingIntentPlayOrPause =
                PendingIntent.getBroadcast(this, 0,
                        intentPlayOrPause, PendingIntent.FLAG_CANCEL_CURRENT);
        remoteViews.setOnClickPendingIntent(R.id.notification_play_pause, pendingIntentPlayOrPause);

        // 下一曲
        Intent intentNext = new Intent(MediaStateCode.ACTION_NEXT);
        PendingIntent pendingIntentNext = PendingIntent.getBroadcast(
                this, 0, intentNext, PendingIntent.FLAG_CANCEL_CURRENT);
        remoteViews.setOnClickPendingIntent(R.id.notification_next, pendingIntentNext);

        // 设置收藏
        Intent intentLove = new Intent(MediaStateCode.ACTION_LOVE);
        PendingIntent pendingIntentLove = PendingIntent.getBroadcast(
                this, 0, intentLove, PendingIntent.FLAG_CANCEL_CURRENT);
        remoteViews.setOnClickPendingIntent(R.id.notification_love, pendingIntentLove);
    }

    public Handler remoteViewsHandler = new Handler() {

        public void handleMessage(android.os.Message msg) {
            Log.d(TAG, "remoteViewsHandler" + ", msg.what=" + msg.what);
            int position  = MediaUtils.currentSongPosition;
            Log.d(TAG, "remoteViewsHandler" + ", 全局播放position=" + position);
            switch (msg.what){
                //上一曲/下一曲;需要跟新专辑图和歌手名,歌曲信息
                case 1:
                    /*专辑图片,歌曲名,歌手名*/
                    RefreshAlbumPicTitleArtist(position);
                    RefreshPlayOrPauseButton();
                    break;

                //播放/暂停同一首音乐;只需要更新:播放/暂停图标
                case 2:
                    RefreshPlayOrPauseButton();
                    break;
            }
            notification.contentView = remoteViews; // 设置下拉图标
            notification.bigContentView = remoteViews; // 防止显示不完全,需要添加apiSupport
            startForeground(123, notification);//启动为前台服务
            Log.d(TAG, "remoteViewsHandler end");
        }
    };

    /**
     * 更新 "专辑图片,歌曲名,歌手名"
     */
    private void RefreshAlbumPicTitleArtist(int position) {
        remoteViews = new RemoteViews(getPackageName(), R.layout.view_remote);
        setNotification();
        try {
            String path = MusicResources.getAlbumArt((int) musicList.get(position).getAlbum_id());

            Log.d(TAG,"RefreshAlbumPicTitleArtist" + "专辑图path="+path);

            Bitmap bitmap;
            if (null == path) {
                bitmap = BitmapFactory.decodeResource(getResources(),R.drawable.play_background02);
            } else {
                bitmap = BitmapFactory.decodeFile(path);
            }
            remoteViews.setImageViewBitmap(R.id.notification_album, bitmap);

            /*歌曲名称 & 歌手名*/
            remoteViews.setTextViewText(R.id.notification_title, musicList.get(position).getTitle());
            remoteViews.setTextViewText(R.id.notification_artist, musicList.get(position).getArtist());

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

就这样,如果想看看完整的例子,可以来我的GitHub上看,
对应类的地址:https://github.com/stoneWangL/StoneMusic/blob/master/app/src/main/java/com/stone/stonemusic/service/MusicService.java

项目地址:https://github.com/stoneWangL/StoneMusic
欢迎follow,一起交流技术吱吱(:

上一篇:地理围栏API服务开发


下一篇:Python基础(一)