DuckAudio-基于ListView的Item进度刷新

Android app中有些需求是刷新ListView中的Item的进度条。

  • 比如下载列表这样子的就需要同时刷新不同Item的下载进度。
  • 于是基于这样的一个需求,我做了一个比较完整的Demo专门用于刷新ListView的Item的进度的。
  • 当然,这个Demo与下载是无关的。不过也是刷新Item的进度的。

项目介绍

  • 功能:扫描手机中的所有歌曲文件,添加到ListView中显示,根据当前选择的操作模式[播放模式,文件操作模式],默认是播放模式。播放模式时点击Item,播放对应的歌曲;操作模式时,复制当前文件到/temp目录中(刻意缓慢复制,以便模拟进度刷新)。播放模式中,点击Item弹出选择播放器界面,选择一个播放器就可以进行音乐播放,如果手机中没有音乐播放器,可能会挂掉app。(bug忘修复了)…….如果是文件操作模式,点击Item就立马进行当前文件复制,并显示复制进度,复制完成删除源文件,删除该Item,目标文件在/temp目录中可以找到。手机自带的音乐播放器一般也能扫描到。准备在app退出的时候添加/temp中的音乐文件到系统数据库,db包还没有完成,就没有弄了。
  • 文件操作模式中,理论上可以同时复制无限多个文件。具体限度取决于手机内存。
  • 代码界面相对清晰,不多做介绍。这里贴出关键代码。
    1. 扫描本地音乐文件,异步返回扫描结果
    /**
     * 异步加载本地audio数据<br/>
     * 通过监听器{@link OnAudioLoaded}获取加载完成的数据
     * 
     * @param context
     */
    public void loadAsyncAudioInfo(final Context context,
            final OnAudioLoaded loaded) {
        // Log.e("aaa", "****--loadAsyncAudioInfo--****");
        final List<Music> musicList = Collections
                .synchronizedList(new LinkedList<Music>());
        new AsyncTask<Void, Void, List<Music>>() {
            @Override
            protected List<Music> doInBackground(Void... params) {
                if (context == null) {
                    Log.e("aaa", "context == null ,fuck !!!");
                    return null;
                }
                // Log.e("aaa", "start to print the message of audios");
                Uri uri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
                Cursor c = context.getContentResolver().query(
                        uri,
                        new String[] { Audio.Media.TITLE, Audio.Media.ARTIST,
                                Audio.Media.DATA }, null, null, null);
                // Log.v("aaa", "the cursor is " + c);
                while (c != null && c.moveToNext()) {
                    String title = c.getString(c
                            .getColumnIndex(Audio.Media.TITLE));
                    String artist = c.getString(c
                            .getColumnIndex(Audio.Media.ARTIST));
                    String data = c.getString(c
                            .getColumnIndex(Audio.Media.DATA));
                    Music music = new Music(title, artist, data);
                    musicList.add(music);
                    // Log.v("xxx", "xxx--> title= " + title + " \t artist= "
                    // + artist + " \t data= " + data);
                }
                c.close();
                return musicList;
            }

            @Override
            protected void onPostExecute(List<Music> result) {
                if (result != null) {
                    if (loaded != null) {
                        loaded.onLoaded(result);
                    } else {
                        Log.e("aaa", "loaded == null fuck");
                    }
                }
                super.onPostExecute(result);
            }
        }.execute();
    }

    public interface OnAudioLoaded {
        void onLoaded(List<Music> musics);
    }
  1. 通过适配器将获取的数据显示在UI 上
private BaseAdapter adapter = new BaseAdapter() {
        class ViewHolder {
            TextView tvTitle;
            TextView tvAuthor;
            ViewGroup pbLayout;
            TextView tv_progress;
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            Music music = mAudioList.get(position);
            ViewHolder holder;
            if (convertView == null) {
                holder = new ViewHolder();
                convertView = mInflater.inflate(R.layout.item, parent, false);
                convertView.setTag(holder);
                holder.tvTitle = (TextView) convertView
                        .findViewById(R.id.tv_title);
                holder.tvAuthor = (TextView) convertView
                        .findViewById(R.id.tv_author);
                holder.pbLayout = (ViewGroup) convertView
                        .findViewById(R.id.pb_layout);
                holder.tv_progress = (TextView) convertView
                        .findViewById(R.id.tv_progress);
            } else {
                holder = (ViewHolder) convertView.getTag();
            }
            holder.tvTitle.setText(music.getTitle());
            holder.tvAuthor.setText(music.getAuthor());
            // long progress2 = music.getProgress();
            // Log.e("aaa", "aa--adapter item progress=" + progress2);
            String progress = mContext.getString(R.string.progress,
                    getCurrentProgress(music));
            holder.tv_progress.setText(progress + "%");
            // default ,don't show the pbLayout
            holder.pbLayout.setVisibility(View.INVISIBLE);
            if (showPbLayout && mCopyingSrcPathList.contains(music.getPath())) {
                holder.pbLayout.setVisibility(View.VISIBLE);
            }
            return convertView;
        }

        private int getCurrentProgress(Music music) {
            long progress = music.getProgress();
            long total = music.getTotal();
            int pro = (int) (progress * 100f / total + 0.5f);
            return pro;
        }

        @Override
        public long getItemId(int position) {
            return position;
        }

        @Override
        public Music getItem(int position) {
            return mAudioList.get(position);
        }

        @Override
        public int getCount() {
            return mAudioList.size();
        }
    };
  1. 根据当前选择的操作模式,处理ListView的Item的点击事件
    /**
     * 设置audio list 的 item的点击
     */
    protected void setItemClick() {
        lvAudio.setOnItemClickListener(new OnItemClickListener() {

            @Override
            public void onItemClick(AdapterView<?> parent, View view,
                    int position, long id) {
                Music music = mAudioList.get(position);
                // Log.e("aaa", "当前点击Item--Path-->" + music.getPath());
                // 根据switch模式的不同,做不同的对应操作
                SwitchState state = SwitchManager.getInstance()
                        .getSwitchState();
                switch (state) {
                case openFile:// 打开文件
                    openFile(view, position);
                    break;
                case cryptFile:// 加密文件
                    // Log.e("aaa", "CRYPT--加密文件:" + music.getPath());
                    handCrypt(view, position);
                    handlerReceiver(view, music);
                    break;
                default:
                    break;
                }
            }

        });

    }
  1. 工具方法 播放音乐文件
public static void openFile(File file, Context context) {

        try {
            Intent intent = new Intent();
            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            // 设置intent的Action属性
            intent.setAction(Intent.ACTION_VIEW);
            // 获取文件file的MIME类型
            String ext = MimeTypeMap.getFileExtensionFromUrl(Uri.fromFile(file)
                    .toString());
            String mimeType = MimeTypeMap.getSingleton()
                    .getMimeTypeFromExtension(ext);
            // 设置intent的data和Type属性。
            intent.setDataAndType(/* uri */Uri.fromFile(file), mimeType);
            // 跳转
            context.startActivity(intent); // 这里最好try一下,有可能会报错。
            // //比如说你的MIME类型是打开邮箱,但是你手机里面没装邮箱客户端,就会报错。
        } catch (Exception e) {
            vtoast(context.getString(R.string.no_that_app), context);
            e.printStackTrace();
        }
    }
  1. 操作模式中点击Item 会进行文件复制的IO操作,放到Service中去处理
private void handCrypt(View view, int position) {
        Context context = view.getContext();
        Log.e("aaa", "--context--" + context);
        Intent serviceIntent = new Intent(context, FileOpService.class);
        serviceIntent.putExtra(Iaudio.srcPath, mAudioList.get(position)
                .getPath());
        ComponentName name = context.startService(serviceIntent);
        Log.v("aaa", "--ComponentName--" + name);
    }
  1. service中会在线程池中去处理IO

    @Override
    public int onStartCommand(final Intent intent, int flags, int startId) {
        mThreadPool.execute(new Runnable() {

            @Override
            public void run() {
                String srcPath = intent.getStringExtra(Iaudio.srcPath);
                File srcFile = new File(srcPath);
                if (srcFile != null && srcFile.exists()) {
                    // TODO
                    Log.e("aaa", "源文件传递OK,开始copy-->" + srcPath);
                    FileOpEntity.getInstance().excute(mContext, srcFile);
                } else {
                    Log.e("aaa", "源文件路径传递有误-->" + srcPath);
                }

            }
        });
        new Thread(new Runnable() {

            @Override
            public void run() {}
        }).start();
        return START_REDELIVER_INTENT;
    }
  1. 文件IO,并将当前进度通过广播发送出去
    /**
     * 
     * @param context
     * @param srcFile
     *            源文件
     * @param destFlo
     *            目标保存目录
     */
    public void excuteCopy(Context context, File srcFile, String destFlo) {
        if (srcFile == null || !srcFile.exists()) {
            return;
        }
        sendStartBroadCast(context, srcFile, 0);
        BufferedInputStream inputStream = null;
        BufferedOutputStream outputStream = null;
        try {
            inputStream = new BufferedInputStream(new FileInputStream(srcFile));

            File destFile = new File(destFlo, srcFile.getName());
            FileOutputStream out;
            out = new FileOutputStream(destFile);
            outputStream = new BufferedOutputStream(out);
            int len = 0;
            long currentProgress = 0;
            byte[] buffer = new byte[1024 * 64];
            while ((len = inputStream.read(buffer)) != -1) {
                SystemClock.sleep(200);// 循环的时候,休眠休眠
                outputStream.write(buffer, 0, len);
                outputStream.flush();
                currentProgress += len;
                // Log.e("aaa",
                // "当前进度--" + currentProgress + " \t 总大小=="
                // + srcFile.length());
                sendProgressBroadCast(context, srcFile, currentProgress);
            }
            // copy结束:删除源文件
            if (srcFile != null && srcFile.exists() && srcFile.isFile()) {
                srcFile.delete();
                int delete = context.getContentResolver().delete(
                        MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
                        Audio.Media.DATA + "=?",
                        new String[] { srcFile.getCanonicalPath() });

                Log.e("aaa", "src delete-- " + delete);// OK 数据库记录删除成功
            }

        } catch (IOException e) {
            e.printStackTrace();
            Log.e("aaa", "文件copy 异常: " + e);
            // 失败删除目标文件
            File destFile = new File(destFlo, srcFile.getName());
            if (destFile != null && destFile.isFile()) {
                destFile.delete();
            }
        } finally {
            sendEndBroadCast(context, srcFile, 100);
            File destFile = new File(destFlo, srcFile.getName());
            if (destFile != null && destFile.isFile()) {
                //添加到数据库,到时候,可以保存到media数据库

            }
            if (outputStream != null) {
                try {
                    outputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    private void sendProgressBroadCast(Context context, File srcFile,
            long currentProgress) {
        Intent intentBroadcast = new Intent();
        intentBroadcast.setAction(Iaudio.ACTION_COPY_PROGRESS);
        // 传递3样东西,源路径,当前进度,总长度
        intentBroadcast.putExtra(Iaudio.progress, currentProgress);
        intentBroadcast.putExtra(Iaudio.total, srcFile.length());
        intentBroadcast.putExtra(Iaudio.srcPath, srcFile.getPath());
        context.sendBroadcast(intentBroadcast);
    }

    private void sendStartBroadCast(Context context, File srcFile,
            long currentProgress) {
        Intent intentBroadcast = new Intent();
        intentBroadcast.setAction(Iaudio.ACTION_COPY_START);
        // 传递3样东西,源路径,当前进度,总长度
        intentBroadcast.putExtra(Iaudio.progress, currentProgress);// 0
        intentBroadcast.putExtra(Iaudio.total, srcFile.length());
        intentBroadcast.putExtra(Iaudio.srcPath, srcFile.getPath());
        context.sendBroadcast(intentBroadcast);
    }

    private void sendEndBroadCast(Context context, File srcFile,
            long currentProgress) {
        Intent intentBroadcast = new Intent();
        intentBroadcast.setAction(Iaudio.ACTION_COPY_END);
        // 传递3样东西,源路径,当前进度,总长度
        intentBroadcast.putExtra(Iaudio.progress, currentProgress);// 100
        intentBroadcast.putExtra(Iaudio.total, srcFile.length());
        intentBroadcast.putExtra(Iaudio.srcPath, srcFile.getPath());
        context.sendBroadcast(intentBroadcast);
    }
  1. 在广播接收器接收进度信息,并通过回调接口,将接收的进度信息存放到回调接口中

public class CopyReceiver extends BroadcastReceiver {

    private static final CopyReceiver COPY_RECEIVER = new CopyReceiver();

    // private OnCopyProgressCallBack mCopyProgressListener;
    private List<OnCopyProgressCallBack> mCopyListeners = Collections
            .synchronizedList(new LinkedList<OnCopyProgressCallBack>());

    private CopyReceiver() {
    }

    public static final CopyReceiver getInstance() {
        return COPY_RECEIVER;
    }

    @Override
    public void onReceive(Context context, Intent intent) {

        // Log.e("aaa", "xxx--》收到广播");
        String action = intent.getAction();
        // Log.e("aaa", "xxx--》收到广播: " + action);
        if (Iaudio.ACTION_COPY_PROGRESS.equals(action)) {// 收到copy进度广播
            // Log.e("aaa", "xx---哈哈哈,收到进度广播中...");
            long currentProgress = 0;
            long total = 0;
            String srcPath = null;
            if (intent.hasExtra(Iaudio.progress)) {
                currentProgress = intent.getLongExtra(Iaudio.progress, 0);
            }

            if (intent.hasExtra(Iaudio.total)) {
                total = intent.getLongExtra(Iaudio.total, 0);
            }
            if (intent.hasExtra(Iaudio.srcPath)) {
                srcPath = intent.getStringExtra(Iaudio.srcPath);
            }
            // Log.e("aaa", "广播收到的: copying... \t progress=" + currentProgress
            // + "\t total=" + total + "\t src=" + srcPath);
            // if (mCopyProgressListener != null) {
            // mCopyProgressListener.onCopyProgress(srcPath, total,
            // currentProgress);
            // }
            if (mCopyListeners != null && mCopyListeners.size() > 0) {
                for (OnCopyProgressCallBack callBack : mCopyListeners) {
                    if (callBack != null) {
                        callBack.onCopyProgress(srcPath, total, currentProgress);
                    }
                }
            }
        } else if (Iaudio.ACTION_COPY_START.equals(action)) {// 收到开始复制的广播
            // Log.e("aaa", "xx---哈哈哈,收到进度广播中...");
            long currentProgress = 0;
            long total = 0;
            String srcPath = null;
            if (intent.hasExtra(Iaudio.progress)) {
                currentProgress = intent.getLongExtra(Iaudio.progress, 0);
            }

            if (intent.hasExtra(Iaudio.total)) {
                total = intent.getLongExtra(Iaudio.total, 0);
            }
            if (intent.hasExtra(Iaudio.srcPath)) {
                srcPath = intent.getStringExtra(Iaudio.srcPath);
            }
            // mCopyProgressListener.onCopyStart(srcPath, total,
            // currentProgress);
            if (mCopyListeners != null && mCopyListeners.size() > 0) {
                for (OnCopyProgressCallBack callBack : mCopyListeners) {
                    if (callBack != null) {
                        callBack.onCopyStart(srcPath, total, currentProgress);
                    }
                }
            }
        } else if (Iaudio.ACTION_COPY_END.equals(action)) {// 收到结束赋值的广播
            // 判断复制成功还是失败先,然后移除进度
            long currentProgress = 0;
            long total = 0;
            String srcPath = null;
            if (intent.hasExtra(Iaudio.progress)) {
                currentProgress = intent.getLongExtra(Iaudio.progress, 0);
            }
            if (intent.hasExtra(Iaudio.total)) {
                total = intent.getLongExtra(Iaudio.total, 0);
            }
            if (intent.hasExtra(Iaudio.srcPath)) {
                srcPath = intent.getStringExtra(Iaudio.srcPath);
            }
            // mCopyProgressListener.onCopyEnd(srcPath, total, currentProgress);
            if (mCopyListeners != null && mCopyListeners.size() > 0) {
                for (OnCopyProgressCallBack callBack : mCopyListeners) {
                    if (callBack != null) {
                        callBack.onCopyEnd(srcPath, total, currentProgress);
                    }
                }
            }
        }
    }

    public void addOnCopyProgressCallBack(OnCopyProgressCallBack callBack) {
        // this.mCopyProgressListener = callBack;
        mCopyListeners.add(callBack);
    }

    public interface OnCopyProgressCallBack {

        void onCopyProgress(String srcPath, long total, long progress);

        void onCopyStart(String srcPath, long total, long progress);

        void onCopyEnd(String srcPath, long total, long progress);
    }

}
  1. 在UI中拿到回调对象,获取其中的进度进行,进行对应Item的刷新
    protected synchronized void handlerReceiver(View view, final Music music) {
        CopyReceiver.getInstance().addOnCopyProgressCallBack(
                new OnCopyProgressCallBack() {
                    @Override
                    public void onCopyProgress(String srcPath, long total,
                            long progress) {
                        if (total == 0) {
                            throw new ArithmeticException("total==0 fuck");
                        }
                        // Log.e("aaa", "ui log==" + progress);
                        if (music.getPath().equals(srcPath)) {
                            adapter.notifyDataSetChanged();
                            // Log.v("aaa",
                            // "musicPath == srcPath-->"
                            // + music.getTitle());
                            music.setProgress(progress);
                            music.setTotal(total);
                            adapter.notifyDataSetChanged();
                            // Log.i("aaa", "adapter.notifyDataSetChanged|-|");
                        }
                    }

                    @Override
                    public void onCopyStart(String srcPath, long total,
                            long progress) {
                        showPbLayout = true;
                        // mCurrentSrcPath = srcPath;
                        mCopyingSrcPathList.add(srcPath);
                    }

                    @Override
                    public void onCopyEnd(String srcPath, long total,
                            long progress) {
                        // showPbLayout = false;
                        // mCurrentSrcPath = srcPath;
                        // mCopyingSrcPathList.add(srcPath);
                        if (srcPath == null)
                            return;
                        if (mCopyingSrcPathList.contains(srcPath)) {
                            mCopyingSrcPathList.remove(srcPath);
                        }
                        File file = new File(srcPath);
                        if (file != null && file.isFile()) {
                            // 说明复制失败保留了源文件
                        } else {
                            // 说明源文件已经被删除掉了!
                            Log.e("aaa", "#####--position=" + music + "^^size="
                                    + mAudioList.size());
                            // TODO
                            // Music music = mAudioList.get(music);// 角标越界了!!!
                            // 因为这里的position还是之前点击的时候的position,不过因为有移除的操作,
                            // 所以size会减少,而position并没有更新,所以会角标越界
                            if (music.getPath().equals(srcPath)) {
                                // 就是要移除这个
                                mAudioList.remove(music);
                            } else {
                                // position与file不对应了
                                Music m = null;
                                for (Music mu : mAudioList) {
                                    if (mu.getPath().equals(srcPath)) {
                                        m = mu;
                                        break;
                                    }
                                }
                                if (m != null) {
                                    // 说明虽然不对应,但是复制成功了,且源文件还在view中
                                    mAudioList.remove(m);
                                }
                            }
                            adapter.notifyDataSetChanged();
                        }
                    }
                });

    }

  • 差不多以上就是这个项目的整个逻辑流程了。并不会很混乱,这也是一个自我感觉比较良好的Demo。
  • 而且这个项目最好的地方,并不是文件扫描,Item刷新,而是Activity没有很臃肿。各种生命周期的乱搞一气,并没有。
  • 不过,倒是有一个BUG,刚刚发现的,就是如果你在一个文件复制结束之前退出app,然后再次进入,再次点击该Item进行文件复制,会出现Item 进度的刷新混乱,并且在复制文件完成时会退出app. 解释一下原因。

    • 混乱是因为被再次复制导致的,前一次复制并没有结束,所以会一直有进度,而本次的点击会再次进行相同的复制。所以会出现两个进度在同一个Item上面,于是进度出现了混乱。
    • 至于,如果进入之后,点击了其他Item,而上次操作未完成的Item为什么不显示进度呢?是因为已经错过了获取之前的进度信息回调的时机,所以不会有数据传递到UI。而且,不仅仅错过了进度回调的时机,还因为进度回调的显示需要获取当前点击的Item的文件信息,因为是再次进入,也就没有了该文件的信息了。
    • 最后,为什么复制完成会退出app?因为第一次复制完成,删除了源文件,而第二次的复制并没有完成,依然处于循环的IO过程中,而这时源文件被删除了。因为我在进度刷新的 时候做了一个逻辑判断,如果文件大小为0就抛出一个运行异常,所以app退出了。(这个处理有待改进)
  • 哈哈哈哈

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值