基于android的网络音乐播放器-网络音乐的多线程下载(六)

作为android初学者,最近把疯狂android讲义和疯狂Java讲义看了一遍,看到书中介绍的知识点非常多,很难全部记住,为了更好的掌握基础知识点,我将开发一个网络音乐播放器-EasyMusic来巩固下,也当作是练练手。感兴趣的朋友可以看看,有设计不足的地方也欢迎指出。

开发之前首先介绍下该音乐播放器将要开发的功能(需求):

1.本地音乐的加载和播放;

2.网络音乐的搜索,试听和下载;

3.音乐的断点下载;

4.点击播放图标加载专辑图片,点击歌词加载歌词并滚动显示(支持滑动歌词改变音乐播放进度);

5.支持基于popupWindow的弹出式菜单;

6.支持后台任务栏显示和控制。

该篇主要是介绍实现多线程下载 :
首先介绍下多线程下载任务的建立过程
1.在netMusicFragment界面点击搜索到的网络音乐,弹出确认下载提示框,点击确认开始建立下载任务:

netMusicListView.setOnItemClickListener(new OnItemClickListener() {
            public void onItemClick(AdapterView<?> parent, View view, final int position, long id) {
                new AlertDialog.Builder(mContext)
                .setTitle("下载提示")
                .setMessage("是否下载该歌曲?")
                .setNegativeButton("取消", new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int which) {
                    }   
                })
                //点击搜索到的网络音乐列表的第position项即可建立下载任务下载对应歌曲
                .setPositiveButton("确定", new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int which) {
                        createDownloadTask(position);
                    }
                }).show();
            }
        });


//创建下载任务,网络音乐列表netMusicList中有对应position位置处的音乐信息
    protected void createDownloadTask(int position) {
        String url = (String)netMusicList.get(position).get("audio");
        String title = (String)netMusicList.get(position).get("title");
        String artist = (String)netMusicList.get(position).get("artist");
        String targetFile = MainActivity.downloadedPath + "/" + title + ".mp3";
        //这里的下载线程暂时设置为固定的数值3,有兴趣的可以改成手动选择线程数量
        DownloadUtil downloadUtil = new DownloadUtil(url, targetFile, 3, mContext);
        Log.d(TAG, "DownloadUrl = " + url);
        Map<String,Object> map = new HashMap<String, Object>();
        map.put("title", title);
        map.put("artist", artist);
        map.put("url", url);
        map.put("downloadUtil", downloadUtil);
        //主activity的executeDownloadUtil方法里调用service执行下载任务
        ((MainActivity)this.getActivity()).executeDownloadUtil(downloadUtil);
        DownloadFragment.downloadingMusic.add(map);
    }

2.通过MainActivity来调用服务执行后台下载。

public void executeDownloadUtil(DownloadUtil util) {
        Toast.makeText(mContext, "executeDownloadUtil", Toast.LENGTH_LONG)
        .show();
        musicService.downloadMusic(util);
    }

musicService执行下载方法downloadMusic(util),将下载任务放进线程池中,当线程池中有空闲的线程时会开始下载。之所以将下载任务的执行放在service中是因为service可以后台运行,而如果在activity中执行下载任务的话,后台运行播放器的时候下载任务会暂停。musicService的下载相关代码如下:

public void downloadMusic(final DownloadUtil util) {
        //传进来的下载任务将放入线程池
        pool.execute(new DownloadRunnable(util));
    }

    private class DownloadRunnable implements Runnable {
        private DownloadUtil util;

        public DownloadRunnable(DownloadUtil util) {
            this.util = util;
        }

        @Override
        public void run() {
            util.download();
        }

    }

多线程下载工具的代码如下,相关注释已经写的很清楚了。

package com.sprd.easymusic.util;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;

import android.content.Context;
import android.content.Intent;
import android.media.MediaScannerConnection;
import android.media.MediaScannerConnection.MediaScannerConnectionClient;
import android.net.Uri;
import android.util.Log;
import android.widget.Toast;

public class DownloadUtil {
    private String TAG = "DownloadUtil";
    private String targetUrl = null;
    private String targetFile = null;
    //多线程下载的线程数量
    private int threadNum;
    private Context mContext;
    //下载文件的大小
    private int fileSize;
    //下载任务是否是暂停状态
    private boolean pause;
    //下载任务是否被删除了
    private boolean delete;
    //下载完成的线程数,当所有线程下载完成时该值等于threadNum
    private int downloadSuccessThread;
    //下载的线程数组
    private DownloadThread[] downloadThreads;

    public DownloadUtil (String targetUrl, String targetFile, int threadNum, Context context) {
        this.targetUrl = targetUrl;
        this.targetFile = targetFile;
        this.threadNum = threadNum;
        downloadThreads = new DownloadThread[threadNum];
        mContext = context;
    }

    public void download() {
        try {
            URL url = new URL(targetUrl);
            HttpURLConnection conn = (HttpURLConnection)url.openConnection();
            conn.setConnectTimeout(5000);
            //获取下载文件的大小
            fileSize = conn.getContentLength();
            conn.disconnect();
            Log.d(TAG, "download:fileSize = " + fileSize);
            File file = new File(targetFile);
            if (!file.exists()) {
                file.createNewFile();
                Log.d(TAG, "create file:" + file);
            }
            RandomAccessFile raf = new RandomAccessFile(file, "rwd");
            raf.setLength(fileSize);
            raf.close();
            //划分每个线程的下载长度
            int currentFileSize = fileSize / threadNum == 0 ? fileSize / threadNum : fileSize / threadNum + 1;
            for (int i=0; i<threadNum; i++) {
                downloadThreads[i] = new DownloadThread(targetUrl, targetFile, currentFileSize * i, currentFileSize);
                downloadThreads[i].start();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    //获取线程进度
    public int getDownloadProgress() {
        int downloadLength = 0;
        for (DownloadThread thread : downloadThreads) {
            if (thread != null) downloadLength += thread.length;
        }
        Log.d(TAG, "fileSize = " + fileSize + " downloadLength = " +downloadLength);
        return fileSize > 0 ? downloadLength * 100 / fileSize: 0;
    }

    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 String downloadUrl = null;
        //下载内容到file文件中
        private String file;
        //下载的起始位置
        private int startPos;
        //需要下载的长度
        private int currentFileSize;
        //读取网络音乐数据的缓存
        private BufferedInputStream bis;

        public DownloadThread (String downloadUrl, String file, int startPos, int currentFileSize) {
            this.downloadUrl = downloadUrl;
            this.file = file;
            this.startPos = startPos;
            this.currentFileSize = currentFileSize;
        }

        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];
                while (length < currentFileSize && (hasRead = bis.read(buff)) > 0 && !delete) {
                    while (pause){
                        Log.d(TAG, "DownloadUtil pause!");
                    }
                    if (!pause) {
                        //字节读入对应文件
                        raf.write(buff, 0, hasRead);
                        length += hasRead;
                        Log.d(TAG, "read " + hasRead + " bytes");
                    }
                }
                Log.d(TAG, "download success? " + (fileSize == length));
                //关闭资源和链接
                raf.close();
                bis.close();
                if (delete) {
                    boolean isDeleted = downloadFile.delete();
                    Log.d(TAG, "delete file success ? " + isDeleted);
                } else {
                    Log.d(TAG, "run:currentFileSize = " + currentFileSize + " downloadLength = " + length);
                    //当前线程下载完成时,DownloadUtil的downloadSuccessThread值+1
                    downloadSuccessThread++;
                    if (downloadSuccessThread == threadNum) {
                        Log.d(TAG, "download success");
                        Intent notifyIntent = new Intent("action_download_success");
                        notifyIntent.putExtra("url", targetUrl);
                        //给DownloadFragment发送下载完成的广播
                        mContext.sendBroadcast(notifyIntent);
                        scanFileToMedia(targetFile);
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

    }

    //将新下载的歌曲扫描到媒体库中,为后面MusicListFragment添加下拉刷新做铺垫
    public void scanFileToMedia(final String url) {
        new Thread(new Runnable() {
            public void run() {
                MediaScannerConnection.scanFile(mContext, new String[] {url}, null,
                    new MediaScannerConnection.OnScanCompletedListener() {
                        public void onScanCompleted(String path, Uri uri) {
                            Log.d(TAG, "scan completed : file = " + url);
                        }
                    });
            }

        }).start();
    }

}

其中实现多线程下载的关键是多个线程的任务分割(每个线程下载的起始位置以及需要下载的长度-最后一个线程下载长度需要额外注意,因为其下载长度可能与前面的线程不同)

最后不要忘了在AndroidManifest中添加权限(读写SD卡,创建和删除文件,连接网络):

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
    <uses-permission android:name="android.permission.INTERNET"/>

音乐播放器已完成,下载地址:
Android音乐播放器

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值