本实例实现多线程下载功能,使用多线程下载李健的一张图片和《假如爱有天意》这首歌,可进行播放。
注:本实例未进行SD卡判断等一般细节的优化,但实现主要功能。
下载地址:https://github.com/zhuanghongji/MultiThreadDownPlayerZhj
Manifest.xml 中添加权限:
<!-- 在SD卡中创建与删除文件权限 --> <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/> <!-- 向SD卡写入数据权限 --> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <!-- 授权访问网络 --> <uses-permission android:name="android.permission.INTERNET"/>
MainActivity.java :
public class MainActivity extends AppCompatActivity { private ImageView mImageView; private DownUtil mPictureDownUtil, mSongDownUtil; private MediaPlayer mMediaPlayer; private ProgressBar mPitureProBar, mSongProBar; private int mPitureDownStatus, mSongDownStatus; private Button mPictureDownBtn, mSongDownBtn; private SeekBar mSongSeekBar; private int mPlayStatus; private ImageButton mPlayBtn; private double mDuration; Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { switch (msg.what) { case 1: // 改变图片的下载进度条 mPitureProBar.setProgress(mPitureDownStatus); break; case 2: // 图片下载完成后,设置歌手图片 Bitmap bitmap = BitmapFactory.decodeFile("/mnt/sdcard/李健.jpg"); mImageView.setImageBitmap(bitmap); break; case 3: // 改变歌曲文件的下载进度条 mSongProBar.setProgress(mSongDownStatus); break; case 4: // 改变播放的进度 mSongSeekBar.setProgress(mPlayStatus); break; case 5: // 播放完成后更改播放按钮图 mPlayBtn.setImageResource(R.drawable.start); break; } } }; public static final String URL_DOWNLOAD_JPG = "http://upload.timedg.com/2015/0228/1425097230412.jpg"; public static final String URL_DOWNLOAD_MP3 = "http://music.baidu.com/data/music/file?link=" + "http://yinyueshiting.baidu.com/data2/music/" + "245302327/2452981731439064061128.mp3?xcode=10c72737bdd9a2f07ed60eb536e08c87&song_id=245298173"; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initViews(); intiEvents(); initMediaPlay(); } /** * 初始化播放器 */ private void initMediaPlay() { try { mMediaPlayer = new MediaPlayer(); mMediaPlayer.setDataSource("/mnt/sdcard/假如爱有天意.mp3"); mMediaPlayer.prepare(); Log.i("Duration", String.valueOf(mMediaPlayer.getDuration())); Log.i("CurrentPosition", String.valueOf(mMediaPlayer.getCurrentPosition())); } catch (IOException e) { e.printStackTrace(); } new Thread() { @Override public void run() { final Timer timer = new Timer(); timer.schedule(new TimerTask() { @Override public void run() { double completeRate = mMediaPlayer.getCurrentPosition(); double duration = mMediaPlayer.getDuration(); mPlayStatus = (int) (completeRate * 100 / duration); mHandler.sendEmptyMessage(4); Log.i("CurrentPosition", String.valueOf(mMediaPlayer.getCurrentPosition())); if (mMediaPlayer.getDuration() - completeRate <= 1000) { timer.cancel(); mHandler.sendEmptyMessage(5); } } }, 0, 100); } }.start(); } /** * 初始化事件 */ private void intiEvents() { mPictureDownBtn.setOnClickListener(new MyOnClickListener()); mSongDownBtn.setOnClickListener(new MyOnClickListener()); mPlayBtn.setOnClickListener(new MyOnClickListener()); mSongSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { @Override public void onProgressChanged(SeekBar seekBar, int i, boolean b) { } @Override public void onStartTrackingTouch(SeekBar seekBar) { mMediaPlayer.pause(); } @Override public void onStopTrackingTouch(SeekBar seekBar) { int progress = mSongSeekBar.getProgress(); mDuration = mMediaPlayer.getDuration(); mMediaPlayer.seekTo((int) (progress * mDuration / 100)); mMediaPlayer.start(); } }); } /** * 初始化控件 */ private void initViews() { mImageView = (ImageView) findViewById(R.id.iv_singer_picture); mPitureProBar = (ProgressBar) findViewById(R.id.bar_dowmload_picture); mSongProBar = (ProgressBar) findViewById(R.id.bar_dowmload_song); mPictureDownBtn = (Button) findViewById(R.id.btn_download_picture); mSongDownBtn = (Button) findViewById(R.id.btn_download_song); mSongSeekBar = (SeekBar) findViewById(R.id.seekbar_song_play); mPlayBtn = (ImageButton) findViewById(R.id.btn_song_play); } public void makeToast(String s) { Toast.makeText(MainActivity.this, s, Toast.LENGTH_SHORT).show(); } private class MyOnClickListener implements OnClickListener { @Override public void onClick(View view) { switch (view.getId()) { case R.id.btn_download_picture: downloadPicture(); break; case R.id.btn_download_song: makeToast("歌曲下载"); downloadSong(); break; case R.id.btn_song_play: playSong(); break; } } } /** * 下载歌手图片 */ private void downloadPicture() { // 初始化DownUtil对象(最后一个参数指定线程数) mPictureDownUtil = new DownUtil(URL_DOWNLOAD_JPG, "/mnt/sdcard/李健.jpg", 8); new Thread() { @Override public void run() { try { // 开始下载 mPictureDownUtil.download(); } catch (Exception e) { e.printStackTrace(); } // 定义每0.1秒调度获取一次系统的完成进度 final Timer timer = new Timer(); timer.schedule(new TimerTask() { @Override public void run() { // 获取下载任务的完成比率 double completeRate = mPictureDownUtil.getCompleteRate(); mPitureDownStatus = (int) (completeRate * 100); // 发送消息通知界面更新进度条 mHandler.sendEmptyMessage(1); // 下载完全后取消任务调度 if (mPitureDownStatus >= 100) { timer.cancel(); mHandler.sendEmptyMessage(2); } } }, 0, 100); } }.start(); } /** * 下载歌曲文件 */ private void downloadSong() { // 初始化DownUtil对象(最后一个参数指定线程数) mSongDownUtil = new DownUtil(URL_DOWNLOAD_MP3, "/mnt/sdcard/假如爱有天意.mp3", 1); new Thread() { @Override public void run() { try { // 开始下载 mSongDownUtil.download(); } catch (Exception e) { e.printStackTrace(); } // 定义每0.1秒调度获取一次系统的完成进度 final Timer timer = new Timer(); timer.schedule(new TimerTask() { @Override public void run() { // 获取下载任务的完成比率 double completeRate = mSongDownUtil.getCompleteRate(); mSongDownStatus = (int) (completeRate * 100); // 发送消息通知界面更新进度条 mHandler.sendEmptyMessage(3); // 下载完全后取消任务调度 if (mSongDownStatus >= 100) { timer.cancel(); } } }, 0, 100); } }.start(); } /** * 播放歌曲 */ private void playSong() { boolean isPlaying = mMediaPlayer.isPlaying(); if (isPlaying == false) { mMediaPlayer.start(); mPlayBtn.setImageResource(R.drawable.pause); } else if ((isPlaying == true)) { mMediaPlayer.pause(); mPlayBtn.setImageResource(R.drawable.start); } } }
DownUtil.java :
public class DownUtil { // 定义下载资源的路径 private String path; // 指定所下载的文件的保存位置 private String targetFile; // 定义需要使用多少线程下载资源 private int threadNum; // 定义下载的线程对象 private DownThread[] threads; // 定义下载的文件的总大小 private long fileSize; public DownUtil(String path, String targetFile, int threadNum) { this.path = path; this.threadNum = threadNum; // 初始化threads数组 threads = new DownThread[threadNum]; this.targetFile = targetFile; } public void download() throws Exception { URL url = new URL(path); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setConnectTimeout(5 * 1000); conn.setRequestMethod("GET"); conn.setRequestProperty( "Accept", "image/gif, image/jpeg, image/pjpeg, image/pjpeg, " + "application/x-shockwave-flash, application/xaml+xml, " + "application/vnd.ms-xpsdocument, application/x-ms-xbap, " + "application/x-ms-application, application/vnd.ms-excel, " + "application/vnd.ms-powerpoint, application/msword, */*"); conn.setRequestProperty("Accept-Language", "zh-CN"); conn.setRequestProperty("Charset", "UTF-8"); conn.setRequestProperty("Connection", "Keep-Alive"); // 得到文件大小 fileSize = conn.getContentLength(); Log.e("fileSize", fileSize + ""); conn.disconnect(); long currentPartSize = fileSize / threadNum; Log.e("currentPartSize",currentPartSize+""); RandomAccessFile file = new RandomAccessFile(targetFile, "rw"); // 设置本地文件的大小 file.setLength(fileSize); file.close(); for (int i = 0; i < threadNum; i++) { // 计算每条线程的下载的开始位置 long startPos = i * currentPartSize; Log.e("startPos",startPos+""); // 每个线程使用一个RandomAccessFile进行下载 RandomAccessFile currentPart = new RandomAccessFile(targetFile, "rw"); // 定位该线程的下载位置 currentPart.seek(startPos); // 创建下载线程 threads[i] = new DownThread(startPos, currentPartSize, currentPart); // 启动下载线程 threads[i].start(); } } public class DownThread extends Thread { // 当前线程的下载位置 private long startPos; // 定义当前线程负责下载的文件大小 private long currentPartSize; // 当前线程需要下载的文件块 private RandomAccessFile currentPart; // 定义已经该线程已下载的字节数 public long length; public DownThread(long startPos, long currentPartSize, RandomAccessFile currentPart) { this.startPos = startPos; this.currentPartSize = currentPartSize; this.currentPart = currentPart; } @Override public void run() { try { URL url = new URL(path); HttpURLConnection conn = (HttpURLConnection)url .openConnection(); conn.setConnectTimeout(5 * 1000); conn.setRequestMethod("GET"); conn.setRequestProperty( "Accept", "image/gif, image/jpeg, image/pjpeg, image/pjpeg, " + "application/x-shockwave-flash, application/xaml+xml, " + "application/vnd.ms-xpsdocument, application/x-ms-xbap, " + "application/x-ms-application, application/vnd.ms-excel, " + "application/vnd.ms-powerpoint, application/msword, */*"); conn.setRequestProperty("Accept-Language", "zh-CN"); conn.setRequestProperty("Charset", "UTF-8"); InputStream inStream = conn.getInputStream(); /* * 跳过startPos个字节,表明该线程只下载自己负责哪部分文件 * 如果直接使用 InputStream.skip(long n); 会出现错误 * 因为用的时候,返回值往往小于n * Java 官方文档指出这个问题,建议自己去写一个方法来实现这个功能 */ long at = this.startPos; while(at > 0) { long amt = inStream.skip(at); if (amt == -1) { throw new RuntimeException(currentPart + ": unexpected EOF"); } at -= amt; } byte[] buffer = new byte[1024]; int hasRead = 0; // 读取网络数据,并写入本地文件 while (length < currentPartSize && (hasRead = inStream.read(buffer)) > 0) { currentPart.write(buffer, 0, hasRead); // 累计该线程下载的总大小 length += hasRead; } currentPart.close(); inStream.close(); } catch (Exception e) { e.printStackTrace(); } } } // 获取下载的完成百分比 public double getCompleteRate() { // 统计多条线程已经下载的总大小 long sumSize = 0; for (int i = 0; i < threadNum; i++) { sumSize += threads[i].length; } // 返回已经完成的百分比 return sumSize * 1.0 / fileSize; } }
activity_main.xml :
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:padding="16dp"> <ImageView android:id="@+id/iv_singer_picture" android:layout_width="match_parent" android:layout_height="240dp" android:layout_margin="16dp"/> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center"> <ProgressBar android:id="@+id/bar_dowmload_picture" style="?android:attr/progressBarStyleHorizontal" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" /> <Button android:id="@+id/btn_download_picture" android:layout_width="100dp" android:layout_height="wrap_content" android:layout_margin="6dp" android:text="点击下载歌手图片"/> </LinearLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center"> <ProgressBar android:id="@+id/bar_dowmload_song" style="?android:attr/progressBarStyleHorizontal" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" /> <Button android:id="@+id/btn_download_song" android:layout_width="100dp" android:layout_height="wrap_content" android:layout_margin="6dp" android:text="点击下载歌曲文件"/> </LinearLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center"> <SeekBar android:id="@+id/seekbar_song_play" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" /> <ImageButton android:id="@+id/btn_song_play" android:layout_width="100dp" android:layout_height="wrap_content" android:layout_margin="6dp" android:background="#00000000" android:src="@drawable/start"/> </LinearLayout> </LinearLayout>