作为android初学者,最近把疯狂android讲义和疯狂Java讲义看了一遍,看到书中介绍的知识点非常多,很难全部记住,为了更好的掌握基础知识点,我将开发一个网络音乐播放器-EasyMusic来巩固下,也当作是练练手。感兴趣的朋友可以看看,有设计不足的地方也欢迎指出。
开发之前首先介绍下该音乐播放器将要开发的功能(需求):
1.本地音乐的加载和播放;
2.网络音乐的搜索,试听和下载;
3.音乐的断点下载;
4.点击播放图标加载专辑图片,点击歌词加载歌词并滚动显示(支持滑动歌词改变音乐播放进度);
5.支持基于popupWindow的弹出式菜单;
6.支持后台任务栏显示和控制。
该篇主要是介绍下载控制界面的功能-下载的暂停/继续/删除/断点下载:
这里分为两部分将,第一部分介绍下载的暂停/继续/删除,这部分比较简单,稍微介绍下,然后第二部分重点介绍下断点下载。
下载任务的控制代码主要在DownloadFragment.java中,其布局是一个ExpandableListView,分为“最近已完成下载”和“正在下载”两组,已完成下载中只显示下载完成音乐的歌名和演唱者,正在下载界面除了显示歌名和演唱者外,还添加了暂停/继续的ImageButton和删除的ImageButton,两个按钮分别控制下载任务的暂停/继续和删除。
1.下载任务的暂停/继续/删除
该实现主要在适配器中,代码如下:
// 音乐列表适配器
private BaseExpandableListAdapter downloadMusicListAdapter = new BaseExpandableListAdapter() {
public int getGroupCount() {
return musicDownloadGroup.length;
}
public int getChildrenCount(int groupPosition) {
if (groupPosition == 0) {
return downloadedMusic.size();
} else {
return downloadingMusic.size();
}
}
public Object getGroup(int groupPosition) {
return musicDownloadGroup[groupPosition];
}
public Object getChild(int groupPosition, int childPosition) {
if (groupPosition == 0) {
return downloadedMusic.get(childPosition);
} else {
return downloadingMusic.get(childPosition);
}
}
public long getGroupId(int groupPosition) {
return groupPosition;
}
public long getChildId(int groupPosition, int childPosition) {
return childPosition;
}
public boolean hasStableIds() {
return true;
}
public View getGroupView(int groupPosition, boolean isExpanded,
View convertView, ViewGroup parent) {
TextView tv = new TextView(mContext);
tv.setText((String) getGroup(groupPosition));
tv.setTextSize(18);
tv.setPadding(10, 10, 10, 10);
return tv;
}
public View getChildView(int groupPosition, final int childPosition,
boolean isLastChild, View convertView, ViewGroup parent) {
View view = convertView;
Map<String, Object> item = null;
if (groupPosition == 0) {
item = downloadedMusic.get(childPosition);
view = inflater.inflate(R.layout.downloadedmusiclist_item, null);
} else {
item = downloadingMusic.get(childPosition);
view = inflater.inflate(R.layout.downloadingmusiclist_item, null);
}
String title = (String) item.get("title");
String artist = (String) item.get("artist");
TextView musicTitle = (TextView) view.findViewById(R.id.musicTitle);
musicTitle.setTag("title");
TextView musicArtist = (TextView) view.findViewById(R.id.musicArtist);
musicTitle.setText(title);
musicArtist.setText(artist);
if (groupPosition == 1) {//groupPosition == 1表明为正在下载的列表,需要更新UI
final ProgressBar progressBar = (ProgressBar)view.findViewById(R.id.progressBar1);
progressBar.setTag("progressBar");
//获取对应的DownloadUtil以便获取下载进度
final DownloadUtil util = (DownloadUtil)item.get("downloadUtil");
progressBar.setProgress(util.getDownloadProgress());
Log.d(TAG, title + ":下载进度为" + util.getDownloadProgress() + "%");
final ImageView pause = (ImageView)view.findViewById(R.id.pause);
pause.setTag("pause");
pause.setImageResource(util.isPause()? android.R.drawable.ic_media_play : android.R.drawable.ic_media_pause);
pause.setOnClickListener(new OnClickListener() {//暂停或者继续下载任务的监听
public void onClick(View v) {
if (util.isPause()) {
util.setPause(false);
pause.setImageResource(android.R.drawable.ic_media_pause);
} else {
util.setPause(true);
pause.setImageResource(android.R.drawable.ic_media_play);
}
}
});
final ImageView delate = (ImageView)view.findViewById(R.id.delete);
delate.setTag("delate");
delate.setOnClickListener(new OnClickListener() {//删除下载任务的监听
public void onClick(View v) {
util.setDelete(true);
downloadingMusic.remove(childPosition);
downloadMusicListAdapter.notifyDataSetChanged();
Toast.makeText(mContext, "删除成功", Toast.LENGTH_LONG)
.show();
}
});
}
return view;
}
控制音乐下载的状态实现主要在DownloadUtil,其关键代码为
public class DownloadUtil {
......
private boolean pause;
private boolean delete;
......
public boolean isPause() {
return pause;
}
public void setPause(boolean pause) {
this.pause = pause;
}
public boolean isDelete() {
return delete;
}
public void setDelete(boolean delete) {
this.delete = delete;
}
private class DownloadThread extends Thread {
public int length;
private int id;
private String downloadUrl = null;
private String file;
private int startPos;
private int currentFileSize;
private BufferedInputStream bis;
public DownloadThread (String downloadUrl, String file, int id, int startPos, int currentFileSize) {
this.downloadUrl = downloadUrl;
this.file = file;
this.id = id;
this.startPos = startPos;
this.currentFileSize = currentFileSize;
}
public void run() {
try {
HttpURLConnection conn = (HttpURLConnection)new URL(downloadUrl).openConnection();
conn.setConnectTimeout(5000);
conn.setRequestMethod("GET");
//conn.setRequestProperty("Range", "bytes =" + currentFileSize);
InputStream is = conn.getInputStream();
is.skip(startPos);
File downloadFile = new File(file);
RandomAccessFile raf = new RandomAccessFile(downloadFile, "rwd");
raf.seek(startPos);
bis = new BufferedInputStream(is);
int hasRead = 0;
byte[] buff = new byte[1024*4];
//delete为false时一直读取数据,delete置为true时将会中断数据读取并删除对应文件
while (length < currentFileSize && (hasRead = bis.read(buff)) > 0 && !delete) {
while (pause){//pause为true时暂停数据的读取
Log.d(TAG, "DownloadUtil pause!");
}
if (!pause) {
if (length + hasRead > currentFileSize) {
int lastRead = currentFileSize - length;
raf.write(buff, 0, lastRead);
length += lastRead;
Log.d(TAG, "last read " + lastRead + " bytes");
updateDownloadedMusicInfo(id, lastRead);
} else {
raf.write(buff, 0, hasRead);
length += hasRead;
Log.d(TAG, "read " + hasRead + " bytes");
updateDownloadedMusicInfo(id, hasRead);
}
//downloadedFileSize[id] += hasRead;
}
}
Log.d(TAG, "download success? " + (currentFileSize == length));
raf.close();
bis.close();
//delete为true则删除文件
if (delete) {
boolean isDeleted = downloadFile.delete();
Log.d(TAG, "delete file success ? " + isDeleted);
} else {
Log.d(TAG, "run:currentFileSize = " + currentFileSize + " downloadLength = " + length);
downloadSuccessThread++;
if (downloadSuccessThread == threadNum) {
Log.d(TAG, "download success");
removeDownloadingMusic();
Intent notifyIntent = new Intent("action_download_success");
notifyIntent.putExtra("url", targetUrl);
mContext.sendBroadcast(notifyIntent);
scanFileToMedia(targetFile);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
这样就可以通过两个逻辑变量pause和delete分别控制下载任务的暂停/继续和删除了。
2.断点下载
断点下载的思路:
1>下载任务DownloadUtil建立时将对应歌曲信息(标题,作家,资源链接,目标路径,文件大小,线程数量,每个线程负责长度,已完成下载)存入数据表downloading_music中,其中已完成下载是为了同时记录所有线程已完成下载的长度而自定义的一种编码方式,比如三个线程分别完成了3,4和5,那么已完成下载记为3/4/5,对应读取的时候以“/”分割一下即可。
2>每当有一个线程往文件中写入一次数据时就更新对应的数据表信息中的已完成下载。
3>下载未完成时退出应用,重新进入时会读取数据表downloading_music,读取到未完成下载任务时会建立一个新的下载任务并置为暂停状态(等待用户点击继续下载),其中每个线程的起点需要跳过已完成的长度,对应负责下载的长度也会减少,减少的长度等于上一次已完成的长度。
4>点击继续下载,音乐下载完成后数据表downloading_music会移除该任务。
主要实现代码如下:
1.正在下载任务信息的录入
public class DownloadUtil {
......
private int[] downloadedFileSize;
......
public DownloadUtil (String title, String artist, String targetUrl, String targetFile, int threadNum,
Context context, MyDBHelper myHelper) {
......
downloadedFileSize = new int[threadNum];
}
public void download() {
......
for (int i=0; i<threadNum; i++) {
downloadThreads[i] = new DownloadThread(targetUrl, targetFile, i, currentFileSize * i, currentFileSize);
downloadThreads[i].start();
}
saveDownloadingTask();
} catch (Exception e) {
e.printStackTrace();
}
}
}
//将正在下载的任务录入数据表downloading_music中
private void saveDownloadingTask() {
db.execSQL("insert into downloading_music values(null,?,?,?,?,?,?,?,?)", new Object[] {
title,artist,targetUrl,targetFile,fileSize,threadNum,currentFileSize,encodeDownloadedSize(downloadedFileSize)});
Log.d(TAG, "saveDownloadingTask");
}
//将多个线程的已完成下载编码
private String encodeDownloadedSize(int[] size) {
StringBuilder sb = new StringBuilder();
for (int i: size) {
sb.append(i + "/");
}
Log.d(TAG, "encodeDownloadedSize:value = " + sb);
return sb.toString();
}
2.正在下载任务的信息更新
public class DownloadUtil {
......
private class DownloadThread extends Thread {
......
public void run() {
try {
HttpURLConnection conn = (HttpURLConnection)new URL(downloadUrl).openConnection();
conn.setConnectTimeout(5000);
conn.setRequestMethod("GET");
InputStream is = conn.getInputStream();
is.skip(startPos);
File downloadFile = new File(file);
RandomAccessFile raf = new RandomAccessFile(downloadFile, "rwd");
raf.seek(startPos);
bis = new BufferedInputStream(is);
int hasRead = 0;
byte[] buff = new byte[1024*4];
//delete置为true后会马上停止读取数据,之后会删除对应文件
while (length < currentFileSize && (hasRead = bis.read(buff)) > 0 && !delete) {
while (pause){//暂停状态不读取数据,也不退出数据区,等待继续读取
Log.d(TAG, "DownloadUtil pause!");
}
if (!pause) {
if (length + hasRead > currentFileSize) {
int lastRead = currentFileSize - length;
raf.write(buff, 0, lastRead);
length += lastRead;
Log.d(TAG, "last read " + lastRead + " bytes");
updateDownloadedMusicInfo(id, lastRead);
} else {
raf.write(buff, 0, hasRead);
length += hasRead;
Log.d(TAG, "read " + hasRead + " bytes");
updateDownloadedMusicInfo(id, hasRead);
}
//downloadedFileSize[id] += hasRead;
}
}
Log.d(TAG, "download success? " + (currentFileSize == length));
raf.close();
bis.close();
......
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
//更新数据表中的下载信息
private void updateDownloadedMusicInfo(int id, int length) {
downloadedFileSize[id] += length;
db.execSQL("update downloading_music set downloadedSize = ? " +
"where title = ?", new Object[]{encodeDownloadedSize(downloadedFileSize), title});
Log.d(TAG, "updateDownloadedMusicInfo:id = " + id + "downloadedSize = " + downloadedFileSize[id]);
}
3.退出应用并重新进入时调取未完成任务
public class DownloadFragment extends Fragment {
......
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
......
checkDownloadingMusic();
Log.d(TAG, "onCreate:downloadingMusic.siez=" + downloadingMusic.size());
}
......
//初始化该Fragment检查数据库downloading_music表中是否有未完成的下载任务
private void checkDownloadingMusic() {
Cursor cursor = a.getMyHelper().getReadableDatabase().rawQuery("select * from downloading_music", null);
while (cursor.moveToNext()) {
Map<String, Object> map = new HashMap<String, Object>();
String title = cursor.getString(1);
String artist = cursor.getString(2);
String url = cursor.getString(3);
String targetFile = cursor.getString(4);
String fileSize = cursor.getString(5);
String threadNum = cursor.getString(6);
String currentSize = cursor.getString(7);
String downloadSize = cursor.getString(8);
map.put("title", title);
map.put("artist", artist);
map.put("url", url);
DownloadUtil util = new DownloadUtil(title,artist,url,targetFile,
Integer.parseInt(threadNum), mContext, a.getMyHelper());
map.put("downloadUtil", util);
util.setPause(true);
util.continueDownloading(Integer.parseInt(fileSize),Integer.parseInt(currentSize),downloadSize);
downloadingMusic.add(map);
}
}
}
//断点继续下载
public void continueDownloading(int fileSize, int currentSize, String size) {
Log.d(TAG, "continueDownloading:fileSize = " + fileSize);
this.fileSize = fileSize;
this.currentFileSize = currentSize;
decodeDownloadedSize(size);
for (int i=0; i<threadNum; i++) {
downloadThreads[i] = new DownloadThread(targetUrl, targetFile, i,
currentFileSize * i + downloadedFileSize[i], currentFileSize - downloadedFileSize[i]);
downloadThreads[i].start();
}
}
//解码多线程的已完成下载
private void decodeDownloadedSize(String size) {
String[] str = size.split("/");
Log.d(TAG, "threadNum = " + str.length);
for (int i=0; i<str.length; i++) {
if (str[i] == null) {
downloadedFileSize[i] = 0;
return;
}
downloadedFileSize[i] = Integer.parseInt(str[i]);
Log.d(TAG, "decodeDownloadedSize:downloadedFileSize[" + i + "]=" + str[i]);
}
}
4.下载完成或者删除下载时将下载信息移出downloading_music数据表中
while (length < currentFileSize && (hasRead = bis.read(buff)) > 0 && !delete) {
while (pause){//暂停状态不读取数据,也不退出数据区,等待继续读取
Log.d(TAG, "DownloadUtil pause!");
}
if (!pause) {
if (length + hasRead > currentFileSize) {
int lastRead = currentFileSize - length;
raf.write(buff, 0, lastRead);
length += lastRead;
Log.d(TAG, "last read " + lastRead + " bytes");
updateDownloadedMusicInfo(id, lastRead);
} else {
raf.write(buff, 0, hasRead);
length += hasRead;
Log.d(TAG, "read " + hasRead + " bytes");
updateDownloadedMusicInfo(id, hasRead);
}
//downloadedFileSize[id] += hasRead;
}
}
Log.d(TAG, "download success? " + (currentFileSize == length));
raf.close();
bis.close();
//删除文件
if (delete) {
boolean isDeleted = downloadFile.delete();
Log.d(TAG, "delete file success ? " + isDeleted);
removeDownloadingMusic();
} else {
Log.d(TAG, "run:currentFileSize = " + currentFileSize + " downloadLength = " + length);
downloadSuccessThread++;
if (downloadSuccessThread == threadNum) {
Log.d(TAG, "download success");
//下载完成移出数据表
removeDownloadingMusic();
Intent notifyIntent = new Intent("action_download_success");
notifyIntent.putExtra("url", targetUrl);
mContext.sendBroadcast(notifyIntent);
scanFileToMedia(targetFile);
}
}
//移除下载任务
private void removeDownloadingMusic() {
db.execSQL("delete from downloading_music where fileSize = " + fileSize);
Log.d(TAG, "removeDownloadingMusic:" + title + "下载完成,移出数据库");
}
音乐播放器已完成,下载地址:
Android音乐播放器