Android app中有些需求是刷新ListView中的Item的进度条。
- 比如下载列表这样子的就需要同时刷新不同Item的下载进度。
- 于是基于这样的一个需求,我做了一个比较完整的Demo专门用于刷新ListView的Item的进度的。
- 当然,这个Demo与下载是无关的。不过也是刷新Item的进度的。
项目介绍
- 功能:扫描手机中的所有歌曲文件,添加到ListView中显示,根据当前选择的操作模式[播放模式,文件操作模式],默认是播放模式。播放模式时点击Item,播放对应的歌曲;操作模式时,复制当前文件到
/temp
目录中(刻意缓慢复制,以便模拟进度刷新)。播放模式中,点击Item弹出选择播放器界面,选择一个播放器就可以进行音乐播放,如果手机中没有音乐播放器,可能会挂掉app。(bug忘修复了)…….如果是文件操作模式,点击Item就立马进行当前文件复制,并显示复制进度,复制完成删除源文件,删除该Item,目标文件在/temp
目录中可以找到。手机自带的音乐播放器一般也能扫描到。准备在app退出的时候添加/temp
中的音乐文件到系统数据库,db包还没有完成,就没有弄了。 - 文件操作模式中,理论上可以同时复制无限多个文件。具体限度取决于手机内存。
- 代码界面相对清晰,不多做介绍。这里贴出关键代码。
- 扫描本地音乐文件,异步返回扫描结果
/**
* 异步加载本地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);
}
- 通过适配器将获取的数据显示在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();
}
};
- 根据当前选择的操作模式,处理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;
}
}
});
}
- 工具方法 播放音乐文件
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();
}
}
- 操作模式中点击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);
}
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;
}
- 文件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);
}
- 在广播接收器接收进度信息,并通过回调接口,将接收的进度信息存放到回调接口中
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);
}
}
- 在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退出了。(这个处理有待改进)