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"/>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/395538.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

新华三H3CNE网络工程师认证—路由基础

我们的一个个网络其实是由不同的广播域构成的&#xff0c;而路由器的作用就是用来连接不同的广播域。那么不同广播域之间是如何通信的呢&#xff1f;比如有三个网段&#xff0c;1.0、2.0和3.0。网段1.0和网段2.0通信需要构造数据包&#xff0c;源是1.1&#xff0c;目标去往2.1。…

Java程序的执行过程:从编译到垃圾回收,一文读懂Java程序的生命周期

你是否曾经好奇过当你编写一段Java代码并运行它时&#xff0c;背后究竟发生了什么&#xff1f;Java程序的执行过程似乎神秘而复杂&#xff0c;但实际上&#xff0c;它遵循着一系列精心设计的步骤。本文将为你揭开Java程序执行的神秘面纱&#xff0c;带你深入了解从源代码到最终…

SpringBoot企业人事管理系统-附源码与配套论文

1.1引言 随着计算机技术的飞速发展&#xff0c;计算机在各种单位机构管理中应用的普及﹐管理信息系统的开发在强调管理、强调信息的现代社会中也显得越来越重要。因此,利用计算机高效率地完成人事管理的日常事务&#xff0c;是适应现代各种单位机构制度要求、推动各种单位机构…

SpringBoot统一功能处理——拦截器

目录 一、什么是拦截器&#xff1f; 二、拦截器使用 2.1 定义拦截器 2.2 注册配置拦截器 三、拦截器详解 3.1 拦截器的拦截路径配置 3.2 拦截器执行流程 一、什么是拦截器&#xff1f; 拦截器是Spring框架提供的核心功能之一, 主要用来拦截用户的请求, 在指定方法前后,…

【2024年精选】分享7款国内大学ai写论文推荐网站工具

在2024年&#xff0c;AI技术的飞速发展为学术研究和论文写作带来了革命性的变化。众多AI论文写作工具应运而生&#xff0c;帮助学生和研究人员提高写作效率&#xff0c;提升论文质量。其中&#xff0c;AIPaperPass作为一款备受瞩目的国内AI写论文推荐网站工具&#xff0c;以其独…

CentOS7.6 HAproxy-7层负载均衡集群——实施方案

目录 1、前期环境准备 1.准备4台主机 1. 设置主机名 2. 设置IP地址然后重启网卡 3. 关闭防火墙和selinux 4. 全部的服务器完成时间统一 二、配置haproxy(192.168.200.11)服务器 1. 安装haproxy 2. haproxy 配置中分成五部分内容 3. 配置HAproxy&#xff08;192.168.2…

Animate软件基本概念:缓动、绘图纸外观及图层

FlashASer&#xff1a;AdobeAnimate2021软件零基础入门教程https://zhuanlan.zhihu.com/p/633230084 FlashASer&#xff1a;实用的各种Adobe Animate软件教程https://zhuanlan.zhihu.com/p/675680471 FlashASer&#xff1a;Animate教程及作品源文件https://zhuanlan.zhihu.co…

05_ Electron 自定义菜单、主进程与渲染进程通信

Electron 自定义菜单、主进程与渲染进程通信 一、定义顶部菜单二、Electron 自定义右键菜单1、使用 electron/remote 模块实现 三、 Electron 主进程和渲染进程通信场景1&#xff1a;渲染进程给主进程发送异步消息场景2&#xff1a;渲染进程给主进程发送异步消息&#xff0c;主…

数据结构--单链

#include "link.h" plink get_head() { plink pmalloc(sizeof(Link)); if(pNULL) { printf("申情节点失败\n"); return NULL; } p->len0; p->nextNULL; return p; } void head_insert(plink L,int a) {…

推荐一个uniapp选择文件上传的插件

插件地址&#xff1a;文件选择、文件上传组件&#xff08;图片&#xff0c;视频&#xff0c;文件等&#xff09; - DCloud 插件市场 支持 H5 / App / 微信小程序

K8s问题案例分析

1.worker节点宕机&#xff0c;请说明一下pod的驱逐流程&#xff1a; k8s有一个节点控制器&#xff0c;节点控制器在一段时间内无法和kubelet通信&#xff0c;那么就会给节点打上unknown 状态&#xff0c;并自动创建NoExecute污点,避免调度器调度新的pod到该节点。同时已经在这…

基于目标检测的目标跟踪(python)

文章目录 概要环境准备目标检测实现目标跟踪实现整合后的代码可能遇到的问题Could not load library libcudnn_ops_infer.so.8. Error: libcudnn_ops_infer.so.8: cannot open shared object file: No such file or directory参考概要 基于目标检测的目标跟踪过程通常包括以下…

Python新手错误集锦(PyCharm)

# 自学Python&#xff0c;用Pycharm作环境。我这个手新到这时我学习的第一个编程软件&#xff0c;且本人专业是化学&#xff0c;以前对电脑最高级的使用是玩扫雷游戏。所以这里集合的错误都是小透明错误&#xff0c;大部分人请绕道。不断更新中...... 缩进错误 记住“indent”…

力扣面试经典算法150题:买卖股票的最佳时机

买卖股票的最佳时机 今天的题目是力扣面试经典150题中的数组的简单题: 多数元素 题目链接&#xff1a;https://leetcode.cn/problems/best-time-to-buy-and-sell-stock/description/?envTypestudy-plan-v2&envIdtop-interview-150 题目描述 给定一个数组 prices&#xf…

SX_错误声明定义了两个以上的数据类型BUG解决_14

具体报错&#xff1a; In file included from perfmon_priv.h:32,from perfmond.c:21: perfmon_api.h:7:18: 错误: 声明指定了两个以上的数据类型7 | #define uint8_t unsigned char perfmon_api.h:7:27: 错误: 声明指定了两个以上的数据类型7 | #define uint8_t unsigned cha…

大数据Flink(一百零六):什么是阿里云实时计算Flink版

文章目录 什么是阿里云实时计算Flink版 一、产品概述 二、产品架构 三、产品优势 什么是阿里云实时计算Flink版 阿里云实时计算Flink版是一套基于Apache Flink构建的⼀站式实时大数据分析平台&#xff0c;提供端到端亚秒级实时数据分析能力&#xff0c;并通过标准SQL降低业…

c++ - c++11(1)

文章目录 前言一、统一的列表初始化1、使用{ }初始化2、 std::initializer_list 二、声明1、auto2、decltype3、nullptr 三、范围for循环四、右值引用1、左值引用和右值引用2、左值引用和右值引用的比较3、左值引用的使用场景4、右值引用的使用场景5、完美转发 前言 一、统一的…

Python爬虫入门实战(详细步骤)

1. 技术选型 爬虫这个功能&#xff0c;我个人理解是什么语言都能写的&#xff0c;只要能正常发送 HTTP 请求&#xff0c;将响应回来的静态页面模版 HTML 上把我们所需要的数据提取出来就可以了&#xff0c;原理很简单&#xff0c;这个东西当然可以手动去统计收集&#xff0c;但…

【C语言】预处理详解(上)

文章目录 前言1. 预定义符号2. #define 定义常量3. #define定义宏4. 带有副作用的宏参数5. 宏替换的规则 前言 在讲解编译和链接的知识点中&#xff0c;我提到过翻译环境中主要由编译和链接两大部分所组成。 其中&#xff0c;编译又包括了预处理、编译和汇编。当时&#xff0c…

【准则化的思想】变异测试的真正价值

下面我们来讨论变异充分准则。这个准则&#xff0c;同样是一种基于缺陷的充分准则&#xff0c;但是跟我们前面讨论过的准则相比&#xff0c;思路又完全不同。我们来具体看一看。 首先&#xff0c;它为什么叫“变异”充分准则呢&#xff1f;我们通常说的变异&#xff0c;指的是…