今天学习了Android开发中比较难的一个环节,就是断点续传下载,很多人看到这个标题就感觉头大,的确,如果没有良好的逻辑思维,这块的确很难搞明白。下面我就将自己学到的知识和一些见解写下供那些在这个环节还烦恼的人参考。这里我以下载mp3文件为例。

断点续传下载,顾名思义,那就是我们在一次下载未结束时,退出下载,第二次下载时会接着第一次下载的进度继续下载。那么怎么记录第一次下载的数据呢,这里肯定就要用到数据库了。下面就是我创建数据库的一个SQLiteOpenHelper类。用来首次运行时创建数据库。

DBHelper类:

package cn.yj3g.DBHelper;

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper; /**
* 建立一个数据库帮助类
*/
public class DBHelper extends SQLiteOpenHelper {
//download.db-->数据库名
public DBHelper(Context context) {
super(context, "download.db", null, );
} /**
* 在download.db数据库下创建一个download_info表存储下载信息
*/
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL("create table download_info(_id integer PRIMARY KEY AUTOINCREMENT, thread_id integer, "
+ "start_pos integer, end_pos integer, compelete_size integer,url char)");
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { } }

下面来看主界面的布局,在这里,我只设计了一个ListView来显示下载的音乐的名称,和一个开始下载按钮和一个暂停按钮。

布局文件如下:

main.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:id="@+id/llRoot">
<ListView android:id="@android:id/list"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
</ListView>
</LinearLayout>

list_item.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<LinearLayout
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="5dip">
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight=""
android:id="@+id/tv_resouce_name"/>
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight=""
android:text="下载"
android:id="@+id/btn_start"
android:onClick="startDownload"/>
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight=""
android:text="暂停"
android:id="@+id/btn_pause"
android:onClick="pauseDownload"/>
</LinearLayout>
</LinearLayout>

主界面运行效果如下:

下面我们来看具体实现下载的方法。首先,我们要定义一个记录在下载时各个时期的数据的类,这里我创建了一个DownloadInfo类来记录。代码如下:

DownloadInfo:

package cn.yj3g.entity;
/**
*创建一个下载信息的实体类
*/
public class DownloadInfo {
private int threadId;//下载器id
private int startPos;//开始点
private int endPos;//结束点
private int compeleteSize;//完成度
private String url;//下载器网络标识
public DownloadInfo(int threadId, int startPos, int endPos,
int compeleteSize,String url) {
this.threadId = threadId;
this.startPos = startPos;
this.endPos = endPos;
this.compeleteSize = compeleteSize;
this.url=url;
}
public DownloadInfo() {
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public int getThreadId() {
return threadId;
}
public void setThreadId(int threadId) {
this.threadId = threadId;
}
public int getStartPos() {
return startPos;
}
public void setStartPos(int startPos) {
this.startPos = startPos;
}
public int getEndPos() {
return endPos;
}
public void setEndPos(int endPos) {
this.endPos = endPos;
}
public int getCompeleteSize() {
return compeleteSize;
}
public void setCompeleteSize(int compeleteSize) {
this.compeleteSize = compeleteSize;
} @Override
public String toString() {
return "DownloadInfo [threadId=" + threadId
+ ", startPos=" + startPos + ", endPos=" + endPos
+ ", compeleteSize=" + compeleteSize +"]";
}
}

在下载时,我们有进度条来显示进度,怎么确定进度条的进度,大小和起始位置呢?这里我定义了一个LoadInfo类来记录下载器详细信息。代码如下:

LoadInfo:

package cn.yj3g.entity;
/**
*自定义的一个记载下载器详细信息的类
*/
public class LoadInfo {
public int fileSize;//文件大小
private int complete;//完成度
private String urlstring;//下载器标识
public LoadInfo(int fileSize, int complete, String urlstring) {
this.fileSize = fileSize;
this.complete = complete;
this.urlstring = urlstring;
}
public LoadInfo() {
}
public int getFileSize() {
return fileSize;
}
public void setFileSize(int fileSize) {
this.fileSize = fileSize;
}
public int getComplete() {
return complete;
}
public void setComplete(int complete) {
this.complete = complete;
}
public String getUrlstring() {
return urlstring;
}
public void setUrlstring(String urlstring) {
this.urlstring = urlstring;
}
@Override
public String toString() {
return "LoadInfo [fileSize=" + fileSize + ", complete=" + complete
+ ", urlstring=" + urlstring + "]";
}
}

下面是最最重要的一步,那就是定义一个下载器来进行下载了,这里我就不多说,具体解释在代码中都有注解,我就直接将代码附下,供大家研究参考

Downloader:

package cn.yj3g.service;

import java.io.File;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import android.content.Context;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import cn.yj3g.Dao.Dao;
import cn.yj3g.entity.DownloadInfo;
import cn.yj3g.entity.LoadInfo; public class Downloader {
private String urlstr;// 下载的地址
private String localfile;// 保存路径
private int threadcount;// 线程数
private Handler mHandler;// 消息处理器
private Dao dao;// 工具类
private int fileSize;// 所要下载的文件的大小
private List<DownloadInfo> infos;// 存放下载信息类的集合
private static final int INIT = ;//定义三种下载的状态:初始化状态,正在下载状态,暂停状态
private static final int DOWNLOADING = ;
private static final int PAUSE = ;
private int state = INIT; public Downloader(String urlstr, String localfile, int threadcount,
Context context, Handler mHandler) {
this.urlstr = urlstr;
this.localfile = localfile;
this.threadcount = threadcount;
this.mHandler = mHandler;
dao = new Dao(context);
}
/**
*判断是否正在下载
*/
public boolean isdownloading() {
return state == DOWNLOADING;
}
/**
* 得到downloader里的信息
* 首先进行判断是否是第一次下载,如果是第一次就要进行初始化,并将下载器的信息保存到数据库中
* 如果不是第一次下载,那就要从数据库中读出之前下载的信息(起始位置,结束为止,文件大小等),并将下载信息返回给下载器
*/
public LoadInfo getDownloaderInfors() {
if (isFirst(urlstr)) {
Log.v("TAG", "isFirst");
init();
int range = fileSize / threadcount;
infos = new ArrayList<DownloadInfo>();
for (int i = ; i < threadcount - ; i++) {
DownloadInfo info = new DownloadInfo(i, i * range, (i + )* range - , , urlstr);
infos.add(info);
}
DownloadInfo info = new DownloadInfo(threadcount - ,(threadcount - ) * range, fileSize - , , urlstr);
infos.add(info);
//保存infos中的数据到数据库
dao.saveInfos(infos);
//创建一个LoadInfo对象记载下载器的具体信息
LoadInfo loadInfo = new LoadInfo(fileSize, , urlstr);
return loadInfo;
} else {
//得到数据库中已有的urlstr的下载器的具体信息
infos = dao.getInfos(urlstr);
Log.v("TAG", "not isFirst size=" + infos.size());
int size = ;
int compeleteSize = ;
for (DownloadInfo info : infos) {
compeleteSize += info.getCompeleteSize();
size += info.getEndPos() - info.getStartPos() + ;
}
return new LoadInfo(size, compeleteSize, urlstr);
}
} /**
* 初始化
*/
private void init() {
try {
URL url = new URL(urlstr);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setConnectTimeout();
connection.setRequestMethod("GET");
fileSize = connection.getContentLength(); File file = new File(localfile);
if (!file.exists()) {
file.createNewFile();
}
// 本地访问文件
RandomAccessFile accessFile = new RandomAccessFile(file, "rwd");
accessFile.setLength(fileSize);
accessFile.close();
connection.disconnect();
} catch (Exception e) {
e.printStackTrace();
}
} /**
* 判断是否是第一次 下载
*/
private boolean isFirst(String urlstr) {
return dao.isHasInfors(urlstr);
} /**
* 利用线程开始下载数据
*/
public void download() {
if (infos != null) {
if (state == DOWNLOADING)
return;
state = DOWNLOADING;
for (DownloadInfo info : infos) {
new MyThread(info.getThreadId(), info.getStartPos(),
info.getEndPos(), info.getCompeleteSize(),
info.getUrl()).start();
}
}
} public class MyThread extends Thread {
private int threadId;
private int startPos;
private int endPos;
private int compeleteSize;
private String urlstr; public MyThread(int threadId, int startPos, int endPos,
int compeleteSize, String urlstr) {
this.threadId = threadId;
this.startPos = startPos;
this.endPos = endPos;
this.compeleteSize = compeleteSize;
this.urlstr = urlstr;
}
@Override
public void run() {
HttpURLConnection connection = null;
RandomAccessFile randomAccessFile = null;
InputStream is = null;
try {
URL url = new URL(urlstr);
connection = (HttpURLConnection) url.openConnection();
connection.setConnectTimeout();
connection.setRequestMethod("GET");
// 设置范围,格式为Range:bytes x-y;
connection.setRequestProperty("Range", "bytes="+(startPos + compeleteSize) + "-" + endPos); randomAccessFile = new RandomAccessFile(localfile, "rwd");
randomAccessFile.seek(startPos + compeleteSize);
// 将要下载的文件写到保存在保存路径下的文件中
is = connection.getInputStream();
byte[] buffer = new byte[];
int length = -;
while ((length = is.read(buffer)) != -) {
randomAccessFile.write(buffer, , length);
compeleteSize += length;
// 更新数据库中的下载信息
dao.updataInfos(threadId, compeleteSize, urlstr);
// 用消息将下载信息传给进度条,对进度条进行更新
Message message = Message.obtain();
message.what = ;
message.obj = urlstr;
message.arg1 = length;
mHandler.sendMessage(message);
if (state == PAUSE) {
return;
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
is.close();
randomAccessFile.close();
connection.disconnect();
dao.closeDb();
} catch (Exception e) {
e.printStackTrace();
}
} }
}
//删除数据库中urlstr对应的下载器信息
public void delete(String urlstr) {
dao.delete(urlstr);
}
//设置暂停
public void pause() {
state = PAUSE;
}
//重置下载状态
public void reset() {
state = INIT;
}
}

在这边下载器类的定义中,我们用到了许多关于进行数据库操作的方法,这里我定义了一个数据库工具类,来提供这些方法,代码如下:

Dao:

package cn.yj3g.Dao;

import java.util.ArrayList;
import java.util.List;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import cn.yj3g.DBHelper.DBHelper;
import cn.yj3g.entity.DownloadInfo; /**
*
* 一个业务类
*/
public class Dao {
private DBHelper dbHelper; public Dao(Context context) {
dbHelper = new DBHelper(context);
} /**
* 查看数据库中是否有数据
*/
public boolean isHasInfors(String urlstr) {
SQLiteDatabase database = dbHelper.getReadableDatabase();
String sql = "select count(*) from download_info where url=?";
Cursor cursor = database.rawQuery(sql, new String[] { urlstr });
cursor.moveToFirst();
int count = cursor.getInt();
cursor.close();
return count == ;
} /**
* 保存 下载的具体信息
*/
public void saveInfos(List<DownloadInfo> infos) {
SQLiteDatabase database = dbHelper.getWritableDatabase();
for (DownloadInfo info : infos) {
String sql = "insert into download_info(thread_id,start_pos, end_pos,compelete_size,url) values (?,?,?,?,?)";
Object[] bindArgs = { info.getThreadId(), info.getStartPos(),
info.getEndPos(), info.getCompeleteSize(), info.getUrl() };
database.execSQL(sql, bindArgs);
}
} /**
* 得到下载具体信息
*/
public List<DownloadInfo> getInfos(String urlstr) {
List<DownloadInfo> list = new ArrayList<DownloadInfo>();
SQLiteDatabase database = dbHelper.getReadableDatabase();
String sql = "select thread_id, start_pos, end_pos,compelete_size,url from download_info where url=?";
Cursor cursor = database.rawQuery(sql, new String[] { urlstr });
while (cursor.moveToNext()) {
DownloadInfo info = new DownloadInfo(cursor.getInt(),
cursor.getInt(), cursor.getInt(), cursor.getInt(),
cursor.getString());
list.add(info);
}
cursor.close();
return list;
} /**
* 更新数据库中的下载信息
*/
public void updataInfos(int threadId, int compeleteSize, String urlstr) {
SQLiteDatabase database = dbHelper.getReadableDatabase();
String sql = "update download_info set compelete_size=? where thread_id=? and url=?";
Object[] bindArgs = { compeleteSize, threadId, urlstr };
database.execSQL(sql, bindArgs);
}
/**
* 关闭数据库
*/
public void closeDb() {
dbHelper.close();
} /**
* 下载完成后删除数据库中的数据
*/
public void delete(String url) {
SQLiteDatabase database = dbHelper.getReadableDatabase();
database.delete("download_info", "url=?", new String[] { url });
database.close();
}
}

下来就是要进行下载和暂停按钮的响应事件了。具体代码和解释如下。

MainActivity:

package cn.yj3g.AndroidDownload;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import android.app.ListActivity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.LinearLayout.LayoutParams;
import android.widget.ProgressBar;
import android.widget.SimpleAdapter;
import android.widget.TextView;
import android.widget.Toast;
import cn.yj3g.entity.LoadInfo;
import cn.yj3g.service.Downloader; public class MainActivity extends ListActivity {
// 固定下载的资源路径,这里可以设置网络上的地址
private static final String URL = "http://192.168.1.105:8080/struts2_net/";
// 固定存放下载的音乐的路径:SD卡目录下
private static final String SD_PATH = "/mnt/sdcard/";
// 存放各个下载器
private Map<String, Downloader> downloaders = new HashMap<String, Downloader>();
// 存放与下载器对应的进度条
private Map<String, ProgressBar> ProgressBars = new HashMap<String, ProgressBar>();
/**
* 利用消息处理机制适时更新进度条
*/
private Handler mHandler = new Handler() {
public void handleMessage(Message msg) {
if (msg.what == ) {
String url = (String) msg.obj;
int length = msg.arg1;
ProgressBar bar = ProgressBars.get(url);
if (bar != null) {
// 设置进度条按读取的length长度更新
bar.incrementProgressBy(length);
if (bar.getProgress() == bar.getMax()) {
Toast.makeText(MainActivity.this, "下载完成!", ).show();
// 下载完成后清除进度条并将map中的数据清空
LinearLayout layout = (LinearLayout) bar.getParent();
layout.removeView(bar);
ProgressBars.remove(url);
downloaders.get(url).delete(url);
downloaders.get(url).reset();
downloaders.remove(url); }
}
}
}
};
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
showListView();
}
// 显示listView,这里可以随便添加音乐
private void showListView() {
List<Map<String, String>> data = new ArrayList<Map<String, String>>();
Map<String, String> map = new HashMap<String, String>();
map.put("name", "mm.mp3");
data.add(map);
map = new HashMap<String, String>();
map.put("name", "pp.mp3");
data.add(map);
map = new HashMap<String, String>();
map.put("name", "tt.mp3");
data.add(map);
map = new HashMap<String, String>();
map.put("name", "You.mp3");
data.add(map);
SimpleAdapter adapter = new SimpleAdapter(this, data, R.layout.list_item, new String[] { "name" },
new int[] { R.id.tv_resouce_name });
setListAdapter(adapter);
}
/**
* 响应开始下载按钮的点击事件
*/
public void startDownload(View v) {
// 得到textView的内容
LinearLayout layout = (LinearLayout) v.getParent();
String musicName = ((TextView) layout.findViewById(R.id.tv_resouce_name)).getText().toString();
String urlstr = URL + musicName;
String localfile = SD_PATH + musicName;
//设置下载线程数为4,这里是我为了方便随便固定的
int threadcount = ;
// 初始化一个downloader下载器
Downloader downloader = downloaders.get(urlstr);
if (downloader == null) {
downloader = new Downloader(urlstr, localfile, threadcount, this, mHandler);
downloaders.put(urlstr, downloader);
}
if (downloader.isdownloading())
return;
// 得到下载信息类的个数组成集合
LoadInfo loadInfo = downloader.getDownloaderInfors();
// 显示进度条
showProgress(loadInfo, urlstr, v);
// 调用方法开始下载
downloader.download();
} /**
* 显示进度条
*/
private void showProgress(LoadInfo loadInfo, String url, View v) {
ProgressBar bar = ProgressBars.get(url);
if (bar == null) {
bar = new ProgressBar(this, null, android.R.attr.progressBarStyleHorizontal);
bar.setMax(loadInfo.getFileSize());
bar.setProgress(loadInfo.getComplete());
ProgressBars.put(url, bar);
LinearLayout.LayoutParams params = new LayoutParams(LayoutParams.FILL_PARENT, );
((LinearLayout) ((LinearLayout) v.getParent()).getParent()).addView(bar, params);
}
}
/**
* 响应暂停下载按钮的点击事件
*/
public void pauseDownload(View v) {
LinearLayout layout = (LinearLayout) v.getParent();
String musicName = ((TextView) layout.findViewById(R.id.tv_resouce_name)).getText().toString();
String urlstr = URL + musicName;
downloaders.get(urlstr).pause();
}
}

最后我们要在清单文件中添加权限,一个是访问网络的权限,一个是往SD卡写数据的权限。代码如下:

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

这样我们就实现了文件的断点续传下载功能。具体效果图如下:

每段路,都有即将要来的旅程 每颗心,都有值得期待的成分

Android之断点续传下载

今天学习了Android开发中比较难的一个环节,就是断点续传下载,很多人看到这个标题就感觉头大,的确,如果没有良好的逻辑思维,这块的确很难搞明白。下面我就将自己学到的知识和一些见解写下供那些在这个环节还烦恼的人参考。这里我以下载mp3文件为例。

断点续传下载,顾名思义,那就是我们在一次下载未结束时,退出下载,第二次下载时会接着第一次下载的进度继续下载。那么怎么记录第一次下载的数据呢,这里肯定就要用到数据库了。下面就是我创建数据库的一个SQLiteOpenHelper类。用来首次运行时创建数据库。

DBHelper类:

按 Ctrl+C 复制代码
按 Ctrl+C 复制代码

下面来看主界面的布局,在这里,我只设计了一个ListView来显示下载的音乐的名称,和一个开始下载按钮和一个暂停按钮。

布局文件如下:

main.xml:

Android之断点续传下载的更多相关文章

  1. android 多线程断点续传下载

    今天跟大家一起分享下Android开发中比较难的一个环节,可能很多人看到这个标题就会感觉头很大,的确如果没有良好的编码能力和逻辑思维,这块是很难搞明白的,前面2次总结中已经为大家分享过有关技术的一些基 ...

  2. Android之断点续传下载(转)

    Android之断点续传下载http://www.cnblogs.com/zxl-jay/archive/2011/10/09/2204195.html

  3. [Android实例] Android之断点续传下载

    在我们做开发的时候经常遇到的就是下载了,现在下载的方法有很多很多,那么怎么做到断点续传下载呢!很多人都头疼这个问题,如果我们没有很好的逻辑真不是很容易解决啊.我参考了一下前辈们的资料了整理了一个项目, ...

  4. android多线程断点续传下载文件

    一.目标 1.多线程抢占服务器资源下载. 2.断点续传. 二.实现思路. 假设分为三个线程: 1.各个线程分别向服务器请求文件的不同部分. 这个涉及Http协议,可以在Header中使用Range参数 ...

  5. Android多线程断点续传下载

    这个月接到一个项目.要写一个像360助手一样的对于软件管理的APP:当中.遇到了一个问题:多线程断点下载 这个 ,因为之前没有写过这方面的应用功能.所以.不免要自学了. 然后就在各个昂站上收索并整理了 ...

  6. Android多线程.断点续传下载

    多线程,可断点续传的demo!最早写于2010.7! /** * @brief  主界面      * @author lixp  */ public class HomeActivity exten ...

  7. 实现android支持多线程断点续传下载器功能

    多线程断点下载流程图: 多线程断点续传下载原理介绍: 在下载的时候多个线程并发可以占用服务器端更多资源,从而加快下载速度手机端下载数据时难免会出现无信号断线.电量不足等情况,所以需要断点续传功能根据下 ...

  8. Android实现网络多线程断点续传下载(转)

    本示例介绍在Android平台下通过HTTP协议实现断点续传下载. 我们编写的是Andorid的HTTP协议多线程断点下载应用程序.直接使用单线程下载HTTP文件对我们来说是一件非常简单的事.那么,多 ...

  9. Android开发多线程断点续传下载器

    使用多线程断点续传下载器在下载的时候多个线程并发可以占用服务器端更多资源,从而加快下载速度,在下载过程中记录每个线程已拷贝数据的数量,如果下载中断,比如无信号断线.电量不足等情况下,这就需要使用到断点 ...

随机推荐

  1. 【翻译】五步快速使用LINQPad尝鲜StreamInsight

    StreamInsight  学习地址:http://www.cnblogs.com/StreamInsight/archive/2011/10/26/StreamInsight-Query-Seri ...

  2. [HNOI 2013] 旅行 (数学)

    感觉此题难啊,数学还是太渣了,看了半天的题解才算明白了点儿. 题目大意 给一个长度为n且仅由1和-1组成的序列ai, i = 1, 2, ..., n,每个位置都有另一个值vi,要求用某种方案将序列划 ...

  3. 控制反转(IOC)/依赖注入(DI)理解

    个人学习笔记,来自Acode. 1.术语 控制反转/反向控制,英文全称“Inversion of Control”,简称IoC. 依赖注入,英文全称“Dependency Injection”,简称D ...

  4. C#_Test

    using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Plus ...

  5. 状态模式(State) 笔记

    让一个对象随着内部的状态改变而发生改变. 状态的两种切换方式: 1) 完全交给Context类切换, 2) 给Context初始化状态,其他的切换根据每一个State类进行切换,Context对象不再 ...

  6. [RxJS] Refactoring CombineLatest to WithLatestFrom

    This lesson shows why it’s preferable to using withLatestFrom instead of combineLatest in certain sc ...

  7. 《Linux Device Drivers》第十二章 PCI司机——note

    一个简短的引论 它给这一章总线架构的高级概述 集中访问讨论Peripheral Component Interconnect(PCI,外围组件互连)外设内核函数 PCI公交车是最好的支持的内核总线 本 ...

  8. Unity 制作虚拟手柄例子

    Unity不愧是收费开发软件,有写好的Joystick(虚拟手柄),使用起来很简单,我们一起来学习一下哈!! 本文源代码Win版的 :http://vdisk.weibo.com/s/BDn59yfn ...

  9. SQLServer中跨库复制数据

    SQLServer中把某个表里的记录复制到另一个数据库的表中的操作方法. 场景 现有数据库a和数据库b,数据库a里有表table1,数据库b里有表table2.现在要把表table1里的记录复制到ta ...

  10. HID class request.

    1.get report. 2.set report report request. Get report范例: 下面这张图是Host跟Device来要设备描述符. USB主机向设备控制器请求数据时, ...