思路:
- 服务器处理多线程问题
1.因为服务器是要很多人访问的,因此里面一定要用多线程来处理,不然只能一个人一个人的访问,那还叫Y啥服务
2,拿上面这个文件上传的例子来说,它将每个连接它的用户封装到线程里面去,把用户要执行的操作定义到 run 方法里面一个用户拿一个线程,拿到线程的就自己去执行,如果有其它用户来的时候,再给新来的用户分配一个新的线程 这样就完成了服务器处理多线程的问题3. 在服务器与客户端互传数据时,我们要特别注意的是,防止两个程序造成 死等的状态,
一般原因有以下: 1.客户端向数据端发送数据时,当发送的是字符时,第次以一行来发送,而服务端在读取的时候,也是以 一行来读取,readLine()而发送的时候往往只是发送换行以行的内容,而不能发换行也发送过去, 那么服务端在读取的时候就不读取不到换行 ,那么 readLine() 就不会停止 2. 客户端发送数据时,如果处理的是用 字符流 或是缓冲流的话,一定要记得刷新流,不然的话,数据就会发不出来 3.在用IO 读取文件里面的数据然后发送到服务端时,当家读取文件 while(in.read()) 读取文件结束时,而在 服务端的接收程序while(sin.read())不会接到一个发送完毕的提示,所以会一直等待下去,所以我们在处理这个问题的时候,还要将其发送一个文件读取结束的标志,告诉接收端文件已经读取完结,不要再等待了 而socket 里面给我们封装了shutdownInput,shutdownOutput 两个操作,此可以关闭 流,两样也可以起到告诉 接收方文件传送完毕的效果
什么是线程:
线程是操作系统运行的最小单元;进程里包含了多个线程,他们处理不同的任务,组成了一个应用或者一个系统的整体逻辑。
Thread.yield():让步,当一个线程执行yield()方法,证明该线程执行让步,让其他线程有可能的获取资源运行。
Thead.join(): 加入,当一个线程执行join(),证明该线程执行加入操作,会终止当前正在运行的线程,开始执行join的线程。
Thread 优先级:Thread.currentThread().setPrority(value);value=Max_prority;Norm_priority;Min_priority;三种不同的优先等级;
Thread.setDaemon(boolean);设置当前线程为后台线程,后台线程要在start之前调用才有效。后台线程,是指程序运行的时候在后台提供一种通用服务的线程,且这种线程并不属于程序中不可或缺的部分;只要有任何非后台线程在运行,程序就不会终止。
sleep和wait的区别
最大的不同是在等待时 wait 会释放锁,而 sleep 一直持有锁。wait 通常被用于线程间交互,sleep 通常被用于暂停执行。
实现线程的方式
1.继承Thread
2.实现Runable接口
3.实现Callable,和Runable的区别是有回调方法。
区别 :实现Runable接口扩展性更好,因为继承只能单向继承
(一)上传
1.上传线程池工具类
public class HttpUtils {
static String result="";
private static final String TAG = "HttpUtils";
/**
* HttpUrlConnection 实现文件上传
* @param params 普通参数
* @param fileFormName 文件在表单中的键 key=img file =文件流
* @param uploadFile 上传的文件
* @param newFileName 文件在表单中的值(服务端获取到的文件名)
* @param urlStr 上传服务器地址url
* @throws IOException 10
*/
public static String uploadForm(Map<String, String> params, String fileFormName, File uploadFile,
String newFileName, String urlStr) throws IOException {
if (newFileName == null || newFileName.trim().equals("")) {
newFileName = uploadFile.getName();
}
StringBuilder sb = new StringBuilder();
if (params != null) {
for (String key : params.keySet()) {
sb.append("Content-Disposition: form-data; name=\"" + key + "\"" + "\r\n");
sb.append("\r\n");
sb.append(params.get(key) + "\r\n");
}
}
sb.append("--" + BOUNDARY + "\r\n");
sb.append("Content-Disposition: form-data; name=\"" + fileFormName + "\"; filename=\"" + newFileName + "\""
+ "\r\n");
sb.append("Content-Type: application/octet-stream" + "\r\n");// 如果服务器端有文件类型的校验,必须明确指定ContentType
sb.append("\r\n");
byte[] headerInfo = sb.toString().getBytes("UTF-8");
byte[] endInfo = ("\r\n--" + BOUNDARY + "--\r\n").getBytes("UTF-8");
URL url = new URL(urlStr);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("POST");
// 设置传输内容的格式,以及长度
conn.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + BOUNDARY);
conn.setRequestProperty("Content-Length",
String.valueOf(headerInfo.length + uploadFile.length() + endInfo.length));
conn.setDoOutput(true);
OutputStream out = conn.getOutputStream();
InputStream in = new FileInputStream(uploadFile);
//写入的文件长度
int count = 0;
//文件的总长度
int available = in.available();
// 写入头部 (包含了普通的参数,以及文件的标示等)
out.write(headerInfo);
// 写入文件
byte[] buf = new byte[1024];
int len;
while ((len = in.read(buf)) != -1) {
out.write(buf, 0, len);
count += len;
int progress = count * 100 / available;
Log.d(TAG, "上传进度: " + progress + " %");
// updateProgress(progress);
}
// 写入尾部
out.write(endInfo);
in.close();
out.close();
if (conn.getResponseCode() == 200) {
System.out.println("文件上传成功");
result = stream2String(conn.getInputStream());
Log.d(TAG, "uploadForm: " + result);
}
return result;
}
// 分割符,自己定义即可
private static final String BOUNDARY = "----WebKitFormBoundaryT1HoybnYeFOGFlBR";
public static String stream2String(InputStream is) {
int len;
byte[] bytes = new byte[1024];
StringBuffer sb = new StringBuffer();
try {
while ((len = is.read(bytes)) != -1) {
sb.append(new String(bytes, 0, len));
}
is.close();
return sb.toString();
} catch (IOException e) {
e.printStackTrace();
}
return "";
}
}
2.创建一个新线程调用工具类
public class UploadFile implements Runnable {
String filePath;
String uploadurl;
public UploadFile(String filePath, String uploadurl) {
this.filePath = filePath; //上传路径
this.uploadurl = uploadurl; //上传的url
}
@Override
public void run() {
/**
* HttpUrlConnection 实现文件上传
* @param params 普通参数
* @param fileFormName 文件在表单中的键 key=img file =文件流
* @param uploadFile 上传的文件
* @param newFileName 文件在表单中的值(服务端获取到的文件名)
* @param urlStr 上传服务器地址url
* @throws IOException 10
*/
// do something --上传
HashMap<String,String> map = new HashMap<>();
map.put("key","imgs");
File file = new File(filePath);
try {
String result= HttpUtils.uploadForm(map,"file",file,file.getName(),uploadurl);
if (!TextUtils.isEmpty(result)&&uploadListener!=null){
uploadListener.uploadSuccess(result);
}
} catch (IOException e) {
e.printStackTrace();
}
}
// 接口回调
UploadListener uploadListener;
public void setUploadListener(UploadListener uploadListener){
this.uploadListener = uploadListener;
}
public interface UploadListener{
void uploadSuccess(String result);
}
}
3.在Activity中实现并获取到要上传的文件
private void uploadFile() {
String string = Environment.getExternalStorageDirectory() + File.separator + "aa.jpg";
File file = new File(string); //获取到文件
if(file.exists()){
Log.d("zll", "uploadFile: "+true);
}
//1.创建线程池
//2.把线程给线程池
ExecutorService executorService = Executors.newCachedThreadPool(); //创建性线程
UploadFile uploadFile = new UploadFile(string, url); //获取到新线程的类
uploadFile.setUploadListener(new UploadFile.UploadListener() { //通过接口回调将实现新线程的方法
@Override
public void uploadSuccess(final String result) {
Log.d("zll", "uploadSuccess: "+result);
runOnUiThread(new Runnable() {
@Override
public void run() {
tv.setText(result);
}
});
}
});
executorService.execute(uploadFile); //开始执行
executorService.shutdown(); //结束
}
}
(二)下载安装的配置
权限:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS"/>
在application之间配置(清单文件中)
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="com.example.day11a" //到自己的包
android:grantUriPermissions="true"
android:exported="false">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_path" />
</provider>
生成一个xml
<resources xmlns:android="http://schemas.android.com/apk/res/android">
<paths>
<external-path path="" name="download"/>
</paths>
</resources>
(三).下载
1.创建线程池下载工具类
public class DownLoadUtils {
//默认下载文件保存路径
private final String DEFAULT_TARGET_FOLDER_PATH = Environment.getExternalStorageDirectory() + File.separator;
//下载文件保存的目标路径(包括文件名)
private static String targetFilePathAndName;
//最大可开启的线程数
public static final int MAX_THREAD_NUMBER = 15;
//下载文件的URL地址
public static String sourcePath;
//下载所需开启的线程数,默认为5个
private int threadNumber = 5;
//还在下载的线程数量
public static int restTask;
//下载文件总大小
private int fileSize;
//每个线程负责下载的文件块大小
private int partSize;
static Context context;
/**
* 开始一次下载
* @param sourcePath 目标URL
* @param targetFilePath 目标保存路径
* @param threadNumber 开启的线程数
* @param fileName 保存的文件名
* @return 开启任务成功否
*/
public boolean start( Context context, String sourcePath, String targetFilePath, int threadNumber, String fileName) throws IOException {
this.sourcePath = sourcePath;
this.context = context;
this.targetFilePathAndName = targetFilePath == null ? DEFAULT_TARGET_FOLDER_PATH
+ (fileName == null ? System.currentTimeMillis() : fileName) :
targetFilePath + (fileName == null ? System.currentTimeMillis() : fileName);
this.threadNumber = threadNumber < 0 || threadNumber > MAX_THREAD_NUMBER ? this.threadNumber : threadNumber;
this.restTask = this.threadNumber;
HttpURLConnection conn = getConnection();
fileSize = conn.getContentLength();
conn.disconnect();
RandomAccessFile file = new RandomAccessFile(targetFilePathAndName, "rw");
file.setLength(fileSize);
file.close();
partSize = fileSize / threadNumber + 1;
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < threadNumber; i++) {
int startPos = i * partSize;
executorService.execute(new ThreadTask(startPos,partSize,targetFilePathAndName));
}
executorService.shutdown();
return true;
}
public static HttpURLConnection getConnection() throws IOException {
URL url = new URL(DownLoadUtils.sourcePath);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setConnectTimeout(10*1000);
conn.setRequestMethod("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", DownLoadUtils.sourcePath);
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");
conn.setRequestProperty("Accept-Encoding", "identity");
// conn.connect();
return conn;
}
//普通安装
public static void installAPK( ) {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
//版本在7.0以上是不能直接通过uri访问的
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N) {
File file = new File(targetFilePathAndName);
// 由于没有在Activity环境下启动Activity,设置下面的标签
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
//参数1 上下文, 参数2 Provider主机地址 和配置文件中保持一致 参数3 共享的文件
Uri apkUri = FileProvider.getUriForFile(context, "com.example.day11a", file);
//添加这一句表示对目标应用临时授权该Uri所代表的文件
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.setDataAndType(apkUri, "application/vnd.android.package-archive"); //改包
} else {
intent.setDataAndType(Uri.fromFile(new File(targetFilePathAndName)),
"application/vnd.android.package-archive");
}
context.startActivity(intent);
}
}
2.创建线程并完成对文件的下载
import static com.example.day11a.DownLoadUtils.restTask;//这个resttask是文件的总大小调用的是工具类里的
public class ThreadTask implements Runnable{
private int partSize;
//当前线程的下载位置
private int startPos;
//当前下载保存到的目标文件(块)
private RandomAccessFile currentPart;
//当前已下载数据大小
private int currentDownLoaded;
public ThreadTask(int startPos, int partSize,String targetFilePathAndName) throws IOException {
this.startPos = startPos;
this.partSize = partSize;
currentPart = new RandomAccessFile(targetFilePathAndName, "rw");
currentPart.seek(startPos);
}
@Override
public void run() {
doHttpTask();
}
private static final String TAG = "ThreadTask";
private void doHttpTask() {
try {
HttpURLConnection connection = DownLoadUtils.getConnection();
InputStream in = connection.getInputStream();
skipFully(in, startPos);
byte[] bytes = new byte[8*1024];
int hasRead;
while ((currentDownLoaded < partSize) && (hasRead = in.read(bytes)) > 0) {
currentPart.write(bytes, 0, hasRead);
currentDownLoaded += hasRead;
Log.d(TAG, "doHttpTask: thread="+Thread.currentThread().getName()+"--"+currentDownLoaded);
}
currentPart.close();
in.close();
connection.disconnect();
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
restTask--;
if (restTask == 0){
new DownLoadUtils().installAPK();
Log.d(TAG, "doHttpTask: 下载完成");
}
}
}
/**
* 从输入流中从起点开始跳过指定长度
*
* @param in 输入流
* @param bytes 要跳过的字节数
* @throws IOException
*/
public final void skipFully(InputStream in, long bytes) throws IOException {
long len;
while (bytes > 0) {
len = in.skip(bytes);
bytes -= len;
}
}
}
3.在Activiry中使用
public void onClick(View v) {
switch (v.getId()) {
default:
break;
case R.id.click:
loadFile();
break;
case R.id.click2:
installApk();
break;
}
}
private void installApk() { //安装的方法
String targetFilePathAndName = Environment.getExternalStorageDirectory() + File.separator+"UnknowApp-1.0.apk";
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
//版本在7.0以上是不能直接通过uri访问的
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
File file = new File(targetFilePathAndName);
// 由于没有在Activity环境下启动Activity,设置下面的标签
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
//参数1 上下文, 参数2 Provider主机地址 和配置文件中保持一致 参数3 共享的文件
Uri apkUri = FileProvider.getUriForFile(MainActivity.this, "com.example.morethreadloadfile", file); //改包
//添加这一句表示对目标应用临时授权该Uri所代表的文件
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.setDataAndType(apkUri, "application/vnd.android.package-archive");
} else {
intent.setDataAndType(Uri.fromFile(new File(targetFilePathAndName)),
"application/vnd.android.package-archive");
}
startActivity(intent);
}
//UnknowApp-1.0.apk
private String apk_url = "http://yun918.cn/study/public/res/UnknowApp-1.0.apk";
private void loadFile() { //下载的方法
new Thread() {
@Override
public void run() {
super.run();
try {
DownLoadUtils downLoadUtils = new DownLoadUtils();
downLoadUtils.start(MainActivity.this, apk_url, null, 2, "UnknowApp-1.0.apk");
} catch (IOException e) {
e.printStackTrace();
}
}
}.start();
}
网友评论