Android 多线程下载断点续传

Android多线程断点续传下载原理及实现 这段时间看了看工作室的工具库的下载组件,发现其存在一些问题: 1.下载核心逻辑有 bug,在暂停下载或下载失败等情况时有概率无法顺利完成下载。 2.虽然原来的设计是采用多线程断点续传的设计,但打了一下日志发现其实下载任务都是在同一个线程下串行执行,并没有起到加快下载速度的作用。 考虑到原来的代码并不复杂,因此对这部分下载组件进行了重写。这里记录一下里面的多线程断点续传功能的实现。 更多完整项... 阅读详情

随笔,注释写得很清楚了,如有不明白的地方,相互交流.....

 

package com.download.service;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;

/**
 * 下载线程
 * @author dream
 *
 */
public class DownThread extends Thread {
	/**
	 * 保存文件路径
	 */
	private String filepath;
	/**
	 * 停止线程标识
	 */
	private boolean stopFlag=false;
	/**
	 * 下载开始位置
	 */
	private int startposition;
	/**
	 * 下载结束位置
	 */
	private int endposition;
	/**
	 * 下载链接
	 */
	private String url;
	/**
	 * 下载服务类
	 */
	private HttpDownService hds;
	/**
	 * 线程下载完毕标识
	 */
	private boolean success=false;
	/**
	 * 当前线程下载量
	 */
	private int currentjd=0;
	
	/**
	 * 获取当前线程下载量
	 * @return
	 */
	public int getCurrentjd() {
		return currentjd;
	}

	/**
	 * 当前线程是否下载完毕
	 * @return
	 */
	public boolean isSuccess() {
		return success;
	}

	@Override
	public void run() {
		BufferedReader br=null;
		RandomAccessFile fout=null;
		int oldposition=0;
		try{
			File fstate=null;
			if(FileUtil.Exists(HttpDownService.DIR, this.getName()+".txt")){
				fstate=FileUtil.createFile(HttpDownService.DIR, this.getName()+".txt");
				br=new BufferedReader(new FileReader(fstate));
				String line=br.readLine();
				br.close();
				if(line!=null){
					line=line.trim();
					//是否已完成下载
					if("complete".equals(line)){
						this.currentjd=endposition-startposition;
						this.success=true;
						return;
					}else{
						int newposition=Integer.parseInt(line);
						//若当前开始位置大于下载开始位置则保存下载开始位置(用于计算下载量)
						if(newposition>startposition){
							oldposition=startposition;
							startposition=newposition;
						}
					}
				}
			}else{
				fstate=FileUtil.createFile(HttpDownService.DIR, this.getName()+".txt");
				oldposition=startposition;
			}
			
			
			fout= new RandomAccessFile(this.filepath, "rwd");
			fout.seek(startposition);
			HttpURLConnection http=DownUtil.getDownConnection(this.url, this.startposition, this.endposition);
			InputStream in= http.getInputStream();
			byte[] buff=new byte[1024];
			int len=0;
			while((len=in.read(buff, 0, 1024))>0){
				success=false;
				if(stopFlag){
					if(fout!=null){
						fout.close();
						return;
					}
					return;
				}
				
				fout.write(buff, 0, len);
				//当前线程下载进度
				startposition=startposition+len;
				int jd=startposition-oldposition;
				//刷新当前进度消息
				this.currentjd=jd;
				//把进度存储到存储设备中去
				FileOutputStream fw=new FileOutputStream(fstate);
				fw.write((startposition+"").getBytes());
				fw.close();
				
			}
			//线程下载完毕
			fout.close();

			//将线程下载完毕信息写到磁盘
			FileOutputStream fw=new FileOutputStream(fstate);
			fw.write(("complete").getBytes());
			fw.close();
			
			//下载完毕信号
			this.success=true;
			//更新进度信息
			this.currentjd=endposition-oldposition;
		}catch(Exception e){
			throw new RuntimeException(e);
		}finally{
			try{
				if(fout!=null){
					fout.close();
				}
			}catch(Exception e){
				throw new RuntimeException(e);
			}
		}
		
		
		
	}

	public DownThread(String randomFile,String threadName,int startposition,int endposition,String url,HttpDownService hds){
		this.filepath=randomFile;
		this.setName(threadName);
		this.startposition=startposition;
		this.endposition=endposition;
		this.url=url;
		this.hds=hds;
	}
	
	/**
	 * 停止下载线程
	 */
	public void StopThread(){
		this.stopFlag=true;
	}
}

package com.download.service;

import java.net.HttpURLConnection;
import java.net.URL;

/**
 * 计算各个线程下载开始和结束位置
 * @author dream
 *
 */
public class DownUtil {
	public static long getContentLength(String url){
		try{
			HttpURLConnection http= DownUtil.getDownConnection(url);
			long len=http.getContentLength();
			http.disconnect();
			return len;
		}catch(Exception e){
			throw new RuntimeException(e);
		}
	}
	
	/**
	 * 获取Http连接
	 * @param url
	 * @return
	 */
	public static HttpURLConnection getDownConnection(String url){
		try{
			URL hurl=new URL(url);
			HttpURLConnection http= (HttpURLConnection) hurl.openConnection();
			http.setRequestProperty("User-Agent", "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.2; WOW64; Trident/6.0; .NET4.0E; .NET4.0C; .NET CLR 3.5.30729; .NET CLR 2.0.50727; .NET CLR 3.0.30729; InfoPath.3; MALCJS)");
			http.setRequestProperty("Connection", "Keep-Alive");
			http.setRequestMethod("GET");
			return http;
		}catch(Exception e){
			throw new RuntimeException(e);
		}
	}
	
	/***
	 * 获取Http连接 
	 * @param url
	 * @param startposition
	 * @param endposition
	 * @return
	 */
	public static HttpURLConnection getDownConnection(String url,long startposition,long endposition){
		try{
			URL hurl=new URL(url);
			HttpURLConnection http= (HttpURLConnection) hurl.openConnection();
			http.setRequestProperty("User-Agent", "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.2; WOW64; Trident/6.0; .NET4.0E; .NET4.0C; .NET CLR 3.5.30729; .NET CLR 2.0.50727; .NET CLR 3.0.30729; InfoPath.3; MALCJS)");
			http.setRequestProperty("Connection", "Keep-Alive");
			http.setRequestMethod("GET");
			http.setRequestProperty("Range", "bytes=" + startposition + "-"+ endposition);
			return http;
		}catch(Exception e){
			throw new RuntimeException(e);
		}
	}
}

package com.download.service;

import java.io.File;

import android.os.Environment;

public class FileUtil {
	public static File createFile(String dir,String filename){
		Environment ev=new Environment();
		String diry= ev.getExternalStorageDirectory().getPath()+"/"+dir;
		File fdir=new File(diry);
		if(!fdir.exists()){
			fdir.mkdirs();
		}
		String path= ev.getExternalStorageDirectory().getPath()+"/"+dir+"/"+filename;
		File file=new File(path);
		try{
			if(file.exists()){
				return file;
			}else{
				file.createNewFile();
				return file;
			}
		}catch(Exception e){
			throw new RuntimeException(e);
		}
	}
	
	public static boolean Exists(String dir,String filename){
		Environment ev=new Environment();
		String path= ev.getExternalStorageDirectory().getPath()+"/"+dir+"/"+filename;
		File file=new File(path);
		return file.exists();
	}
}

package com.download.service;

import java.io.File;
import java.util.ArrayList;
import java.util.List;

import android.os.Handler;
/**
 * 多线程下载/断点续
 * @author dream
 *
 */
public class HttpDownService {
	/**
	 * 线程状态记录文件目录(处于SD卡)
	 */
	public static final String DIR="downtemp";
	/**
	 * 下载进度
	 */
	private int jd=0;
	/**
	 * 下载链接
	 */
	private String url;
	/**
	 * 总大小
	 */
	private int total;
	/**
	 * 获取总大小
	 * @return
	 */
	public int getTotal() {
		return total;
	}
	
	/**
	 * 设置总大小
	 * @param total
	 */
	public void setTotal(int total) {
		this.total = total;
	}

	/**
	 * 开启的下载线程数量(数量越多下载效率越高)
	 */
	private int threadcount;
	/**
	 * 保存文件路径
	 */
	private String filepath;
	/**
	 * 下载线程集合
	 */
	private List<DownThread> list;
	/**
	 * 界面UI handler处理
	 */
	private Handler handler;
	
	/**
	 * 下载状态信息处理
	 */
	private ProcessHandler ph;
	/**
	 * 扫描下载状态的线程
	 */
	private ScanThread st;
	public int getJd() {
		return this.jd;
	}

	public List<DownThread> getList() {
		return list;
	}

	/**
	 * 设置当前下载进度(线程安全)
	 * @param jd
	 * @param oldjd
	 */
	public void setJd(int jd,int oldjd) {
		synchronized (this) {
			this.jd=this.jd-oldjd;
			this.jd = this.jd+ jd;
		}
	}
	
	public HttpDownService(String url,String filepath,Handler handler,ProcessHandler ph){
		this.url=url;
		this.filepath=filepath;
		this.total=(int) DownUtil.getContentLength(url);
		this.handler=handler;
		this.ph=ph;
	}
	
	/***
	 * 启动下载
	 * @param threadCount
	 */
	public void StartDown(int threadCount){
		this.threadcount=threadCount;
		if(this.list==null){
			this.list=new ArrayList<DownThread>();
		}
		this.list.clear();
		int blocksize=this.total/this.threadcount;
		for(int i=0;i<this.threadcount;i++){
			int startposition=i*blocksize;
			int endposition=(i+1)*blocksize;
			if(this.threadcount-i==1){
				endposition=this.total;
			}
			DownThread dt=new DownThread(this.filepath, "e"+i, startposition,endposition, this.url, this);
			dt.start();
			this.list.add(dt);
		}
		st=new ScanThread(this, this.handler, this.ph);
		st.start();
	}
	
	/**
	 * 停止下载
	 */
	public void StopDown(){
		for(DownThread dt:this.list){
			dt.StopThread();
		}
		this.st.StopThread();
	}
	
	/**
	 * 下载是否完成
	 * @return
	 */
	public boolean IsDownComplete(){
		boolean ret=false;
		int i=0;
		for(DownThread dt:this.list){
			if(dt.isSuccess()){
				i++;
			}
		}
		if(this.threadcount==i){
			ret=true;
		}
		return ret;
	}
	
	/**
	 * 下载进度
	 * @return
	 */
	public int DownProcess(){
		int process=0;
		for(DownThread dt:this.list){
			process=process+dt.getCurrentjd();
		}
		return process;
	}
	
	/**
	 * 删除临时文件
	 */
	public void DeleteStateFile(){
		for(DownThread dt:this.list){
			File file=FileUtil.createFile(HttpDownService.DIR, dt.getName()+".txt");
			if(file.exists()){
				file.delete();
			}
		}
	}
	
}

package com.download.service;

import android.os.Handler;
/**
 * 下载信息转发处理
 * @author dream
 *
 */
public interface ProcessHandler {
	public void Process(Handler handler,int total,int current,boolean iscomplete);
}

package com.download.service;

import android.os.Handler;
import android.os.Message;

/**
 * 扫描下载状态,并转发状态信息
 * @author dream
 *
 */
public class ScanThread extends Thread {
	/**
	 * 下载服务类
	 */
	private HttpDownService hds;
	/**
	 * 界面UI Handler处理
	 */
	private Handler handler;
	/**
	 * 线程下载状态处理器
	 */
	private ProcessHandler ph;
	/**
	 * 停止线程标识
	 */
	private boolean Stopflag=false;
	public ScanThread(HttpDownService hds,Handler handler,ProcessHandler ph){
		this.handler=handler;
		this.hds=hds;
		this.ph=ph;
	}
	@Override
	public void run() {
		this.Stopflag=false;
		try{
			while(!Stopflag){
				Thread.sleep(10);
				this.ph.Process(this.handler, this.hds.getTotal(),this.hds.DownProcess(),this.hds.IsDownComplete());
				//当所有线程下载完毕时,转发下载完毕信息
				if(this.hds.IsDownComplete()){
					this.ph.Process(this.handler, this.hds.getTotal(),this.hds.DownProcess(),this.hds.IsDownComplete());
					this.hds.DeleteStateFile();
					this.Stopflag=true;
					return;
				}
			}
		}catch(Exception e){
			throw new RuntimeException(e); 
		}
	}
	
	/**
	 * 停止线程
	 */
	public void StopThread(){
		this.Stopflag=true;
	}

}


效果图:

Android 断点续传进阶之多线程下载 小编这些年深知大多数初中级Android工程师,想要提升自己,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。因此我收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!资料⬅专栏获取", 阅读详情

相关推荐

Android 中实现多线程下载和断点续传的原理和代码

在上一章,用Java代码实现了 多线程下载和断点续传, 现在我们将它移植到 Android 上面.下面这个类能帮助我们实现多线程下载和断点续传功能. 介绍一下这个类的使用方法.快速下载 1. 该类的构造方法需要提供三个参数. 2. public MuchThreadDown(String path, String targetFilePath, int threadCount)

喜洋洋 2484

Android之—多线程断点续传下载

Android之——多线程断点续传下载示例 文件下载在App应用中也用到很多,一般版本更新时多要用的文件下载来进行处理,以前也有看过很多大神有过该方面的博客,今天我也自己来实践一下,写的一般,还请大家多提意见,共同进步。主要思路: 1.多线程下载: 首先通过下载总线程数来划分文件的下载区域:利用int range = fileSize / threadCount;得到每一段下载量;每

qingxuan521721的博客 606

Android网络多线程断点续传下载实战教程

在了解多线程下载之前,我们先来了解单线程下载的基本原理。单线程下载是指用户设备上的下载客户端与服务器之间建立一个HTTP连接,通过这个连接从服务器下载数据,直到文件下载完成。然而,这种方式受到网络带宽和服务器处理能力的限制,下载速度有限。

weixin_31185473的博客 888

android 多线程下载,断点续传,线程池

android 多线程下载,断点续传,线程池你可以在这里看到这个demo的源码: https://github.com/onlynight/MultiThreadDownloader效果图这张效果图是同时开启三个下载任务,限制下载线程数量的效果图。多线程下载原理多线程下载的原理就是将下载任务分割成一个个小片段再将每个小片段分配给各个线程进行下载。 例如一个文件大小为100M,我们决定使用4个线程

Android开源技术交流 4469

Android多线程断点续传下载及在线音乐播放指南

本文还有配套的精品资源,点击获取 简介:在Android应用开发中,实现高效且稳定的多线程断点续传下载和在线音乐播放涉及多个关键技术和组件。本文将详细探讨如何通过网络编程、文件操作、多线程处理等技术实现这些功能,以及如何使用网络请求库、线程池、内存管理、多媒体播放、服务、通知和下载管理器等组件,为用户提供无缝的下载和音乐播放体验。 1. Android多线程下载技...

weixin_42581003的博客 872

避坑指南:微信小程序蓝牙广播在iOS上的那些‘坑’(附serviceUuids传值解决方案)

本文深入解析微信小程序蓝牙广播在iOS平台上的开发难点,特别是`serviceUuids`传值解决方案。通过对比iOS与安卓的蓝牙协议实现差异,提供实战编码策略和硬件端配合方案,帮助开发者有效规避兼容性问题,提升蓝牙外围设备开发效率。

weixin_30316097的博客 423

Android多线程断点续传下载

版权声明:情之所钟,虽丑不嫌。 https://blog.youkuaiyun.com/u012827205/article/details/26945735 这个月接到一个项目。要写一个像36...

weixin_30699741的博客 101

Vectorless dynamic动态功耗分析——Voltus

power 计算工具会根据设计信息和用户提供的activity和switching约束来生成虚拟的worst-case的power vector,难点在于哪些instances视为switching,在没有仿真的前提下如何确定开关的时间,使用timing文件(TWF)

qq_44347276的博客 2789

Android实现网络多线程断点续传下载

本示例介绍在Android平台下通过HTTP协议实现断点续传下载。 我们编写的是Andorid的HTTP协议多线程断点下载应用程序。直接使用单线程下载HTTP文件对我们来说是一件非常简单的事。那么,多线程断点需要什么功能? 1.多线程下载, 2.支持断点。   使用多线程的好处:使用多线程下载会提升文件下载的速度。那么多线程下载文件的过程是:   

StoneNotes的专栏 1047

Python3.11安装fairseq避坑指南:解决dataclasses兼容性问题(附详细代码修改)

本文详细解析了在Python 3.11环境下安装Fairseq时因dataclasses模块规则变更导致的安装失败问题。通过定位核心配置文件,将`default=SomeConfig()`修改为`default_factory=SomeConfig`,并调整依赖版本,提供了从源码修改到环境验证的完整解决方案,确保Fairseq顺利运行。

weixin_29098367的博客 495

飞牛fnOS安装1Panel后Docker容器消失?三步教你恢复数据并避免踩坑

本文详细解析了飞牛fnOS安装1Panel后Docker容器消失的问题,指出这是由于daemon.json文件被覆盖导致。通过SSH连接、修复配置文件和系统重启三个步骤,用户可以轻松恢复数据。文章还提供了预防措施和最佳实践,帮助用户避免类似问题。

yellow的专栏 869

Android开发--多线程下载加断点续传

文件下载在App应用中也用到很多,一般版本更新时多要用的文件下载来进行处理,以前也有看过很多大神有过该方面的博客,今天我也自己来实践一下,写的一般,还请大家多提意见,共同进步。主要思路:         1.多线程下载:                首先通过下载总线程数来划分文件的下载区域:利用int range = fileSize / threadCount;得到每一段下载量;每一段的位

Joe_c 的专栏 2421

FPGA之串口收发字符串之(四)——串口屏USART_HMI介绍

之前对串口各模块的设计都做了相关介绍。这里介绍串口屏的相关知识。 本次项目使用的串口屏型号是陶晶驰串口屏TJC3224T024_011,这个型号的串口屏的资料在官网可以找到。本文抛转引玉,简单介绍以便在本工程中更好的应用。 下图是该串口屏的实物图: 一般地,使用单片机或者嵌入式MCU控制串口屏,都会用到串口,同样地,在FPGA中实现也是类似,不过稍微复杂,具体代码设计过程中有以下问题需要...

隔壁old余的博客 7495

android 多线程断点续传下载 三

今天跟大家一起分享下android开发中比较难的一个环节,可能很多人看到这个标题就会感觉头很大,的确如果没有良好的编码能力和逻辑思维,这块是很难搞明白的,前面2次总结中已经为大家分享过有关技术的一些基本要领,我们先一起简单回顾下它的基本原理。 http://blog.youkuaiyun.com/shimiso/article/details/6763664  android 多线程断点续传下载 一 ht

观千剑而后识器,操千曲而后晓声。 2万+

作为一名爬虫人如何实现千万级数据爬取?

爬虫门槛不高,高就高在往后余生的每一次实操都随时会让你崩溃!Bro

吴秋霖的博客 1万+

当SLAM遇上FPGA:打造一个会“硬件加速”的激光雷达SLAM系统

点云数据的运动畸变校正特征点提取与匹配、以及ICP迭代最近点配准。在传统方案中,我们通常用ARM核完成所有计算。但点云配准中的最近邻搜索(KD-Tree)需要大量分支判断,CPU处理起来效率很低;而ICP中的矩阵分解(SVD)虽然计算量大,但非常适合并行化。ZYNQ的特殊之处在于它把ARM(PS端)和FPGA(PL端)集成在同一颗芯片上。ARM擅长跑操作系统、处理复杂逻辑和调度,FPGA擅长做高吞吐量的并行计算。两者之间通过AXI总线高速互联,数据交换延迟只有几十个时钟周期。

QQ_778132974的博客 374

Android 实现多线程下载文件+断点续传

                            Android 多线程下载文件+断点续传       在项目快要结束的时候,发现了app没有版本更新的功能,于是找到一些过去的资料,在app上应用完成了版本更新,现在记录一下apk的下载,也就是如何通过多线程将apk下载到本地。多线程文件的下载,不是线程开的越多下载的越快(一般手机软件、迅雷建议3-4个线程),多线程的下载还与服务器给你的带...

tiger_gy的博客 678

Field Characteristics 深入解析,理解 SAP ABAP Analytics 中 Dimension 与 Measure 的真正边界

在SAP ABAP Analytics数据模型中,字段应建模为Dimension还是Measure,关键在于业务语义而非数据类型。Dimension提供分析上下文,构成数据坐标系,支持钻取与切片;Measure则需具备明确聚合语义(如#SUM、#MIN、#MAX),且必须满足Distinct(无重复)与Complete(无缺失)原则,确保聚合结果准确。错误的建模会导致聚合偏差,根源常在事实粒度不清或关联关系不当。因此,正确划分字段角色,本质是让Analytical Engine准确理解业务数据的分析逻辑。

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 56

安卓M3U8多线程下载器:断点续传与批量下载实战

本文详细解析安卓平台M3U8多线程下载器的核心功能与实战技巧,重点介绍其断点续传机制如何确保下载中断后无需重头开始,以及多线程加速和批量下载功能如何显著提升下载效率与文件管理能力,是移动端视频下载的实用指南。

tea88的博客 712

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

ssm框架搭建

这个是一个在idea上成功跑起来的ssm框架项目,保存在这里方便以后使用直接下载

android 断点续传原理,Android多线程断点续传下载原理

步骤:1.在本地创建一个与服务器端一样大小的空白文件;确定服务器端文件的大小:发送一个请求,得到content-length;创建空白文件,设置文件的大小:RandomAccessFile2.在设置线程的个数;创建int类型的变量;3.计算子线程下载数据的范围;blockSize = length / threadCount;4.计算子线程下载数据的开始位置和结束位置;startIndex = t...

weixin_39797393的博客 179

最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版

Part-E1-SDIO-Specification-Ver4.00-Final-120220.pdf:最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版,包括SDIO 接口的外设,SDIO 接口官方协议等。

Android之 多线程下载、断点续传 实现

这里使用的是github上的一个开源项目 DownloadProvider  下载地址:https://github.com/yxl/DownloadProvider,支持多线程下载和断点续传功能,自己在它上面作了一些修改以满足业务需求。

keeven_niu的专栏 1188

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

android开发之多线程下载断点续传

多线程下载断点续传 原理什么的就不讲了一眼就明了主要还是这个代码比较麻烦给大家补充一下代码 这里呢就是封装好了的一个下载类直接调用方法就可以了注释什么的都写全了,代码的搬运工开始行动吧 public class DownLoadFile { private static final String SP_NAME = "download_file"; private stati

南城一点风雨的博客 584

Android多线程代码,Android多线程断点续传下载实现代码

学习了多线程下载,而且可以断点续传的逻辑,线程数量可以自己选择,但是线程数量过多手机就承受不起,导致闪退,好在有断点续传。步骤写在了代码的注释里。大概就是获取服务器文件的大小,在本地新建一个相同大小的文件用来申请空间,然后将服务器的文件读下来写到申请的文件中去。若开多线程,将文件分块,计算每个线程下载的开始位置和结束位置。若断点传输,则保存断开后下载的位置,下次将此位置赋给开始下载的位置即可。细节...

weixin_39589511的博客 169

Android之——多线程断点续传下载示例

Android之——多线程断点续传下载示例 原创 2015年07月15日 21:02:39 标签:android /下载 /多线程 /断点 转载请注明出处:http://blog.youkuaiyun.com/l1028386804/article/details/46897641 一、概述 在上一篇博文《Android之——多线程下载示例》中,我们讲

qingxuan521721的博客 510
上一篇: Linux Shell
下一篇: C++ 字符编码转换工具类
IT回忆录
博客等级 码龄16年 42粉丝 100原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值