Android ファイルのダウンロード (2)
このセクションの紹介:
このセクションでは、Android におけるマルチスレッド ブレークポイント再開のコード分析について説明します。なぜなら私は 書き出すことすらできないよ( ╯□╰ )!まずはブレークポイントの意味についてお話しましょう!いわゆるブレークポイントは、データベースを使用してスレッドが毎日行うことを記録します。 ダウンロードが進んでいます!開始されるたびに、スレッド ID に基づいてスレッドのダウンロードの進行状況が照会され、ダウンロードが続行されます。とてもシンプルに聞こえますが、 書こうとすると書けない可能性が高いので、この部分は理解しておいたほうが良いでしょう。理解できなくても、修正して使えるのであれば問題ありません。それ! さて、このセクションを始めましょう~
Android マルチスレッド ブレークポイント ダウンロードのコード プロセス分析:
レンダリングの実行:
実装プロセスの完全な分析:
ステップ 1: 作成スレッドのダウンロード情報を記録するために使用されるテーブル
データベーステーブルを作成するため、データベースマネージャークラスを作成し、SQLiteOpenHelperクラスを継承します onCreate() メソッドと onUpgrade() メソッドをオーバーライドして、作成したテーブル フィールドは次のとおりです:
DBOpenHelper.java:
package com.jay.example.db; import android.content.Context; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabase.CursorFactory; import android.database.sqlite.SQLiteOpenHelper; public class DBOpenHelper extends SQLiteOpenHelper { public DBOpenHelper(Context context) { super(context, "downs.db", null, 1); } @Override public void onCreate(SQLiteDatabase db) { //数据库的结构为:表名:filedownlog 字段:id,downpath:当前下载的资源, //threadid:下载的线程id,downlength:线程下载的最后位置 db.execSQL("CREATE TABLE IF NOT EXISTS filedownlog " + "(id integer primary key autoincrement," + " downpath varchar(100)," + " threadid INTEGER, downlength INTEGER)"); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { //当版本号发生改变时调用该方法,这里删除数据表,在实际业务中一般是要进行数据备份的 db.execSQL("DROP TABLE IF EXISTS filedownlog"); onCreate(db); } }
ステップ 2: データベース操作クラスを作成する
必要なもの作成方法はどうですか?
- ①URLに基づいて各スレッドの現在のダウンロード長を取得するメソッドが必要です
- ②次に、スレッドが新しく開かれたときに、スレッドに関連するパラメータをデータベースに挿入するメソッドが必要です
- ③も定義しますダウンロードされたファイルの長さをリアルタイムで更新できるメソッドです
- ④スレッドのダウンロードが完了した後も、スレッドIDに従って対応するレコードを削除する必要があります
FileService.java
package com.jay.example.db; import java.util.HashMap; import java.util.Map; import android.content.Context; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; /* * 该类是一个业务bean类,完成数据库的相关操作 * */ public class FileService { //声明数据库管理器 private DBOpenHelper openHelper; //在构造方法中根据上下文对象实例化数据库管理器 public FileService(Context context) { openHelper = new DBOpenHelper(context); } /** * 获得指定URI的每条线程已经下载的文件长度 * @param path * @return * */ public Map getData(String path) { //获得可读数据库句柄,通常内部实现返回的其实都是可写的数据库句柄 SQLiteDatabase db = openHelper.getReadableDatabase(); //根据下载的路径查询所有现场的下载数据,返回的Cursor指向第一条记录之前 Cursor cursor = db.rawQuery("select threadid, downlength from filedownlog where downpath=?", new String[]{path}); //建立一个哈希表用于存放每条线程已下载的文件长度 Map data = new HashMap(); //从第一条记录开始遍历Cursor对象 cursor.moveToFirst(); while(cursor.moveToNext()) { //把线程id与该线程已下载的长度存放到data哈希表中 data.put(cursor.getInt(0), cursor.getInt(1)); data.put(cursor.getInt(cursor.getColumnIndexOrThrow("threadid")), cursor.getInt(cursor.getColumnIndexOrThrow("downlength"))); } cursor.close();//关闭cursor,释放资源; db.close(); return data; } /** * 保存每条线程已经下载的文件长度 * @param path 下载的路径 * @param map 现在的di和已经下载的长度的集合 */ public void save(String path,Map map) { SQLiteDatabase db = openHelper.getWritableDatabase(); //开启事务,因为此处需要插入多条数据 db.beginTransaction(); try{ //使用增强for循环遍历数据集合 for(Map.Entry entry : map.entrySet()) { //插入特定下载路径特定线程ID已经下载的数据 db.execSQL("insert into filedownlog(downpath, threadid, downlength) values(?,?,?)", new Object[]{path, entry.getKey(), entry.getValue()}); } //设置一个事务成功的标志,如果成功就提交事务,如果没调用该方法的话那么事务回滚 //就是上面的数据库操作撤销 db.setTransactionSuccessful(); }finally{ //结束一个事务 db.endTransaction(); } db.close(); } /** * 实时更新每条线程已经下载的文件长度 * @param path * @param map */ public void update(String path,int threadId,int pos) { SQLiteDatabase db = openHelper.getWritableDatabase(); //更新特定下载路径下特定线程已下载的文件长度 db.execSQL("update filedownlog set downlength=? where downpath=? and threadid=?", new Object[]{pos, path, threadId}); db.close(); } /** *当文件下载完成后,删除对应的下载记录 *@param path */ public void delete(String path) { SQLiteDatabase db = openHelper.getWritableDatabase(); db.execSQL("delete from filedownlog where downpath=?", new Object[]{path}); db.close(); } }
ステップ3: ファイル ダウンローダー クラスを作成します
さて、データベース マネージャーとオペレーション クラスが完成しました。次に、ファイル ダウンローダー クラスを作成します。これを再度完成させる必要があります。 どのような操作ですか?やるべきことがたくさんあります:
① 一連の変数を定義します。その中心となるのは、スレッド プールのスレッドと同期コレクション ConcurrentHashMap であり、スレッドのダウンロード長をキャッシュするために使用されます
② スレッド プール内のスレッド数を取得するメソッドを定義します;
③ ダウンロードメソッドを終了するメソッド、
④ 現在のファイルサイズを取得するメソッド、
⑤ 現在のダウンロードされた長さを蓄積するメソッドを定義します。ここでは、次の問題を解決するために同期キーワードを追加する必要があります。同時アクセス
⑥指定したスレッドの場所の最後のダウンロードを更新します。また、同期を使用する必要があります
⑦構築メソッドでファイルのダウンロード、スレッドのオープン、その他の操作を完了するには
⑧ファイル名の取得方法: まず、指定された URL の最後の '/' の後の文字列をインターセプトします。 そうでない場合は、head フィールドを再度検索します。 見つからない場合は、ネットワーク カードの識別番号 + CPU の固有の番号を使用して、ファイル名として 16 バイトのバイナリを生成します
⑨ファイルのダウンロードを開始する方法
⑩ http 応答を取得する方法ヘッダーフィールド
⑪ httpヘッダーフィールドを印刷する方法
12. ログ情報を印刷する方法
FileDownloadered.java:
package com.jay.example.service; import java.io.File; import java.io.RandomAccessFile; import java.net.HttpURLConnection; import java.net.URL; import java.util.LinkedHashMap; import java.util.Map; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.regex.Matcher; import java.util.regex.Pattern; import android.content.Context; import android.util.Log; import com.jay.example.db.FileService; public class FileDownloadered { private static final String TAG = "文件下载类"; //设置一个查log时的一个标志 private static final int RESPONSEOK = 200; //设置响应码为200,代表访问成功 private FileService fileService; //获取本地数据库的业务Bean private boolean exited; //停止下载的标志 private Context context; //程序的上下文对象 private int downloadedSize = 0; //已下载的文件长度 private int fileSize = 0; //开始的文件长度 private DownloadThread[] threads; //根据线程数设置下载的线程池 private File saveFile; //数据保存到本地的文件中 private Map data = new ConcurrentHashMap(); //缓存个条线程的下载的长度 private int block; //每条线程下载的长度 private String downloadUrl; //下载的路径 /** * 获取线程数 */ public int getThreadSize() { //return threads.length; return 0; } /** * 退出下载 * */ public void exit() { this.exited = true; //将退出的标志设置为true; } public boolean getExited() { return this.exited; } /** * 获取文件的大小 * */ public int getFileSize() { return fileSize; } /** * 累计已下载的大小 * 使用同步锁来解决并发的访问问题 * */ protected synchronized void append(int size) { //把实时下载的长度加入到总的下载长度中 downloadedSize += size; } /** * 更新指定线程最后下载的位置 * @param threadId 线程id * @param pos 最后下载的位置 * */ protected synchronized void update(int threadId,int pos) { //把指定线程id的线程赋予最新的下载长度,以前的值会被覆盖掉 this.data.put(threadId, pos); //更新数据库中制定线程的下载长度 this.fileService.update(this.downloadUrl, threadId, pos); } /** * 构建文件下载器 * @param downloadUrl 下载路径 * @param fileSaveDir 文件的保存目录 * @param threadNum 下载线程数 * @return */ public FileDownloadered(Context context,String downloadUrl,File fileSaveDir,int threadNum) { try { this.context = context; //获取上下文对象,赋值 this.downloadUrl = downloadUrl; //为下载路径赋值 fileService = new FileService(this.context); //实例化数据库操作的业务Bean类,需要传一个context值 URL url = new URL(this.downloadUrl); //根据下载路径实例化URL if(!fileSaveDir.exists()) fileSaveDir.mkdir(); //如果文件不存在的话指定目录,这里可创建多层目录 this.threads = new DownloadThread[threadNum]; //根据下载的线程数量创建下载的线程池 HttpURLConnection conn = (HttpURLConnection) url.openConnection(); //创建远程连接句柄,这里并未真正连接 conn.setConnectTimeout(5000); //设置连接超时事件为5秒 conn.setRequestMethod("GET"); //设置请求方式为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("Referer", downloadUrl); //设置请求的来源页面,便于服务端进行来源统计 conn.setRequestProperty("Charset", "UTF-8"); //设置客户端编码 //设置用户代理 conn.setRequestProperty("User-Agent", "Mozilla/4.0 (compatible; MSIE 8.0; " + "Windows NT 5.2; Trident/4.0; .NET CLR 1.1.4322; .NET CLR 2.0.50727;" + " .NET CLR 3.0.04506.30; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729)"); conn.setRequestProperty("Connection", "Keep-Alive"); //设置connection的方式 conn.connect(); //和远程资源建立正在的链接,但尚无返回的数据流 printResponseHeader(conn); //打印返回的Http的头字段集合 //对返回的状态码进行判断,用于检查是否请求成功,返回200时执行下面的代码 if(conn.getResponseCode() == RESPONSEOK) { this.fileSize = conn.getContentLength(); //根据响应获得文件大小 if(this.fileSize <= 0)throw new RuntimeException("不知道文件大小"); //文件长度小于等于0时抛出运行时异常 String filename = getFileName(conn); //获取文件名称 this.saveFile = new File(fileSaveDir,filename); //根据文件保存目录和文件名保存文件 Map logdata = fileService.getData(downloadUrl); //获取下载记录 //如果存在下载记录 if(logdata.size() > 0) { //遍历集合中的数据,把每条线程已下载的数据长度放入data中 for(Map.Entry entry : logdata.entrySet()) { data.put(entry.getKey(), entry.getValue()); } } //如果已下载的数据的线程数和现在设置的线程数相同时则计算所有现场已经下载的数据总长度 if(this.data.size() == this.threads.length) { //遍历每条线程已下载的数据 for(int i = 0;i 0) randOut.setLength(this.fileSize); randOut.close(); //关闭该文件,使设置生效 URL url = new URL(this.downloadUrl); if(this.data.size() != this.threads.length){ //如果原先未曾下载或者原先的下载线程数与现在的线程数不一致 this.data.clear(); //遍历线程池 for (int i = 0; i < this.threads.length; i++) { this.data.put(i+1, 0);//初始化每条线程已经下载的数据长度为0 } this.downloadedSize = 0; //设置已经下载的长度为0 } for (int i = 0; i < this.threads.length; i++) {//开启线程进行下载 int downLength = this.data.get(i+1); //通过特定的线程id获取该线程已经下载的数据长度 //判断线程是否已经完成下载,否则继续下载 if(downLength < this.block && this.downloadedSize<this.fileSize){ //初始化特定id的线程 this.threads[i] = new DownloadThread(this, url, this.saveFile, this.block, this.data.get(i+1), i+1); //设置线程优先级,Thread.NORM_PRIORITY = 5; //Thread.MIN_PRIORITY = 1;Thread.MAX_PRIORITY = 10,数值越大优先级越高 this.threads[i].setPriority(7); this.threads[i].start(); //启动线程 }else{ this.threads[i] = null; //表明线程已完成下载任务 } } fileService.delete(this.downloadUrl); //如果存在下载记录,删除它们,然后重新添加 fileService.save(this.downloadUrl, this.data); //把下载的实时数据写入数据库中 boolean notFinish = true; //下载未完成 while (notFinish) { // 循环判断所有线程是否完成下载 Thread.sleep(900); notFinish = false; //假定全部线程下载完成 for (int i = 0; i < this.threads.length; i++){ if (this.threads[i] != null && !this.threads[i].isFinish()) { //如果发现线程未完成下载 notFinish = true; //设置标志为下载没有完成 if(this.threads[i].getDownLength() == -1){ //如果下载失败,再重新在已下载的数据长度的基础上下载 //重新开辟下载线程,设置线程的优先级 this.threads[i] = new DownloadThread(this, url, this.saveFile, this.block, this.data.get(i+1), i+1); this.threads[i].setPriority(7); this.threads[i].start(); } } } if(listener!=null) listener.onDownloadSize(this.downloadedSize); //通知目前已经下载完成的数据长度 } if(downloadedSize == this.fileSize) fileService.delete(this.downloadUrl); //下载完成删除记录 } catch (Exception e) { print(e.toString()); throw new Exception("文件下载异常"); } return this.downloadedSize; } /** * 获取Http响应头字段 * @param http * @return */ public static Map getHttpResponseHeader(HttpURLConnection http) { //使用LinkedHashMap保证写入和便利的时候的顺序相同,而且允许空值 Map header = new LinkedHashMap(); //此处使用无线循环,因为不知道头字段的数量 for (int i = 0;; i++) { String mine = http.getHeaderField(i); //获取第i个头字段的值 if (mine == null) break; //没值说明头字段已经循环完毕了,使用break跳出循环 header.put(http.getHeaderFieldKey(i), mine); //获得第i个头字段的键 } return header; } /** * 打印Http头字段 * @param http */ public static void printResponseHeader(HttpURLConnection http){ //获取http响应的头字段 Map header = getHttpResponseHeader(http); //使用增强for循环遍历取得头字段的值,此时遍历的循环顺序与输入树勋相同 for(Map.Entry entry : header.entrySet()){ //当有键的时候则获取值,如果没有则为空字符串 String key = entry.getKey()!=null ? entry.getKey()+ ":" : ""; print(key+ entry.getValue()); //打印键和值得组合 } } /** * 打印信息 * @param msg 信息字符串 * */ private static void print(String msg) { Log.i(TAG, msg); } }
ステップ4: ダウンロードスレッドクラスをカスタマイズする
このカスタムスレッドクラスとは
- ① まず、Threadクラスを継承して、Run()メソッドを書き換える必要があります
- ② Run()メソッド: まずダウンロードが完了したかどうかを判断し、完了していない場合: URLConnectionを開きますリンク、次に RandomAccessFile データを読み書きし、完了したら完了マークをtrueに設定し、例外が発生した場合は長さを-1に設定し、例外情報を出力します
- ③ログ情報を出力する方法
- ④ダウンロードが完了したかどうかを判断する方法(による)完了マーク)
- ⑤ ダウンロードされたコンテンツのサイズを取得します
DownLoadThread.java:
package com.jay.example.service; import java.io.File; import java.io.InputStream; import java.io.RandomAccessFile; import java.net.HttpURLConnection; import java.net.URL; import android.util.Log; public class DownloadThread extends Thread { private static final String TAG = "下载线程类"; //定义TAG,在打印log时进行标记 private File saveFile; //下载的数据保存到的文件 private URL downUrl; //下载的URL private int block; //每条线程下载的大小 private int threadId = -1; //初始化线程id设置 private int downLength; //该线程已下载的数据长度 private boolean finish = false; //该线程是否完成下载的标志 private FileDownloadered downloader; //文件下载器 public DownloadThread(FileDownloadered downloader, URL downUrl, File saveFile, int block, int downLength, int threadId) { this.downUrl = downUrl; this.saveFile = saveFile; this.block = block; this.downloader = downloader; this.threadId = threadId; this.downLength = downLength; } @Override public void run() { if(downLength < block){//未下载完成 try { HttpURLConnection http = (HttpURLConnection) downUrl.openConnection(); http.setConnectTimeout(5 * 1000); http.setRequestMethod("GET"); http.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, */*"); http.setRequestProperty("Accept-Language", "zh-CN"); http.setRequestProperty("Referer", downUrl.toString()); http.setRequestProperty("Charset", "UTF-8"); int startPos = block * (threadId - 1) + downLength;//开始位置 int endPos = block * threadId -1;//结束位置 http.setRequestProperty("Range", "bytes=" + startPos + "-"+ endPos);//设置获取实体数据的范围 http.setRequestProperty("User-Agent", "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.2; Trident/4.0; .NET CLR 1.1.4322; .NET CLR 2.0.50727; .NET CLR 3.0.04506.30; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729)"); http.setRequestProperty("Connection", "Keep-Alive"); InputStream inStream = http.getInputStream(); //获得远程连接的输入流 byte[] buffer = new byte[1024]; //设置本地数据的缓存大小为1MB int offset = 0; //每次读取的数据量 print("Thread " + this.threadId + " start download from position "+ startPos); //打印该线程开始下载的位置 RandomAccessFile threadfile = new RandomAccessFile(this.saveFile, "rwd"); threadfile.seek(startPos); //用户没有要求停止下载,同时没有达到请求数据的末尾时会一直循环读取数据 while (!downloader.getExited() && (offset = inStream.read(buffer, 0, 1024)) != -1) { threadfile.write(buffer, 0, offset); //直接把数据写入到文件中 downLength += offset; //把新线程已经写到文件中的数据加入到下载长度中 downloader.update(this.threadId, downLength); //把该线程已经下载的数据长度更新到数据库和内存哈希表中 downloader.append(offset); //把新下载的数据长度加入到已经下载的数据总长度中 } threadfile.close(); inStream.close(); print("Thread " + this.threadId + " download finish"); this.finish = true; //设置完成标记为true,无论下载完成还是用户主动中断下载 } catch (Exception e) { this.downLength = -1; //设置该线程已经下载的长度为-1 print("Thread "+ this.threadId+ ":"+ e); } } } private static void print(String msg){ Log.i(TAG, msg); } /** * 下载是否完成 * @return */ public boolean isFinish() { return finish; } /** * 已经下载的内容大小 * @return 如果返回值为-1,代表下载失败 */ public long getDownLength() { return downLength; } }
ステップ 5: ダウンロードの進行状況を監視する DownloadProgressListener インターフェイスを作成します
FileDownloader は、進行状況の監視に DownloadProgressListener を使用します。 したがって、ここではインターフェイスを作成し、メソッドの空の実装を定義する必要があります:
DownloadProgressListener.java:
package com.jay.example.service; public interface DownloadProgressListener { public void onDownloadSize(int downloadedSize); }
ステップ 6: レイアウト コードを作成します
さらに、 android:enabled="false" を呼び出して、コンポーネントをクリックできるかどうかを設定します。コードは次のとおりです
activity_main.xml:
ステップ 7: MainActivity の書き込み
最後は MainActivity で、コンポーネントと関連変数の初期化を完了します。 ハンドラーを使用してインターフェース更新操作を完了します。また、時間のかかる操作はメインスレッドでは実行できません。 したがって、Runnable で実装される新しいスレッドをここで開く必要があります。詳細についてはコードを参照してください
MainActivity.java:
package com.jay.example.multhreadcontinuabledemo; import java.io.File; import com.jay.example.service.FileDownloadered; import android.app.Activity; import android.os.Bundle; import android.os.Environment; import android.os.Handler; import android.os.Message; import android.view.View; import android.widget.Button; import android.widget.EditText; import android.widget.ProgressBar; import android.widget.TextView; import android.widget.Toast; public class MainActivity extends Activity { private EditText editpath; private Button btndown; private Button btnstop; private TextView textresult; private ProgressBar progressbar; private static final int PROCESSING = 1; //正在下载实时数据传输Message标志 private static final int FAILURE = -1; //下载失败时的Message标志 private Handler handler = new UIHander(); private final class UIHander extends Handler{ public void handleMessage(Message msg) { switch (msg.what) { //下载时 case PROCESSING: int size = msg.getData().getInt("size"); //从消息中获取已经下载的数据长度 progressbar.setProgress(size); //设置进度条的进度 //计算已经下载的百分比,此处需要转换为浮点数计算 float num = (float)progressbar.getProgress() / (float)progressbar.getMax(); int result = (int)(num * 100); //把获取的浮点数计算结果转换为整数 textresult.setText(result+ "%"); //把下载的百分比显示到界面控件上 if(progressbar.getProgress() == progressbar.getMax()){ //下载完成时提示 Toast.makeText(getApplicationContext(), "文件下载成功", 1).show(); } break; case FAILURE: //下载失败时提示 Toast.makeText(getApplicationContext(), "文件下载失败", 1).show(); break; } } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); editpath = (EditText) findViewById(R.id.editpath); btndown = (Button) findViewById(R.id.btndown); btnstop = (Button) findViewById(R.id.btnstop); textresult = (TextView) findViewById(R.id.textresult); progressbar = (ProgressBar) findViewById(R.id.progressBar); ButtonClickListener listener = new ButtonClickListener(); btndown.setOnClickListener(listener); btnstop.setOnClickListener(listener); } private final class ButtonClickListener implements View.OnClickListener{ public void onClick(View v) { switch (v.getId()) { case R.id.btndown: String path = editpath.getText().toString(); if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)){ File saveDir = Environment.getExternalStorageDirectory(); download(path, saveDir); }else{ Toast.makeText(getApplicationContext(), "sd卡读取失败", 1).show(); } btndown.setEnabled(false); btnstop.setEnabled(true); break; case R.id.btnstop: exit(); btndown.setEnabled(true); btnstop.setEnabled(false); break; } } /* 由于用户的输入事件(点击button, 触摸屏幕....)是由主线程负责处理的,如果主线程处于工作状态, 此时用户产生的输入事件如果没能在5秒内得到处理,系统就会报“应用无响应”错误。 所以在主线程里不能执行一件比较耗时的工作,否则会因主线程阻塞而无法处理用户的输入事件, 导致“应用无响应”错误的出现。耗时的工作应该在子线程里执行。 */ private DownloadTask task; /** * 退出下载 */ public void exit(){ if(task!=null) task.exit(); } private void download(String path, File saveDir) {//运行在主线程 task = new DownloadTask(path, saveDir); new Thread(task).start(); } /* * UI控件画面的重绘(更新)是由主线程负责处理的,如果在子线程中更新UI控件的值,更新后的值不会重绘到屏幕上 * 一定要在主线程里更新UI控件的值,这样才能在屏幕上显示出来,不能在子线程中更新UI控件的值 */ private final class DownloadTask implements Runnable{ private String path; private File saveDir; private FileDownloadered loader; public DownloadTask(String path, File saveDir) { this.path = path; this.saveDir = saveDir; } /** * 退出下载 */ public void exit(){ if(loader!=null) loader.exit(); } public void run() { try { loader = new FileDownloadered(getApplicationContext(), path, saveDir, 3); progressbar.setMax(loader.getFileSize());//设置进度条的最大刻度 loader.download(new com.jay.example.service.DownloadProgressListener() { public void onDownloadSize(int size) { Message msg = new Message(); msg.what = 1; msg.getData().putInt("size", size); handler.sendMessage(msg); } }); } catch (Exception e) { e.printStackTrace(); handler.sendMessage(handler.obtainMessage(-1)); } } } } }
ステップ 8: AndroidManifest.xml ファイルに関連するアクセス許可を追加します
リファレンス コードダウンロード:
詳細 スレッド ブレークポイント ダウンローダーのデモ: MulThreadContinuableDemo.zip
マルチスレッド ブレークポイント ダウンロード + オンライン ミュージック プレーヤー: マルチスレッド ブレークポイント ダウンロード + オンライン ミュージック プレーヤー.zip
このセクションの概要:
さて、このセクションは Android マルチスレッド ブレークポイント ダウンロードのコードについてです分析は限られており、非常に圧倒されますが、それでも とはいえ、他の人がホイールを作ったのなら、なぜ自分で作るのでしょうか?しかも、今でも作ることは可能です。 そうですか?とりあえず理解して、使えて、変更方法もわかりました〜、以上です、ありがとうございます〜