Java 的屏幕广播(基于UDP)

Java的屏幕广播,是基于UDP协议的,user datagram protocal 用户数据报协议,无连接,无顺序,不安全,但是作为发送实时数据还是十分常用的。

整个难点在于要字节制定协议,由于UDP的一个包最大不能超过64K,而一帧屏幕截图(1366*768)是肯定超过64K的,所以我们需要对所截出来的image进行分割发送。
假设我们将一张屏幕截图分割成若干个包发送出去,要构成屏幕广播,就需要不断的截图发包,接收端就肯定需要知道哪几个包是同一张图片,而在这几个包中哪个包是图片的哪一部分,便于恢复成一张完整的图片。

如此就需要一个协议来规定各个包之间的关系。

在下面的程序中,每张图片之间我用时间戳来区别,图片中分割的各个部分用编号来确定,还要加上每张图片所分割的数量,就构造了一个数据报包。

首先的代码是一个工具类,将byte转换成long、int,还有反转

package Boardcast;

 * 用于存放byte转换成long,int的工具代码
public class Utils {

	public static byte[] long2Byte(long number) {
		byte[] b = new byte[8];
		for(int i=0; i<8; i++) {
			b[i] = (byte) (number>>((8-i-1)*8));
		}
		return b;
	}
	
	 * 从offset开始往后的8个字节转换为long数据
	public static long byte2Long(byte[] b, int offset) {
		long end = 0;
		for(int i=0; i<8; i++) {
			end = end | ((long)(b[i+offset] & 0xff)<<((8-i-1)*8));
			 * 其中的long类型转换一定要加上,如果没加上结果就成了int类型
		}
		return end;
	}

    public static byte[] int2Byte(int number) {
        byte[] b = new byte[4];
        b[0] = (byte) (number >> 24);
        b[1] = (byte) (number >> 16);
        b[2] = (byte) (number >> 8);
        b[3] = (byte)number;
        return b;
    }

    public static int byte2Int(byte[] b, int offset) {
        int i3 = (b[0+offset] & 0xFF)<< 24;
        int i2 = (b[1+offset] & 0xFF)<< 16;
        int i1 = (b[2+offset] & 0xFF)<< 8;
        int i0 =  b[3+offset] & 0xFF;
        return i3 | i2 | i1 | i0;
    }
}

下面是广播者,发送截屏后过压缩,将压缩后的数据分割后进行发送

package Boardcast;

import java.awt.Rectangle;
import java.awt.Robot;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

import javax.imageio.ImageIO;

 * 广播者,屏幕截图后转换成byte[]格式,过压缩,分割成一个个60K左右的小包发送出去
 * 对于同一帧的图片分割出来的小包,有着相同的时间戳,同样的包数量,不同的编号,用于区分

public class Boardcaster {
	private DatagramSocket socket;
	private Robot robot;
	private Rectangle rect;
	
	public static void main(String[] args) {
		Boardcaster bc = new Boardcaster();
		System.out.println("开始发送数据。。。。");
		bc.start();
	}
	
	public void start() {
		try {
			socket = new DatagramSocket(8888);
			 * 实例化一个Robot,用于抓图
			robot = new Robot();
			 * 设置所抓图片的位置,长宽
			rect = new Rectangle(0, 0, 1366, 768);
			 
			while(true) {
				popDatagramPacket(socket);
				System.out.println("发送一帧图片");
			}
		} catch(Exception e) {
			e.printStackTrace();
		}
	}
	
	 * 抓图
	public BufferedImage getPrintScreen() {
		BufferedImage image = robot.createScreenCapture(rect);
		return image;
	}
	
	 * 抓图,压缩,分割发送
	private void popDatagramPacket(DatagramSocket socket) {
		try {
			 * 抓图
			BufferedImage image = getPrintScreen();
			 * 将图写入baos
			ByteArrayOutputStream baos = new ByteArrayOutputStream();
			ImageIO.write(image, "jpg", baos);
			
			System.out.println("未压缩的byte[]大小 = " + baos.toByteArray().length);
			 * 过压缩
			byte[] buffer = compressImage(baos.toByteArray());
			
			System.out.println("过压缩的byte[]大小 = " + buffer.length);
			
			 * 分割图片并发送出去
			cutImageByteAndPost(buffer, socket);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	 * 将image的byte[]过压缩,返回压缩后的byte[]
	private byte[] compressImage(byte[] buffer) throws Exception {
		 * 新建一个baos,用于存放转压缩后的byte[]数据
		ByteArrayOutputStream baos = new ByteArrayOutputStream();
		ZipOutputStream zos = new ZipOutputStream(baos);
		
		ZipEntry entry = new ZipEntry("image.jpg");
		 * 放入条目
		zos.putNextEntry(entry);
		 * 写入数据
		zos.write(buffer);
		
		zos.close();
		baos.close();
		
		return baos.toByteArray();
	}

	 * 将数据的byte[]分割发送出去,同一帧的图片分割成的小包,有着相同的时间戳,同样的包数量,不同的编号
	 * 每一个小包,开始8个字节存放时间戳,接下来四个字节存放本帧图片所分割出来的包数量,再放入四个字节的编号
	public void cutImageByteAndPost(byte[] src, DatagramSocket socket) {
		try {
			int len = src.length;
			 * 分割的包数量
			int count = len/60/1024;
			if(len > count*60*1024) {
				count++;
			}
			System.out.println("len = " + len + ", count = " + count);
			
			byte[] buffer = new byte[60*1024+8+4+4];
			long time = System.nanoTime();
			
			 * 发count数量的包
			for(int i=0; i<count; i++) {
				 * 如果是最后一个包,就将剩下的所有数据都发送出去
				if(i == count-1) {
					buffer = new byte[len%(60*1024)+16];
				}
				 * 写入时间戳
				System.arraycopy(Utils.long2Byte(time), 0,  buffer, 0, 8);
				 * 写入分割的数量
				System.arraycopy(Utils.int2Byte(count), 0, buffer, 8, 4);
				 * 写入当前的编号,从0开始
				System.arraycopy(Utils.int2Byte(i), 0, buffer, 12, 4);
				 * 写入image内容
				System.arraycopy(src, i*60*1024, buffer, 16, buffer.length-16);
				
				System.out.println("i = " + (i) + ", buffer.length = " + buffer.length);
				
				 * 发送数据,组装成数据报包
				DatagramPacket pack = new DatagramPacket(buffer, buffer.length);
				 * 设置发送地址、端口
				pack.setSocketAddress(new InetSocketAddress("localhost", 9999));
				
				socket.send(pack);
				System.out.println("发送一个数据报包");
			}
		} catch(Exception e) {
			e.printStackTrace();
		}
	}
}

接收者,较广播方复杂一些

是用JFrame建立的界面,用于展示收到的屏幕截图
对上面的数据进行反解,取出各个小包中的内容数据,合成一个大的数据byte[],再解压缩成图片数据,最后转换成图片显示再界面中

在对数据报包合成时,使用Map来存储,时间戳作为key,value是TreeMap<Integer, PackInfo>,每个小包解析为一个PackInfo对象,Integer是存放包的编号,TreeMap本身是有序存放的,所以合成数据时直接取出来合成即可

package Boardcast;

import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.zip.ZipInputStream;

import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;

 * 接收屏幕广播界面
public class BoradcastScreenClientUI extends JFrame{
	private static final long serialVersionUID = -580341982722536152L;
	 * label用于放置image
	private JLabel label;

	public static void main(String[] args) {
		BoradcastScreenClientUI bsClient = new BoradcastScreenClientUI();
	}
	
	public BoradcastScreenClientUI() {
		init();
		getData();
	}
	
	 * 初始化面板设置,设置一个label,放置image
	public void init() {
		this.setTitle("屏幕广播");
		this.setBounds(270, 80, 1366, 768);
		
		try {
			BufferedImage image = ImageIO.read(new File("E://TestCase//day20//demo.jpg"));
			ImageIcon icon = new ImageIcon(image);
			label = new JLabel(icon);
			label.setBounds(25, 25, 1366, 768);
		} catch(Exception e) {
			e.printStackTrace();
		}
		this.add(label);
		
		 * 监听窗口关闭事件,关闭窗口的同时停止程序
		this.addWindowListener(new WindowAdapter(){
			@Override
			public void windowClosing(WindowEvent e) {
				System.out.println("关闭窗口");
				System.exit(-1);
			}	
		});
		
		 * 设置面板可见
		this.setVisible(true);
	}
	
	 * 启动一个线程监听端口,接收数据,解析成image并放入面板中显示
	public void getData() {
		Thread t = new Thread() {
			private DatagramSocket receiver;
			@Override
			public void run() {
				try {
					receiver = new DatagramSocket(9999);
					byte[] buffer = new byte[64*1024];
					DatagramPacket pack = new DatagramPacket(buffer, buffer.length);
					
					System.out.println("等待接收数据");
					 * 用Map来存放image的各个包数据,Long是接收到数据包的时间戳,Integer是包的编号,
					 * PackInfo中存放了解析出来的包数据、包数量、包编号、时间戳
					Map<Long, HashMap<Integer, PackInfo>> map = null;
					 * key用于存放接收的上一个包数据的时间戳
					long key = 0;
					while(true) {
						receiver.receive(pack);
						System.out.println("收到一个数据报包 " + pack.getLength());
						 * 将收到的数据包解析成数据对象
						PackInfo packinfo = parsePackInfo(pack);
						
						 * 如果接收的的数据时间戳大于之前的包的时间戳,则丢弃之前的map,新建一个map,放入新的数据
						if(packinfo.getTime() > key) {
							map = new HashMap<Long, HashMap<Integer,PackInfo>>();
							HashMap<Integer,PackInfo> value = new HashMap<Integer,PackInfo>();
							value.put(packinfo.getNum(), packinfo);
							map.put(packinfo.getTime(), value);
							 * 更新key的值
							key = packinfo.getTime();
						}else if(packinfo.getTime() == key) {  * 如果时间戳相等则将包数据放入map中
							map.get(key).put(packinfo.getNum(), packinfo);
							 * 检测是否够合成一张图片了,条件是map.value.size等于包的数量
							checkMap(map);
						}
					}
				} catch(Exception e) {
					e.printStackTrace();
				}
			}
		};
		 * 作为守护线程,并启动
		t.setDaemon(true);
		t.start();
	}
	
	 * 解析一个包的数据,并放入对象中返回
	private PackInfo parsePackInfo(DatagramPacket pack) {
		 * 新建一个包数据对象存放数据
		PackInfo packinfo = new PackInfo();
		
		byte[] buffer = pack.getData();
		
		 * 取出时间戳,0-7的字节
		long time = Utils.byte2Long(buffer, 0);
		packinfo.setTime(time);
		
		 * 取出分割数量,8-11的字节
		int count = Utils.byte2Int(buffer, 8);
		packinfo.setCount(count);
		
		 * 取出当前编号,12-15的字节
		int num = Utils.byte2Int(buffer, 12);
		packinfo.setNum(num);
		
		 * 当前被压缩后的数据
		byte[] data = new byte[pack.getLength()-16];
		System.arraycopy(buffer, 16, data, 0, data.length);
		packinfo.setData(data);
		
		System.out.println(time + ", " + count + ", " + num + ", " + data.length);
		
		return packinfo;
	}
	
	 * 检测放入的小包是否等于包的总数,等于则合成一张图片
	public void checkMap(Map<Long, HashMap<Integer, PackInfo>> map) throws Exception {
		Iterator<Entry<Long, HashMap<Integer,PackInfo>>> it = map.entrySet().iterator();
		while(it.hasNext()) {
			Entry<Long, HashMap<Integer,PackInfo>> entry = it.next();
			PackInfo packinfo = entry.getValue().entrySet().iterator().next().getValue();
			
			 * 检测是否集齐了所有小包,集齐了就合成一帧图片
			if(packinfo.getCount() == entry.getValue().size()) {
				System.out.println("满了,合成图片");
				
				 * 将各个小包合成图片
				ByteArrayOutputStream baos = new ByteArrayOutputStream();
				
				for(int i=0; i<packinfo.getCount(); i++) {
					PackInfo p = entry.getValue().get(i);
					baos.write(p.getData());
				}
				baos.close();
				
				 * 解压缩数据
				byte[] b = unCompressImage(baos.toByteArray());
				
				System.out.println("未解压缩的数据长度 = " + baos.toByteArray().length);
				System.out.println("解压缩后的图片长度 = " + b.length);
				
				 * 设置图片到面板中
				BufferedImage image = ImageIO.read(new ByteArrayInputStream(b));
				ImageIcon i = new ImageIcon(image);
				label.setIcon(i);
				
				repaint();
				System.out.println("设置一张图片");
				 * 将设置过后的image数据从map中移除
				it.remove();
			}
		}
		System.out.println("字典长度 = " + map.size());
	}

	 * 对合成的数据进行解压缩,返回解压的byte[]
	private byte[] unCompressImage(byte[] b) throws IOException {
		ByteArrayInputStream bais = new ByteArrayInputStream(b);
		ZipInputStream zis = new ZipInputStream(bais);
		zis.getNextEntry();
		byte[] data = zis.readAllBytes();
		zis.close();
		bais.close();
		return data;
	}
}

 * 存放包的数据的实例类
class PackInfo {
	private long time;  *  包的时间戳
	private int count;  * 分割的包数量
	private int num;    *  当前的包编号
	private byte[] data;  * 包中存放的数据
	
	public long getTime() {
		return time;
	}
	public void setTime(long time) {
		this.time = time;
	}
	public int getCount() {
		return count;
	}
	public void setCount(int count) {
		this.count = count;
	}
	public int getNum() {
		return num;
	}
	public void setNum(int num) {
		this.num = num;
	}
	public byte[] getData() {
		return data;
	}
	public void setData(byte[] data) {
		this.data = data;
	}
}

程序的效果如下图

接受者启动的初始画面,后台线程正在等待接收数据

android 屏保广播 屏幕广播怎么用_System

启动广播者,不断的在发送数据包

android 屏保广播 屏幕广播怎么用_java_02

可以看到面板中的实时屏幕广播了

android 屏保广播 屏幕广播怎么用_android 屏保广播_03



多线程下载器

使用java的多线程从服务器下载文件,我的服务器是在本地用tomcat搭建的

可以实现暂停下载和断点续传,断点续传的数据文件是用Properties来处理的。

每个线程下载后都会更新下载的数据文件来保存下载信息,使得在停止下载后可以续传。

package multidownload;

import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JProgressBar;
import javax.swing.JTextField;

 * 使用多线程从服务器下载文件
public class DownloadUI extends JFrame implements ActionListener {
	private static final long serialVersionUID = 5521199214537722822L;
	public JTextField textUrl;   * 下载的URL输入框
	public JTextField textPath;   * 保存路径输入框
	public JTextField textThreadCount;  * 线程数量
	private JButton buttonStart;
	private JButton buttonStop;
	public JProgressBar bar;     * 总进度条
	public boolean flag = false;  * 是否暂停下载标志位
	public String ProfilePath;    * 下载线程保存的数据文件路径
	public Properties p;    * 下载线程保存的数据文件

	public static void main(String[] args) {
		DownloadUI multiDown = new DownloadUI();
	}

	public DownloadUI() {
		init();
	}

	private void init() {
		this.setTitle("多线程下载");
		this.setBounds(270, 80, 800, 600);
		this.setLayout(null);

		Font fontText = new Font("宋体", Font.ITALIC, 20);
		Font fontLab = new Font("宋体", Font.BOLD, 27);

		JLabel labSrcPath = new JLabel("URL地址");
		labSrcPath.setFont(fontLab);
		labSrcPath.setBounds(30, 30, 120, 60);

		textUrl = new JTextField();
		textUrl.setFont(fontText);
		textUrl.setBounds(150, 30, 400, 60);
		textUrl.setText("http://localhost:8000//demo.avi");

		this.add(labSrcPath);
		this.add(textUrl);

		JLabel labAimPath = new JLabel("保存路径");
		labAimPath.setFont(fontLab);
		labAimPath.setBounds(20, 130, 150, 60);

		textPath = new JTextField();
		textPath.setFont(fontText);
		textPath.setBounds(150, 130, 400, 60);
		textPath.setText("E:\\TestCase\\day21\\demo.avi");

		this.add(labAimPath);
		this.add(textPath);

		JLabel labThreadCount = new JLabel("线程数量");
		labThreadCount.setFont(fontLab);
		labThreadCount.setBounds(20, 230, 150, 60);

		textThreadCount = new JTextField();
		textThreadCount.setFont(fontLab);
		textThreadCount.setBounds(150, 230, 400, 60);
		textThreadCount.setText("3");

		 * 开始下载按钮
		buttonStart = new JButton("开始下载");
		buttonStart.setBounds(100, 320, 100, 40);
		buttonStart.addActionListener(this);

		 * 停止下载按钮
		buttonStop = new JButton("停止下载");
		buttonStop.setBounds(250, 320, 100, 40);
		buttonStop.addActionListener(this);

		this.add(labThreadCount);
		this.add(textThreadCount);
		this.add(buttonStart);
		this.add(buttonStop);

		 * 添加窗口事件处理程序,使用适配器
		this.addWindowListener(new WindowAdapter() {
			@Override
			public void windowClosing(WindowEvent e) {
				System.out.println("关闭窗口");
				System.exit(-1);
			}
		});

		 * 进度条,作为总量进度条
		bar = new JProgressBar();
		bar.setBounds(50, 380, 700, 50);
		this.add(bar);

		this.setVisible(true);
	}

	 * 对按钮的事件监听和处理
	@Override
	public void actionPerformed(ActionEvent e) {
		try {
			Downloader downloader;
			if (e.getSource() == buttonStart) {  * 要判断是新的下载还是断点续传
				System.out.println("点击开始按钮");

				 * 获取面板上的数据
				String url = textUrl.getText();
				String savePath = textPath.getText();
				int count = Integer.parseInt(textThreadCount.getText());
				System.out.println("URL地址:" + url + ", 保存路径:" + savePath + ", 线程数:" + count);
				
				 * 为了可以断点续传,需要存储传输的各个线程节点的信息,线程的开始位置,传输数量等
				 * 构造数据文件的路径,和保存的文件路径在同一个位置,命名的也相同,后缀名为.properties
				ProfilePath = savePath.substring(0, savePath.lastIndexOf(".")) + ".properties";
				
				 * flag是暂停的标志,检测是否是已经开启过线程下载了
				if(flag) {
					System.out.println("下载已暂停,请点击继续下载");
				}else {
					File file = new File(ProfilePath);
					
					 *  1.初始化下载器对象
					downloader = new Downloader(url, savePath, count, this);
					
					if (file.exists()) {  * 断点续传
						System.out.println("断点续传");
						
						 * 是断点续传的话就肯定会存在同名的数据文件,读取传输的量并继续下载
						p = new Properties();
						p.load(new FileInputStream(ProfilePath));

						 *  2.取得初始化列表对象
						List<Download> lists = downloader.keepDownload();

						System.out.println("文件总大小:" + downloader.fileSize);
						
						 *  3.给总进度条设置最大值
						this.bar.setMaximum(downloader.fileSize);
						
						 *  4.按照线程数量动态添加进度条
						List<JProgressBar> bars = addBar(lists);

						 *  5.开始下载
						downloader.startDownload(bars);

					}else {  * 新的下载
						System.out.println("开始新的下载");
						
						 *  2.取得初始化列表对象
						List<Download> lists = downloader.prepareDownload();

						 *  3.将基本数据存入一个properties文件中
						saveDownloadInfo(lists, downloader.fileSize);

						System.out.println("文件总大小:" + downloader.fileSize);

						 *  4.给进度条设置最大值
						this.bar.setMaximum(downloader.fileSize);

						 *  5.按照线程数量动态添加进度条
						List<JProgressBar> bars = addBar(lists);

						 *  6.开始下载
						downloader.startDownload(bars);
					}
				}
			} else if (e.getSource() == buttonStop) {  * 检测是否停止
				System.out.println("点击停止按钮");
				this.flag = !this.flag;
			}
		} catch (Exception e2) {
			e2.printStackTrace();
		}
	}

	 * 保存下载文件的下载数据信息
	private void saveDownloadInfo(List<Download> lists, int fileSize) {
		try {
			p = new Properties();
			 * 设置公共的url,savePath,线程数量,文件总大小
			p.setProperty("Thread.url", lists.get(0).getUrl());
			p.setProperty("Thread.savePath", lists.get(0).getSavePath());
			p.setProperty("Thread.count", lists.size() + "");
			p.setProperty("Thread.fileSize", fileSize + "");
			 * 为每一个线程设置基本数据,下载的位置,还需要下载的size大小,下载的总数amount,线程编号index
			for (Download d : lists) {
				p.setProperty("Thread." + d.getIndex() + ".start", d.getStart() + "");
				p.setProperty("Thread." + d.getIndex() + ".size", d.getSize() + "");
				p.setProperty("Thread." + d.getIndex() + ".amount", d.getSize() + "");
				p.setProperty("Thread." + d.getIndex() + ".index", d.getIndex() + "");
			}
			p.store(new FileOutputStream(ProfilePath), "Thread basic information for remember");

			System.out.println("保存基本数据");
		} catch (Exception e) {
			e.printStackTrace();
			System.out.println("保存初始化数据出错");
		}
	}
	
	 * 按照线程个数动态添加进度条
	private List<JProgressBar> addBar(List<Download> lists) {
		List<JProgressBar> bars = new ArrayList<JProgressBar>();
		bar.setValue(0);
		 * allAmount是为总进度条设置的
		int allAmount = 0;
		for (Download download : lists) {
			JProgressBar b = new JProgressBar();
			b.setBounds(50, 420 + (download.getIndex() + 1) * 30, 700, 25);

			int amount = Integer.parseInt(p.getProperty("Thread." + download.getIndex() + ".amount"));
			b.setMaximum(amount);

			b.setValue(amount - download.getSize());
			allAmount = allAmount + amount - download.getSize();
			this.add(b);
			bars.add(b);
		}
		this.bar.setValue(allAmount);
		this.repaint();
		return bars;
	}
}
package multidownload;

import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JProgressBar;

 * 这是一个下载器,启动下载线程
public class Downloader {
	private String url;       * 下载的url
	private String savePath;  * 本地保存路径
	private int count;        * 线程数量
	private List<Download> lists;  * 下载线程数的info列表
	public int fileSize;      * 要下载的文件大小
	private DownloadUI downui;  * 对应的就是DownloadUI传过来的对象
	
	 * 初始化
	public Downloader(String url, String savePath, int count, DownloadUI downui) throws Exception {
		this.url = url;
		this.savePath = savePath;
		this.count = count;
		this.downui = downui;
	
		URL u = new URL(url);
		URLConnection conn = u.openConnection();
		fileSize = conn.getContentLength();
	}
	
	 * 下载前的初始化工作
	public List<Download> prepareDownload() {
		try {
			 * 每个线程下载的基本数量,最后一个线程下载的量要另外计算
			int block = fileSize/count;
			
			lists = new ArrayList<Download>();
			for(int i=0; i<count; i++) {
				Download download = new Download();
				if(i != count-1) {  * 不是最后一个线程,下载量就是block
					download.setSize(block);
				}else {  * 最后一个线程,下载量是总量减去之前线程的下载量
					download.setSize(fileSize-(count-1)*block);
				}
				download.setUrl(url);
				download.setSavePath(savePath);
				download.setIndex(i);        * 设置线程编号
				download.setStart(i*block);  * 设置线程的开始下载位置
				
				lists.add(download);
			}
			return lists;
		} catch(Exception e) {
			e.printStackTrace();
		}
		return null;
	}
	
	 * 断点续传的,取出上次下载的线程储存的数据文件,读取到Download中,构成list返回
	public List<Download> keepDownload(){
		lists = new ArrayList<Download>();
		
		int count = Integer.parseInt(downui.p.getProperty("Thread.count"));
		String url = downui.p.getProperty("Thread.url");
		String savePath = downui.p.getProperty("Thread.savePath");
		
		downui.textThreadCount.setText(count+"");
		downui.textUrl.setText(url);
		downui.textPath.setText(savePath);
		
		for(int i=0; i<count; i++) {
			Download d = new Download();
			d.setUrl(url);
			d.setSavePath(savePath);
			d.setStart(Integer.parseInt(downui.p.getProperty("Thread." + i + ".start")));
			d.setIndex(i);
			d.setSize(Integer.parseInt(downui.p.getProperty("Thread." + i + ".size")));
			lists.add(d);
		}
		return lists;
	}

	 * 启动所有线程开始下载
	public void startDownload(List<JProgressBar> bars) {
		for(Download download : lists) {
			System.out.println(download);
			JProgressBar b = bars.get(download.getIndex());
			new DownloadThread(download, b, downui).start();
		}
	}
}

 * 下载线程,每个线程下载的位置信息从download对象中取得
 * 每个线程操作的Properties对象都是同一个,如果不是同一个就会有数据丢失的风险
class DownloadThread extends Thread{
	private Download download;
	private JProgressBar b;
	private DownloadUI downui;
	public DownloadThread(Download download, JProgressBar b, DownloadUI downui){
		this.download = download;
		this.b = b;
		this.downui = downui;
	}
	
	@Override
	public void run() {
		System.out.println(Thread.currentThread().getName() + " download = " + download);
		try {
			 * 连接
			URL url = new URL(download.getUrl());
			URLConnection conn = url.openConnection();
			 * 设置请求头信息
			conn.setRequestProperty("Range", "bytes=" + download.getStart() + "-" + (download.getStart()+download.getSize()));
			
			System.out.println(Thread.currentThread().getName() + "bytes=" + download.getStart() + "-" + (download.getStart()+download.getSize()));
			
			InputStream in = conn.getInputStream();
			RandomAccessFile raf = new RandomAccessFile(download.getSavePath(), "rw");
			raf.seek(download.getStart());
			
			int len = -1;
			byte[] buffer = new byte[1024];
			
			while((len = in.read(buffer)) != -1) {
				 * 检测是否停止下载,不要使用while(downui.flag);这句来堵塞进程,效果不好
				while(downui.flag) {
					Thread.sleep(200);
				};
				
				raf.write(buffer, 0, len);
				b.setValue(b.getValue()+len);
				synchronized (downui) {
					downui.bar.setValue(downui.bar.getValue()+len);
					downui.p.setProperty("Thread."+download.getIndex()+".start", Integer.parseInt(downui.p.getProperty("Thread."+download.getIndex()+".start"))+len + "");
					downui.p.setProperty("Thread."+download.getIndex()+".size", Integer.parseInt(downui.p.getProperty("Thread."+download.getIndex()+".size"))-len + "");
					downui.p.store(new FileOutputStream(downui.ProfilePath), "");
				}
			}
			raf.close();
			in.close();
			 * 检测文件是否下载结束了,结束后删除线程数据文件(不过是删除失败的)
			if(isEnd()) {
				System.out.println(Thread.currentThread().getName() + " 文件下载结束了");
				System.out.println("删除数据文件");
				downui.p = null;
				File f = new File(downui.ProfilePath);
				if(f.delete()) {
					System.out.println("删除成功");
				}else {
					System.out.println("删除失败");
				}
			}
		} catch(Exception e) {
			e.printStackTrace();
			System.out.println(Thread.currentThread().getName() + "线程下载出错");
		}
	}

	 * 检测所有线程是否下载结束了,注意:Properties是线程安全的,其内部使用了synchronized
	private boolean isEnd() {
		int count = Integer.parseInt(downui.p.getProperty("Thread.count"));
		for(int i=0; i<count; i++) {
			int size = Integer.parseInt(downui.p.getProperty("Thread." + i + ".size"));
			if(size > 0) {
				return false;
			}
		}
		return true;
	}
}

 * 储存单个线程的下载信息
class Download{
	private String url;
	private String savePath;
	private int start;
	private int size;
	private int index;
	
	public String getUrl() {
		return url;
	}
	public void setUrl(String url) {
		this.url = url;
	}
	public String getSavePath() {
		return savePath;
	}
	public void setSavePath(String savePath) {
		this.savePath = savePath;
	}
	public int getStart() {
		return start;
	}
	public void setStart(int start) {
		this.start = start;
	}
	public int getSize() {
		return size;
	}
	public void setSize(int size) {
		this.size = size;
	}
	public int getIndex() {
		return index;
	}
	public void setIndex(int index) {
		this.index = index;
	}
	@Override
	public String toString() {
		return "Download [url=" + url + ", savePath=" + savePath + ", start=" + start + ", size=" + size + ", index="
				+ index + "]";
	}
}

暂停下载

android 屏保广播 屏幕广播怎么用_System_04

关闭程序之后再店家开始下载,可以实现断点续传

android 屏保广播 屏幕广播怎么用_android 屏保广播_05

下载完成

android 屏保广播 屏幕广播怎么用_System_06