Android 文件上传与下载
在实际开发涉及文件上传不会自己写上传代码,一般 会集成第三网络库来做图片上传,比如android-async-http,okhttp等,另外还有七牛也提供 了下载和上传的API。
1.项目用到的图片上传的关键方法:
这里用到一个第三方的库: android-async-http.jar,自己到github下下这个库~然后调用一下下面的方法即可,自己改下url!
上传图片的核心方法如下:
private void sendImage(Bitmap bm)
{ByteArrayOutputStream stream = new ByteArrayOutputStream();bm.compress(Bitmap.CompressFormat.PNG, 60, stream);byte[] bytes = stream.toByteArray();String img = new String(Base64.encodeToString(bytes, Base64.DEFAULT));AsyncHttpClient client = new AsyncHttpClient();RequestParams params = new RequestParams();params.add("img", img);client.post("http:xxx/postIcon", params, new AsyncHttpResponseHandler() {@Overridepublic void onSuccess(int i, Header[] headers, byte[] bytes) {Toast.makeText(MainActivity.this, "Upload Success!", Toast.LENGTH_LONG).show();}@Overridepublic void onFailure(int i, Header[] headers, byte[] bytes, Throwable throwable) {Toast.makeText(MainActivity.this, "Upload Fail!", Toast.LENGTH_LONG).show();}});
}
2.使用HttpConnection上传文件:
public class SocketHttpRequester
{ /** * 发送xml数据 * @param path 请求地址 * @param xml xml数据 * @param encoding 编码 * @return * @throws Exception */ public static byte[] postXml(String path, String xml, String encoding) throws Exception{ byte[] data = xml.getBytes(encoding); URL url = new URL(path); HttpURLConnection conn = (HttpURLConnection)url.openConnection(); conn.setRequestMethod("POST"); conn.setDoOutput(true); conn.setRequestProperty("Content-Type", "text/xml; charset="+ encoding); conn.setRequestProperty("Content-Length", String.valueOf(data.length)); conn.setConnectTimeout(5 * 1000); OutputStream outStream = conn.getOutputStream(); outStream.write(data); outStream.flush(); outStream.close(); if(conn.getResponseCode()==200){ return readStream(conn.getInputStream()); } return null; } /** * 直接通过HTTP协议提交数据到服务器,实现如下面表单提交功能: * <FORM METHOD=POST ACTION="http://192.168.0.200:8080/ssi/fileload/test.do" enctype="multipart/form-data"> <INPUT TYPE="text" NAME="name"> <INPUT TYPE="text" NAME="id"> <input type="file" name="imagefile"/> <input type="file" name="zip"/> </FORM> * @param path 上传路径(注:避免使用localhost或127.0.0.1这样的路径测试, * 因为它会指向手机模拟器,你可以使用http://www.baidu.com或http://192.168.1.10:8080这样的路径测试) * @param params 请求参数 key为参数名,value为参数值 * @param file 上传文件 */ public static boolean post(String path, Map<String, String> params, FormFile[] files) throws Exception { //数据分隔线 final String BOUNDARY = "---------------------------7da2137580612"; //数据结束标志"---------------------------7da2137580612--" final String endline = "--" + BOUNDARY + "--/r/n"; //下面两个for循环都是为了得到数据长度参数,依据表单的类型而定 //首先得到文件类型数据的总长度(包括文件分割线) int fileDataLength = 0; for(FormFile uploadFile : files) { StringBuilder fileExplain = new StringBuilder(); fileExplain.append("--"); fileExplain.append(BOUNDARY); fileExplain.append("/r/n"); fileExplain.append("Content-Disposition: form-data;name=/""+ uploadFile.getParameterName()+"/";filename=/""+ uploadFile.getFilname() + "/"/r/n"); fileExplain.append("Content-Type: "+ uploadFile.getContentType()+"/r/n/r/n"); fileExplain.append("/r/n"); fileDataLength += fileExplain.length(); if(uploadFile.getInStream()!=null){ fileDataLength += uploadFile.getFile().length(); }else{ fileDataLength += uploadFile.getData().length; } } //再构造文本类型参数的实体数据 StringBuilder textEntity = new StringBuilder(); for (Map.Entry<String, String> entry : params.entrySet()) { textEntity.append("--"); textEntity.append(BOUNDARY); textEntity.append("/r/n"); textEntity.append("Content-Disposition: form-data; name=/""+ entry.getKey() + "/"/r/n/r/n"); textEntity.append(entry.getValue()); textEntity.append("/r/n"); } //计算传输给服务器的实体数据总长度(文本总长度+数据总长度+分隔符) int dataLength = textEntity.toString().getBytes().length + fileDataLength + endline.getBytes().length; URL url = new URL(path); //默认端口号其实可以不写 int port = url.getPort()==-1 ? 80 : url.getPort(); //建立一个Socket链接 Socket socket = new Socket(InetAddress.getByName(url.getHost()), port); //获得一个输出流(从Android流到web) OutputStream outStream = socket.getOutputStream(); //下面完成HTTP请求头的发送 String requestmethod = "POST "+ url.getPath()+" HTTP/1.1/r/n"; outStream.write(requestmethod.getBytes()); //构建accept String accept = "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, */*/r/n"; outStream.write(accept.getBytes()); //构建language String language = "Accept-Language: zh-CN/r/n"; outStream.write(language.getBytes()); //构建contenttype String contenttype = "Content-Type: multipart/form-data; boundary="+ BOUNDARY+ "/r/n"; outStream.write(contenttype.getBytes()); //构建contentlength String contentlength = "Content-Length: "+ dataLength + "/r/n"; outStream.write(contentlength.getBytes()); //构建alive String alive = "Connection: Keep-Alive/r/n"; outStream.write(alive.getBytes()); //构建host String host = "Host: "+ url.getHost() +":"+ port +"/r/n"; outStream.write(host.getBytes()); //写完HTTP请求头后根据HTTP协议再写一个回车换行 outStream.write("/r/n".getBytes()); //把所有文本类型的实体数据发送出来 outStream.write(textEntity.toString().getBytes()); //把所有文件类型的实体数据发送出来 for(FormFile uploadFile : files) { StringBuilder fileEntity = new StringBuilder(); fileEntity.append("--"); fileEntity.append(BOUNDARY); fileEntity.append("/r/n"); fileEntity.append("Content-Disposition: form-data;name=/""+ uploadFile.getParameterName()+"/";filename=/""+ uploadFile.getFilname() + "/"/r/n"); fileEntity.append("Content-Type: "+ uploadFile.getContentType()+"/r/n/r/n"); outStream.write(fileEntity.toString().getBytes()); //边读边写 if(uploadFile.getInStream()!=null) { byte[] buffer = new byte[1024]; int len = 0; while((len = uploadFile.getInStream().read(buffer, 0, 1024))!=-1) { outStream.write(buffer, 0, len); } uploadFile.getInStream().close(); } else { outStream.write(uploadFile.getData(), 0, uploadFile.getData().length); } outStream.write("/r/n".getBytes()); } //下面发送数据结束标志,表示数据已经结束 outStream.write(endline.getBytes()); BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream())); //读取web服务器返回的数据,判断请求码是否为200,如果不是200,代表请求失败 if(reader.readLine().indexOf("200")==-1) { return false; } outStream.flush(); outStream.close(); reader.close(); socket.close(); return true; } /** * 提交数据到服务器 * @param path 上传路径(注:避免使用localhost或127.0.0.1这样的路径测试,因为它会指向手机模拟器,你可以使用http://www.baidu.com或http://192.168.1.10:8080这样的路径测试) * @param params 请求参数 key为参数名,value为参数值 * @param file 上传文件 */ public static boolean post(String path, Map<String, String> params, FormFile file) throws Exception { return post(path, params, new FormFile[]{file}); } /** * 提交数据到服务器 * @param path 上传路径(注:避免使用localhost或127.0.0.1这样的路径测试,因为它会指向手机模拟器,你可以使用http://www.baidu.com或http://192.168.1.10:8080这样的路径测试) * @param params 请求参数 key为参数名,value为参数值 * @param encode 编码 */ public static byte[] postFromHttpClient(String path, Map<String, String> params, String encode) throws Exception { //用于存放请求参数 List<NameValuePair> formparams = new ArrayList<NameValuePair>(); for(Map.Entry<String, String> entry : params.entrySet()) { formparams.add(new BasicNameValuePair(entry.getKey(), entry.getValue())); } UrlEncodedFormEntity entity = new UrlEncodedFormEntity(formparams, encode); HttpPost httppost = new HttpPost(path); httppost.setEntity(entity); //看作是浏览器 HttpClient httpclient = new DefaultHttpClient(); //发送post请求 HttpResponse response = httpclient.execute(httppost); return readStream(response.getEntity().getContent()); } /** * 发送请求 * @param path 请求路径 * @param params 请求参数 key为参数名称 value为参数值 * @param encode 请求参数的编码 */ public static byte[] post(String path, Map<String, String> params, String encode) throws Exception { //String params = "method=save&name="+ URLEncoder.encode("老毕", "UTF-8")+ "&age=28&";//需要发送的参数 StringBuilder parambuilder = new StringBuilder(""); if(params!=null && !params.isEmpty()) { for(Map.Entry<String, String> entry : params.entrySet()) { parambuilder.append(entry.getKey()).append("=") .append(URLEncoder.encode(entry.getValue(), encode)).append("&"); } parambuilder.deleteCharAt(parambuilder.length()-1); } byte[] data = parambuilder.toString().getBytes(); URL url = new URL(path); HttpURLConnection conn = (HttpURLConnection)url.openConnection(); //设置允许对外发送请求参数 conn.setDoOutput(true); //设置不进行缓存 conn.setUseCaches(false); conn.setConnectTimeout(5 * 1000); conn.setRequestMethod("POST"); //下面设置http请求头 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("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("Content-Type", "application/x-www-form-urlencoded"); conn.setRequestProperty("Content-Length", String.valueOf(data.length)); conn.setRequestProperty("Connection", "Keep-Alive"); //发送参数 DataOutputStream outStream = new DataOutputStream(conn.getOutputStream()); outStream.write(data);//把参数发送出去 outStream.flush(); outStream.close(); if(conn.getResponseCode()==200) { return readStream(conn.getInputStream()); } return null; } /** * 读取流 * @param inStream * @return 字节数组 * @throws Exception */ public static byte[] readStream(InputStream inStream) throws Exception { ByteArrayOutputStream outSteam = new ByteArrayOutputStream(); byte[] buffer = new byte[1024]; int len = -1; while( (len=inStream.read(buffer)) != -1) { outSteam.write(buffer, 0, len); } outSteam.close(); inStream.close(); return outSteam.toByteArray(); }
}
3.普通单线程下载文件:
直接使用URLConnection.openStream()打开网络输入流,然后将流写入到文件中!
核心方法:
public static void downLoad(String path,Context context)throws Exception
{URL url = new URL(path);InputStream is = url.openStream();//截取最后的文件名String end = path.substring(path.lastIndexOf("."));//打开手机对应的输出流,输出到文件中OutputStream os = context.openFileOutput("Cache_"+System.currentTimeMillis()+end, Context.MODE_PRIVATE);byte[] buffer = new byte[1024];int len = 0;//从输入六中读取数据,读到缓冲区中while((len = is.read(buffer)) > 0){os.write(buffer,0,len);}//关闭输入输出流is.close();os.close();
}


4.普通多线程下载:
我们都知道使用多线程下载文件可以更快地完成文件的下载,但是为什么呢?
答:因为抢占的服务器资源多,假设服务器最多服务100个用户,服务器中的一个线程 对应一个用户100条线程在计算机中并发执行,由CPU划分时间片轮流执行,加入a有99条线程 下载文件,那么相当于占用了99个用户资源,自然就有用较快的下载速度
PS:当然不是线程越多就越好,开启过多线程的话,app需要维护和同步每条线程的开销, 这些开销反而会导致下载速度的降低,另外还和你的网速有关!
多线程下载的流程:
获取网络连接
本地磁盘创建相同大小的空文件
计算每条线程需从文件哪个部分开始下载,结束
依次创建,启动多条线程来下载网络资源的指定部分

PS:这里直接创建一个Java项目,然后在JUnit里运行指定方法即可,
核心代码如下:
public class Downloader {//添加@Test标记是表示该方法是Junit测试的方法,就可以直接运行该方法了@Testpublic void download() throws Exception{//设置URL的地址和下载后的文件名String filename = "meitu.exe";String path = "http://10.13.20.32:8080/Test/XiuXiu_Green.exe";URL url = new URL(path);HttpURLConnection conn = (HttpURLConnection) url.openConnection();conn.setConnectTimeout(5000);conn.setRequestMethod("GET");//获得需要下载的文件的长度(大小)int filelength = conn.getContentLength();System.out.println("要下载的文件长度"+filelength);//生成一个大小相同的本地文件RandomAccessFile file = new RandomAccessFile(filename, "rwd");file.setLength(filelength);file.close();conn.disconnect();//设置有多少条线程下载int threadsize = 3;//计算每个线程下载的量int threadlength = filelength % 3 == 0 ? filelength/3:filelength+1;for(int i = 0;i < threadsize;i++){//设置每条线程从哪个位置开始下载int startposition = i * threadlength;//从文件的什么位置开始写入数据RandomAccessFile threadfile = new RandomAccessFile(filename, "rwd");threadfile.seek(startposition);//启动三条线程分别从startposition位置开始下载文件new DownLoadThread(i,startposition,threadfile,threadlength,path).start();}int quit = System.in.read();while('q' != quit){Thread.sleep(2000);}}private class DownLoadThread extends Thread {private int threadid;private int startposition;private RandomAccessFile threadfile;private int threadlength;private String path;public DownLoadThread(int threadid, int startposition,RandomAccessFile threadfile, int threadlength, String path) {this.threadid = threadid;this.startposition = startposition;this.threadfile = threadfile;this.threadlength = threadlength;this.path = path;}public DownLoadThread() {}@Overridepublic void run() {try{URL url = new URL(path);HttpURLConnection conn = (HttpURLConnection) url.openConnection();conn.setConnectTimeout(5000);conn.setRequestMethod("GET");//指定从什么位置开始下载conn.setRequestProperty("Range", "bytes="+startposition+"-");//System.out.println(conn.getResponseCode());if(conn.getResponseCode() == 206){InputStream is = conn.getInputStream();byte[] buffer = new byte[1024];int len = -1;int length = 0;while(length < threadlength && (len = is.read(buffer)) != -1){threadfile.write(buffer,0,len);//计算累计下载的长度length += len;}threadfile.close();is.close();System.out.println("线程"+(threadid+1) + "已下载完成");}}catch(Exception ex){System.out.println("线程"+(threadid+1) + "下载出错"+ ex);}}}
}


注意事项:
int filelength = conn.getContentLength(); //获得下载文件的长度(大小)
RandomAccessFile file = new RandomAccessFile(filename, “rwd”); //该类运行对文件进行读写,是多线程下载的核心
nt threadlength = filelength % 3 == 0 ? filelength/3:filelength+1; //计算每个线程要下载的量
conn.setRequestProperty(“Range”, “bytes=”+startposition+“-”); //指定从哪个位置开始读写,这个是URLConnection提供的方法
//System.out.println(conn.getResponseCode()); //这个注释了的代码是用来查看conn的返回码的,我们前面用的都是200, 而针对多线程的话,通常是206,必要时我们可以通过调用该方法查看返回码!
int quit = System.in.read();while(‘q’ != quit){Thread.sleep(2000);} //这段代码是做延时操作的,因为我们用的是本地下载,可能该方法运行完了,而我们的 线程还没有开启,这样会引发异常,这里的话,让用户输入一个字符,如果是’q’的话就退出
5.使用DownloadManager更新应用并覆盖安装:
下面的代码可以直接用,加入到项目后,记得为这个内部广播注册一个过滤器:
AndroidManifest.xml
import android.app.DownloadManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.support.v7.app.AppCompatActivity;/*** Created by Jay on 2015/9/9 0009.*/
public class UpdateAct extends AppCompatActivity {//这个更新的APK的版本部分,我们是这样命名的:xxx_v1.0.0_xxxxxxxxx.apk//这里我们用的是git提交版本的前九位作为表示private static final String FILE_NAME = "ABCDEFGHI";@Overridepublic void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);String endpoint = "";try {//这部分是获取AndroidManifest.xml里的配置信息的,包名,以及Meta_data里保存的东西ApplicationInfo info = getPackageManager().getApplicationInfo(getPackageName(), PackageManager.GET_META_DATA);//我们在meta_data保存了xxx.xxx这样一个数据,是https开头的一个链接,这里替换成httpendpoint = info.metaData.getString("xxxx.xxxx").replace("https","http");} catch (PackageManager.NameNotFoundException e) {e.printStackTrace();}//下面的都是拼接apk更新下载url的,path是保存的文件夹路径final String _Path = this.getIntent().getStringExtra("path");final String _Url = endpoint + _Path;final DownloadManager _DownloadManager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);DownloadManager.Request _Request = new DownloadManager.Request(Uri.parse(_Url));_Request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, FILE_NAME + ".apk");_Request.setTitle(this.getString(R.string.app_name));//是否显示下载对话框_Request.setShowRunningNotification(true);_Request.setMimeType("application/com.trinea.download.file");//将下载请求放入队列_DownloadManager.enqueue(_Request);this.finish();}//注册一个广播接收器,当下载完毕后会收到一个android.intent.action.DOWNLOAD_COMPLETE//的广播,在这里取出队列里下载任务,进行安装public static class Receiver extends BroadcastReceiver {public void onReceive(Context context, Intent intent) {final DownloadManager _DownloadManager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);final long _DownloadId = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, 0);final DownloadManager.Query _Query = new DownloadManager.Query();_Query.setFilterById(_DownloadId);final Cursor _Cursor = _DownloadManager.query(_Query);if (_Cursor.moveToFirst()) {final int _Status = _Cursor.getInt(_Cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_STATUS));final String _Name = _Cursor.getString(_Cursor.getColumnIndexOrThrow("local_filename"));if (_Status == DownloadManager.STATUS_SUCCESSFUL&& _Name.indexOf(FILE_NAME) != 0) {Intent _Intent = new Intent(Intent.ACTION_VIEW);_Intent.setDataAndType(Uri.parse(_Cursor.getString(_Cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_LOCAL_URI))),"application/vnd.android.package-archive");_Intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);context.startActivity(_Intent);}}_Cursor.close();}}
}
6.Android多线程断点下载的代码流程解析:

实现流程全解析:
Step 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);}@Overridepublic 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)");}@Overridepublic void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {//当版本号发生改变时调用该方法,这里删除数据表,在实际业务中一般是要进行数据备份的db.execSQL("DROP TABLE IF EXISTS filedownlog");onCreate(db);}}
Step 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<Integer, Integer> getData(String path){//获得可读数据库句柄,通常内部实现返回的其实都是可写的数据库句柄SQLiteDatabase db = openHelper.getReadableDatabase();//根据下载的路径查询所有现场的下载数据,返回的Cursor指向第一条记录之前Cursor cursor = db.rawQuery("select threadid, downlength from filedownlog where downpath=?",new String[]{path});//建立一个哈希表用于存放每条线程已下载的文件长度Map<Integer,Integer> data = new HashMap<Integer, Integer>();//从第一条记录开始遍历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<Integer,Integer> map){SQLiteDatabase db = openHelper.getWritableDatabase();//开启事务,因为此处需要插入多条数据db.beginTransaction();try{//使用增强for循环遍历数据集合for(Map.Entry<Integer, Integer> 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();}}
Step 3:创建一个文件下载器类
好了,数据库管理器与操作类都完成了接着就该弄一个文件下载器类了,在该类中又要完成 什么操作呢?要做的事就多了:
①定义一堆变量,核心是线程池threads和同步集合ConcurrentHashMap,用于缓存线程下载长度的
②定义一个获取线程池中线程数的方法;
③定义一个退出下载的方法,
④获取当前文件大小的方法
⑤累计当前已下载长度的方法,这里需要添加一个synchronized关键字,用来解决并发访问的问题
⑥更新指定线程最后的下载位置,同样也需要用同步
⑦在构造方法中完成文件下载,线程开辟等操作
⑧获取文件名的方法:先截取提供的url最后的’/'后面的字符串,如果获取不到,再从头字段查找,还是 找不到的话,就使用网卡标识数字+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; //获取本地数据库的业务Beanprivate boolean exited; //停止下载的标志private Context context; //程序的上下文对象private int downloadedSize = 0; //已下载的文件长度private int fileSize = 0; //开始的文件长度private DownloadThread[] threads; //根据线程数设置下载的线程池private File saveFile; //数据保存到本地的文件中private Map<Integer, Integer> data = new ConcurrentHashMap<Integer, Integer>(); //缓存个条线程的下载的长度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); //根据下载路径实例化URLif(!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<Integer,Integer> logdata = fileService.getData(downloadUrl); //获取下载记录//如果存在下载记录if(logdata.size() > 0){//遍历集合中的数据,把每条线程已下载的数据长度放入data中for(Map.Entry<Integer, Integer> entry : logdata.entrySet()){data.put(entry.getKey(), entry.getValue());}}//如果已下载的数据的线程数和现在设置的线程数相同时则计算所有现场已经下载的数据总长度if(this.data.size() == this.threads.length){//遍历每条线程已下载的数据for(int i = 0;i < this.threads.length;i++){this.downloadedSize += this.data.get(i+1);}print("已下载的长度" + this.downloadedSize + "个字节");}//使用条件运算符求出每个线程需要下载的数据长度this.block = (this.fileSize % this.threads.length) == 0?this.fileSize / this.threads.length:this.fileSize / this.threads.length + 1;}else{//打印错误信息print("服务器响应错误:" + conn.getResponseCode() + conn.getResponseMessage());throw new RuntimeException("服务器反馈出错");}}catch (Exception e) {print(e.toString()); //打印错误throw new RuntimeException("无法连接URL");}}/*** 获取文件名* */private String getFileName(HttpURLConnection conn){//从下载的路径的字符串中获取文件的名称String filename = this.downloadUrl.substring(this.downloadUrl.lastIndexOf('/') + 1);if(filename == null || "".equals(filename.trim())){ //如果获取不到文件名称for(int i = 0;;i++) //使用无限循环遍历{String mine = conn.getHeaderField(i); //从返回的流中获取特定索引的头字段的值if (mine == null) break; //如果遍历到了返回头末尾则退出循环//获取content-disposition返回字段,里面可能包含文件名if("content-disposition".equals(conn.getHeaderFieldKey(i).toLowerCase())){//使用正则表达式查询文件名Matcher m = Pattern.compile(".*filename=(.*)").matcher(mine.toLowerCase());if(m.find()) return m.group(1); //如果有符合正则表达式规则的字符串,返回}}filename = UUID.randomUUID()+ ".tmp";//如果都没找到的话,默认取一个文件名//由网卡标识数字(每个网卡都有唯一的标识号)以及CPU时间的唯一数字生成的一个16字节的二进制作为文件名}return filename;}/*** 开始下载文件* @param listener 监听下载数量的变化,如果不需要了解实时下载的数量,可以设置为null* @return 已下载文件大小* @throws Exception*///进行下载,如果有异常的话,抛出异常给调用者public int download(DownloadProgressListener listener) throws Exception{try {RandomAccessFile randOut = new RandomAccessFile(this.saveFile, "rwd");//设置文件大小if(this.fileSize>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<String, String> getHttpResponseHeader(HttpURLConnection http) {//使用LinkedHashMap保证写入和便利的时候的顺序相同,而且允许空值Map<String, String> header = new LinkedHashMap<String, String>();//此处使用无线循环,因为不知道头字段的数量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<String, String> header = getHttpResponseHeader(http);//使用增强for循环遍历取得头字段的值,此时遍历的循环顺序与输入树勋相同for(Map.Entry<String, String> 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);}
}
Step 4:自定义一个下载线程类
这个自定义的线程类要做的事情如下:
① 首先肯定是要继承Thread类啦,然后重写Run()方法
② Run()方法:先判断是否下载完成,没有得话:打开URLConnection链接,接着RandomAccessFile 进行数据读写,完成时设置完成标记为true,发生异常的话设置长度为-1,打印异常信息
③打印log信息的方法
④判断下载是否完成的方法(根据完成标记)
⑤获得已下载的内容大小
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; //下载的URLprivate 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;}@Overridepublic 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]; //设置本地数据的缓存大小为1MBint 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; //设置该线程已经下载的长度为-1print("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;}
}
Step 5:创建一个DownloadProgressListener接口监听下载进度
FileDownloader中使用了DownloadProgressListener进行进度监听, 所以这里需要创建一个接口,同时定义一个方法的空实现:
DownloadProgressListener.java:
package com.jay.example.service;
public interface DownloadProgressListener {public void onDownloadSize(int downloadedSize);
}
Step 6:编写我们的布局代码
另外调用android:enabled="false"设置组件是否可点击, 代码如下
activity_main.xml:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:id="@+id/LinearLayout1"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"tools:context="com.jay.example.multhreadcontinuabledemo.MainActivity" ><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="请输入要下载的文件地址" /><EditText android:id="@+id/editpath"android:layout_width="match_parent"android:layout_height="wrap_content"android:text="http://10.13.20.32:8080/Test/twelve.mp3" /><Button android:layout_width="wrap_content"android:layout_height="wrap_content"android:id="@+id/btndown"android:text="下载" /><Button android:layout_width="wrap_content"android:layout_height="wrap_content"android:id="@+id/btnstop"android:text="停止"android:enabled="false" /><ProgressBarandroid:layout_width="fill_parent" android:layout_height="18dp" style="?android:attr/progressBarStyleHorizontal"android:id="@+id/progressBar"/><TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:gravity="center"android:id="@+id/textresult"android:text="显示实时下载的百分比"/></LinearLayout>
Step 7:MainActivity的编写
最后就是我们的MainActivity了,完成组件以及相关变量的初始化; 使用handler来完成界面的更新操作,另外耗时操作不能够在主线程中进行, 所以这里需要开辟新的线程,这里用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;}}}@Overrideprotected 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));}} }}
}
Step 8:AndroidManifest.xml文件中添加相关权限
<!-- 访问internet权限 -->
<uses-permission android:name="android.permission.INTERNET"/>
<!-- 在SDCard中创建与删除文件权限 -->
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
<!-- 往SDCard写入数据权限 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
相关文章:
Android 文件上传与下载
在实际开发涉及文件上传不会自己写上传代码,一般 会集成第三网络库来做图片上传,比如android-async-http,okhttp等,另外还有七牛也提供 了下载和上传的API。 1.项目用到的图片上传的关键方法: 这里用到一个第三方的库…...
Java语言的充电桩系统Charging station system
介绍 SpringBoot 框架,充电桩平台充电桩系统充电平台充电桩互联互通协议云快充协议1.5-1.6协议新能源汽车二轮车公交车二轮车充电-四轮车充电充电源代码充电平台源码Java源码-共享充电桩-充电桩软件 软件介绍 小程序端:城市切换、附近电站、电桩详情页…...
RCE之无参数读取文件
什么是无参数? 顾名思义,就是只使用函数,且函数不能带有参数,这里有种种限制:比如我们选择的函数必须能接受其括号内函数的返回值;使用的函数规定必须参数为空或者为一个参数等 例题: <?…...
Python GUI开发必看:Tkinter Button控件使用详解
Button(按钮)组件用于实现各种各样的按钮。 Button组件可以包含文本或图像,你可以将一个Python的函数或方法与之相关联,当按钮被按下时,对应的函数或方法将被自动执行。 Button组件仅能显示单一字体的文本,…...
上海市计算机学会竞赛平台2024年7月月赛丙组得分排名
题目描述 给定 nn 名学生的考试得分,这些学生的学号为 11 到 nn,其第 ii 号学生的得分为 aiai,请将这些学生按照分数从大到小的顺序排列并输出学号序列。 若两个学生得分相同,则先输出较小的学号。 输入格式 第一行…...
Can GPT-3 Perform Statutory Reasoning?
文章目录 题目摘要相关工作SARAGPT-3 对美国法典的了解GPT-3 在对合成法规进行简单推理时遇到困难结论 题目 GPT-3 可以进行法定推理吗? 论文地址:https://arxiv.org/abs/2302.06100 摘要 法定推理是用事实和法规进行推理的任务,法规是立法机…...
redis面试(十一)锁超时
boolean res lock.tryLock(100, 10, TimeUnit.SECONDS); RedissonLock里面有这样一个方法tryLock(),意思是尝试获取锁的结果。 最大等待时间100s,并且获取到锁之后,10s之内没有释放的话,锁会自动失效。 尝试获取锁超时 time …...
C代码做底层及Matlab_SimuLink做应用层设计单片机程序
前言:SimuLink工具极其强大,但是能直接支持单片机自主开发的很少,造成这个问题的原因主要是我们使用的芯片底层多是C代码工程,芯片厂家也只提供C代码库,很少能提供SimuLink的支持库,即使提供也不是很不完善,如NXP的一些芯片提供的SimuLink库不含盖高级应用,再比如意法半…...
Cloud Kernel SIG 月度动态:ANCK OOT 驱动基线更新,发布 2 个 ANCK 版本
Cloud Kernel SIG(Special Interest Group):支撑龙蜥内核版本的研发、发布和服务,提供生产可用的高性价比内核产品。 01 SIG 整体进展 1. 发布 ANCK 5.10-016.4 小版本。 2. 发布 ANCK 5.10-017.1 小版本。 3. ANCK 新增海光平…...
vue3仿飞书头像,根据不同名称生成不同的头像背景色
效果展示: 传递三个参数: name:要显示的名称;size:头像的大小;cutNum:分割当前名称的最后几位数; 代码如下: <template><div:style"{color: #fff,borde…...
SpringBoot整合三方
SpringBoot整合redis 引入redis依赖包 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><dependency><groupId>redis.clients</groupId&g…...
React之组件的使用
Vue、React和Angular是三个流行的前端框架,采用组件化的开发方式。支持虚拟DOM(Virtual DOM)技术,有丰富的生态系统、大量的插件和工具可以使用。Vue的语法是传统的HTML和JavaScript,React使用JSX语法,Angu…...
深度学习--长短期记忆网络
1.引入 RNN 可以将以前的信息与当前的信息进行连接。例如,在视频中,可以用前面的帧来 帮助理解当前帧的内容;在文本中,可以用前面半句话的内容来预测后面的内容。但是, RNN 存在一个记忆消失的问题。例如,…...
研0 冲刺算法竞赛 day29 P2249 【深基13.例1】查找
P2249 【深基13.例1】查找 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 思路: ①二分查找 ②stl函数:lower_bound(a.begin(),a.end(),x) 返回第一个大于等于 x的数的地址 代码: #include<iostream> #include<algorithm> …...
基于vue框架的CKD电子病历系统nfa2e(程序+源码+数据库+调试部署+开发环境)系统界面在最后面。
系统程序文件列表 项目功能:患者,医生,药品信息,电子病历,临时医嘱,长期医嘱,健康科普 开题报告内容 基于Vue框架的CKD电子病历系统 开题报告 一、选题背景 随着信息技术的飞速发展和医疗信息化的深入推进,电子病历系统(Electronic Medic…...
笔记:python 安装tar包报错
报错信息 ERROR: Could not find a version that satisfies the requirement setuptools>40.8.0 (from versions: none)ERROR: No matching distribution found for setuptools>40.8.0分析 1,当前已安装 setuptools 并且版本超过40.8.0 解决方案 缺包了&am…...
575. 分糖果
哈喽!大家好,我是奇哥,一位专门给面试官添堵的职业面试员 文章持续更新,可以微信搜索【小奇JAVA面试】第一时间阅读,回复【资料】更有我为大家准备的福利哟! 文章目录 一、题目二、答案三、总结 一、题目 …...
手机电量消耗分析工具 Battery Historian 指南
阅读五分钟,每日十点,和您一起终身学习,这里是程序员Android 本篇文章主要介绍 Android 开发中 电量 的部分知识点,通过阅读本篇文章,您将收获以下内容: 一、安装Battery Historian二、收集Batterystats 数据三、使用B…...
笔试练习day4
目录 WY22 Fibonacci数列题目解析解法暴力解法贪心代码 NC242 单词搜索题目解析例子1解析例子2解析例子3解析解法深度优先遍历dfs实现最终代码 BC140 杨辉三角解法线性dp问题代码 感谢各位大佬对我的支持,如果我的文章对你有用,欢迎点击以下链接 🐒🐒&am…...
公主少爷都爱看的haproxy七层代理详细介绍及常见实验详解
目录 一、负载均衡 1.1什么是负载均衡 1.2为什么要实验负载均衡 1.3四层负载均衡 1.4七层负载均衡 1.5四层负载均衡和七层负载均衡的对比 二、什么是haproxy 2.1定义 2. 2功能和特点 2.3应用场景 2.4haproxy的分类 三、安装及基本配置的信息 3.1软件的安装 3.2ha…...
论文解读:交大港大上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架(二)
HoST框架核心实现方法详解 - 论文深度解读(第二部分) 《Learning Humanoid Standing-up Control across Diverse Postures》 系列文章: 论文深度解读 + 算法与代码分析(二) 作者机构: 上海AI Lab, 上海交通大学, 香港大学, 浙江大学, 香港中文大学 论文主题: 人形机器人…...
PHP和Node.js哪个更爽?
先说结论,rust完胜。 php:laravel,swoole,webman,最开始在苏宁的时候写了几年php,当时觉得php真的是世界上最好的语言,因为当初活在舒适圈里,不愿意跳出来,就好比当初活在…...
相机Camera日志实例分析之二:相机Camx【专业模式开启直方图拍照】单帧流程日志详解
【关注我,后续持续新增专题博文,谢谢!!!】 上一篇我们讲了: 这一篇我们开始讲: 目录 一、场景操作步骤 二、日志基础关键字分级如下 三、场景日志如下: 一、场景操作步骤 操作步…...
渗透实战PortSwigger靶场-XSS Lab 14:大多数标签和属性被阻止
<script>标签被拦截 我们需要把全部可用的 tag 和 event 进行暴力破解 XSS cheat sheet: https://portswigger.net/web-security/cross-site-scripting/cheat-sheet 通过爆破发现body可以用 再把全部 events 放进去爆破 这些 event 全部可用 <body onres…...
mysql已经安装,但是通过rpm -q 没有找mysql相关的已安装包
文章目录 现象:mysql已经安装,但是通过rpm -q 没有找mysql相关的已安装包遇到 rpm 命令找不到已经安装的 MySQL 包时,可能是因为以下几个原因:1.MySQL 不是通过 RPM 包安装的2.RPM 数据库损坏3.使用了不同的包名或路径4.使用其他包…...
让回归模型不再被异常值“带跑偏“,MSE和Cauchy损失函数在噪声数据环境下的实战对比
在机器学习的回归分析中,损失函数的选择对模型性能具有决定性影响。均方误差(MSE)作为经典的损失函数,在处理干净数据时表现优异,但在面对包含异常值的噪声数据时,其对大误差的二次惩罚机制往往导致模型参数…...
【Go语言基础【13】】函数、闭包、方法
文章目录 零、概述一、函数基础1、函数基础概念2、参数传递机制3、返回值特性3.1. 多返回值3.2. 命名返回值3.3. 错误处理 二、函数类型与高阶函数1. 函数类型定义2. 高阶函数(函数作为参数、返回值) 三、匿名函数与闭包1. 匿名函数(Lambda函…...
QT3D学习笔记——圆台、圆锥
类名作用Qt3DWindow3D渲染窗口容器QEntity场景中的实体(对象或容器)QCamera控制观察视角QPointLight点光源QConeMesh圆锥几何网格QTransform控制实体的位置/旋转/缩放QPhongMaterialPhong光照材质(定义颜色、反光等)QFirstPersonC…...
【Android】Android 开发 ADB 常用指令
查看当前连接的设备 adb devices 连接设备 adb connect 设备IP 断开已连接的设备 adb disconnect 设备IP 安装应用 adb install 安装包的路径 卸载应用 adb uninstall 应用包名 查看已安装的应用包名 adb shell pm list packages 查看已安装的第三方应用包名 adb shell pm list…...
Git常用命令完全指南:从入门到精通
Git常用命令完全指南:从入门到精通 一、基础配置命令 1. 用户信息配置 # 设置全局用户名 git config --global user.name "你的名字"# 设置全局邮箱 git config --global user.email "你的邮箱example.com"# 查看所有配置 git config --list…...
