下载的apk是一个真实的项目,是一个双开+系统通讯录拨号 跟系统的拦截(运用了Hook、DroidPlugin技术)
先说下这个小例子的一个总体代码思路:
下面的界面是主界面,这个界面就是判断当前的app版本,跟服务器的版本是否一致,更服务器交互,获取app的更新内容、版本信息、下载地址,如果一致就没有更新的标志,如果不一致,就显示更新的标志,
然后带着服务器返回的新版本号、更新内容、下载地址更新到下面这个页面,然后就是更新下载操作
我们先来看第一个页面吧:
private void requestVersion() { task = new GetVersionTask(); // runnable接口 new Thread(task).start(); }
上面是开启后台线程进行服务器的请求
@Override public void run() { /** * 去请求服务器 */ String os_type = "android"; String app_type = "3"; String version_code = versionCode + ""; DownLoad downLoad = new DownLoad(); downLoad.setOs_type(os_type); downLoad.setApp_type(app_type); downLoad.setVersion_code(version_code); // 定义一个接口,为了请求完毕后,去外面实现接口方法 RequescurVersion rquest = new RequescurVersion(MainActivity.this, downLoad, downloadProgressListener); rquest.start(); }
上面是线程中做的操作,就是封装json数据,然后定义个回调接口,待请求完毕,获取接口数据回调
requestJson = ProtocolUtil.buildJSONPacketBody(new String[] { "os_type", "app_type", "version_code" }, new Object[] { downLoad.getOs_type(), downLoad.getApp_type(), downLoad.getVersion_code() });
上面就是封装为公司需要的json格式,之前的几篇对这个工具类有介绍说明,这里就不再次说明了
获取数据完毕,利用JsonReader进行封装对象,并且接口回调
jsonParse(result); // jsonParse(respondReader); if (downLoad != null) { Log.e("Safly", "RequescurVersion getStatuscode:" + downLoad.getStatuscode() + ",getStatusmsg" + downLoad.getStatusmsg() + ",getVersion:" + downLoad.getVersion() + ",getUpdateMsg:" + downLoad.getUpdateMsg()); if (listener != null) { listener.getVersion(downLoad); } }
以上就基本上是第一个界面的流程
我们继续看第二个下载的界面:
点击立即更新将服务器获取的数据带过去--下载的路线,版本号,更新的信息
if (downLoadPath != null && newVersion != null) { intent.putExtra("downLoadPath", downLoadPath); intent.putExtra("version", newVersion); if (updateMsg != null) { intent.putExtra("update_msg", updateMsg); } startActivity(intent); }
进来第二个页面后将传过来的服务器的最新版本,更新的内容填充界面
updateMessage.setText(update_msg);
tv_version.setText(version);
构造url,以及下载存储的路径
// path:http://www.imlianxi.com/Apk/Android/Vph/VPH1.1.1.20160714_Alpha.apk path = path.substring(0, path.lastIndexOf("/") + 1) + filename;
savDir = Environment.getExternalStorageDirectory(); // savDir:/storage/emulated/0
然后就开启后台线程进行下载
private void download(String path, File savDir) { task = new DownloadTask(path, savDir); new Thread(task).start(); }
我们看看Thread里面的run方法,构建一个文件下载类,进行下载
loader = new FileDownloader(getApplicationContext(), path, saveDir, 3);
/************ 开始下载操作 *******************/ loader.download(downloadProgressListener);
那我们看看构造器做了哪些工作??loader = new FileDownloader(getApplicationContext(), path,saveDir, 3);
URL url = new URL(this.downloadUrl);这里是一些网络请求的参数,就不赘述了,比如setConnectTimeout,setRequestProperty
另外我们参数里面还带一个多线程下载, this.threads = new DownloadThread[threadNum];// 实例化线程数组(这个一会再说)
获取相应成功后:
fileSize = conn.getContentLength()获取大小,为了分给每个线程的下载量、以及判断下载是否完毕情况
String filename = getFileName(conn);这个是获取服务器上的apk,然后构造一个名字
private String getFileName(HttpURLConnection conn) { //VPH1.1.1.20160714_Alpha.apk 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())) { // pattern() 返回正则表达式的字符串形式,其实就是返回Pattern.complile(String regex)的regex参数 Matcher m = Pattern.compile(".*filename=(.*)").matcher( mine.toLowerCase()); if (m.find()) return m.group(1); } } filename = UUID.randomUUID() + ".tmp";// 默认取一个文件名 } return filename; }
然后构建一个下载的文件的路径,以及构建一个文件的File
pathToSave=fileSaveDir.getPath()+"/"+filename;//下载到的目录 this.saveFile = new File(fileSaveDir, filename);// 构建保存文件
我们采用的是线程下载、也涉及到断点续传,那么就要涉及到数据库的操作,我们也在FileDownloader构造器定义了一个 fileService = new FileService(this.context);//对下载进度的管理、
看下数据库的结构如下:
如果之前下载过,但是没有下载完成,就存到数据库中,所以我们下载时候,进行判断
Map<Integer, Integer> logdata = fileService.getData(downloadUrl);// 获取下载记录,将每条线程threadid, downlength键值对放到map里面
然后在构造临时的map集合,然后统计所有线程下载的总和
if (logdata.size() > 0) { 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++) { //threadid-->>1 2 3 this.downloadSize += this.data.get(i + 1); } print("已经下载的长度" + this.downloadSize); }
在downloadSize这里算出是否有过下载量,或者之前没有下载过
this.block = (this.fileSize % this.threads.length) == 0 ? this.fileSize / this.threads.length : this.fileSize / this.threads.length + 1;
然后在计算出某条线程分配到的下载量
接下来,就进入到下载的操作了:
这里还做一个合理化的判断,如果你之前代码设置的是2个线程下载,后来修改代码改成3个线程,那么就修改下之前提到过的临时map集合,初始化每条线程为0,将之前即使有过的下载量改为0
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.downloadSize = 0; }
如果杀掉进程退出,在进来下载,可以先获取之前的数据库的线程下载记录,然后dele掉之前的数据库记录,然后在更新即可
如果去掉delete就会出现如下的尴尬局面
fileService.delete(this.downloadUrl);// 如果存在下载记录,删除它们,然后重新添加 fileService.save(this.downloadUrl, this.data);
然后去循环线程,如果每条线程的量小于之前预分配的为每条线程的block量,就开始start下载
for (int i = 0; i < this.threads.length; i++) {// 开启线程进行下载 int downLength = this.data.get(i + 1); if (downLength < this.block && this.downloadSize < this.fileSize) {// 判断线程是否已经完成下载,否则继续下载 if(this.threads[i] == null){ this.threads[i] = new DownloadThread(this, url, this.saveFile, this.block, this.data.get(i + 1), i + 1); } setExit(false); // this.threads[i].setPriority(7); // 设置线程优先级 /** * 下載操作 */ this.threads[i].start(); } else { this.threads[i] = null; } }
接下来我们就说说之前的那个DownloadThread
我们需要一个重要的类RandomAccessFile,百度上找了一段介绍如下:
RandomAccessFile是用来访问那些保存数据记录的文件的,你就可以用seek( )方法来访问记录,并进行读写了。这些记录的大小不必相同;但是其大小和位置必须是可知的。但是该类仅限于操作文件。
RandomAccessFile threadfile = new RandomAccessFile( this.saveFile, "rwd");
去获取每一条线程的初始结束位置
int startPos = block * (threadId - 1) + downLength;// 每一個線程下載的开始位置 int endPos = block * threadId - 1;// 结束位置
threadfile.seek(startPos);
定位到每条线程的开始位置
threadfile.write(buffer, 0, offset); downLength += offset; // 累加下载的大小 downloader.update(this.threadId, downLength); // 更新指定线程下载最后的位置 downloader.append(offset); // 累加已下载大小
输出到 this.saveFile目录,
更新数据库方法如下
protected synchronized void update(int threadId, int pos) { this.data.put(threadId, pos); this.fileService.update(this.downloadUrl, threadId, pos); }
更新下载的总量
protected synchronized void append(int size) { downloadSize += size; }
然后在FileDownloader中进行循环判断,看看是不是下载完毕,如果下载失败,在进行下载,比如断网、杀掉进程
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.downloadSize);// 通知目前已经下载完成的数据长度 Log.e("onDownloadSize", "onDownloadSize=====>" + downloadSize); }
最后下载完毕进行监听回调即可
if (listener != null) listener.onDownloadSize(this.downloadSize);// 通知目前已经下载完成的数据长度
那么我们就去看看通知栏的相关代码
当初在NewVersionActivity中DownloadTask的run方法中就进行了通知栏的设置
updateIntent = new Intent(); // (Context context, int requestCode, Intent intent, int // flags) 点击通知栏,回到自己的页面(没有去跳转其他的页面) updatePendingIntent = PendingIntent.getActivity( NewVersionActivity.this, 0, updateIntent, 0);
是进行通知栏这代码啥意思?就是点击通知栏回到自己的页面
if (maxSize == 0) maxSize = loader.getFileSize(); /********** 更新下载进度条 ************/ updateNotificationManager.notify(0, mBuilder.build());
进行通知栏目的一些图标,进度的设置,时时获取下载的进度,然后通知通知栏去更新进度
当下载完毕,去调用系统的安装进行安装,updatePendingIntent = PendingIntent.getActivity(
NewVersionActivity.this, 0, updateIntent, 0);
if (size == maxSize) { /** * 下载完毕,通过Intent机制,调出系统安装应用, */ Uri uri = Uri.fromFile(new File(FileDownloader.pathToSave)); updateIntent = new Intent(Intent.ACTION_VIEW); updateIntent.setDataAndType(uri, "application/vnd.android.package-archive"); /** * 启动安装界面,由本界面启动到安装的界面(系统的安装) */ updatePendingIntent = PendingIntent.getActivity( NewVersionActivity.this, 0, updateIntent, 0); mBuilder.setContentIntent(updatePendingIntent); mBuilder.setContentText("下载完成,点击安装"); //更新通知栏 updateNotificationManager.notify(0, mBuilder.build()); maxSize=0; tv_button.setEnabled(true); Toast.makeText(getApplicationContext(), "下载完成", Toast.LENGTH_LONG).show(); }
接下来看看断网的时候,怎么处理的:
private void registNetChangeReceiver() { IntentFilter mFilter = new IntentFilter(); mFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION); registerReceiver(myNetReceiver, mFilter); }
private BroadcastReceiver myNetReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) { if (isNet()) { // 网络连接 String name = netInfo.getTypeName(); if (netInfo.getType() == ConnectivityManager.TYPE_WIFI || netInfo.getType() == ConnectivityManager.TYPE_ETHERNET || netInfo.getType() == ConnectivityManager.TYPE_MOBILE) { if (path != null && savDir != null) download(path, savDir); } } else { exit();// 网络断开 } } } };
基本上就是这样的流程,介绍完毕,以下就是代码
############################代码区##########################
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/> <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
activity_main.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#ccc" tools:context="com.example.updateapp.MainActivity" > <RelativeLayout android:id="@+id/re_new_version" android:layout_width="fill_parent" android:layout_height="50dp" android:background="@drawable/selector_row"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerVertical="true" android:layout_marginLeft="22dp" android:text="新版本" android:textColor="@color/b6" android:textSize="16sp"/> <ImageView android:id="@+id/version_arrow" android:layout_width="31dp" android:layout_height="fill_parent" android:layout_alignParentRight="true" android:paddingLeft="6dp" android:paddingRight="17dp" android:src="@drawable/arrow"/> <ImageView android:visibility="gone" android:id="@+id/new_version" android:layout_width="wrap_content" android:layout_height="fill_parent" android:layout_toLeftOf="@id/version_arrow" android:paddingTop="14dp" android:paddingBottom="14dp" android:src="@drawable/newversiontip"/> </RelativeLayout> </RelativeLayout>
activity_new_version.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#e6e6e6" android:orientation="vertical"> <RelativeLayout android:layout_width="fill_parent" android:layout_height="48.33dp" android:background="@color/b2"> <ImageView android:id="@+id/image_back" android:layout_width="44dp" android:layout_height="fill_parent" android:layout_centerVertical="true" android:paddingLeft="10dp" android:paddingRight="10dp" android:src="@drawable/top_return_n"/> </RelativeLayout> <RelativeLayout android:layout_width="fill_parent" android:layout_height="wrap_content"> <ImageView android:id="@+id/iv_logo" android:layout_width="86dp" android:layout_height="86dp" android:layout_centerHorizontal="true" android:layout_marginTop="43dp" android:src="@drawable/logo" /> <TextView android:id="@+id/tv_version" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@id/iv_logo" android:layout_marginTop="14dp" android:layout_centerHorizontal="true" android:text="v.11" android:textColor="@color/black_b1" android:textSize="18sp"/> <TextView android:id="@+id/tv_button" android:layout_width="293.33dp" android:layout_height="40dp" android:layout_below="@id/tv_version" android:layout_marginTop="28dp" android:gravity="center" android:layout_centerHorizontal="true" android:background="#ccc" android:text="立即更新" android:textColor="#fff" android:textSize="18sp"/> <TextView android:id="@+id/tv_updated_content_detail" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@id/tv_button" android:layout_marginTop="14dp" android:layout_marginLeft="61dp" android:layout_marginRight="61dp" android:text="本次更新内容" android:textSize="14sp"/> </RelativeLayout> </LinearLayout>
先看第一个判断是否是新版本的部分:
UpdateApplication
package com.example.updateapp; import android.app.Application; import android.content.Context; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; public class UpdateApplication extends Application { private static UpdateApplication instance; private String versionName; private int versionCode; private String newVersion = null; private String downLoadPath = null; @Override public void onCreate() { super.onCreate(); this.instance = this; //初始化获取版本信息 getCurrentVersion(this); } public static UpdateApplication getInstance() { return instance; } public void getCurrentVersion(Context context) { PackageManager manager = context.getPackageManager(); try { PackageInfo info = manager.getPackageInfo(context.getPackageName(), 0); versionName = info.versionName; setVersionName(versionName); versionCode = info.versionCode; setVersionCode(versionCode); } catch (Exception e) { e.printStackTrace(); } } public void setVersionName(String versionName) { this.versionName = versionName; } public String getVersionName() { return versionName; } public void setVersionCode(int versionCode) { this.versionCode = versionCode; } public int getVersionCode() { return versionCode; } public void setNewVersion(String newVersion) { this.newVersion = newVersion; } public String getNewVersion() { return newVersion; } public void setDownLoadPath(String downLoadPath) { this.downLoadPath = downLoadPath; } public String getDownLoadPath() { return downLoadPath; } }
DownLoad
package com.example.updateapp; public class DownLoad { private String os_type; private String app_type; private String version_code; private String app_url; private String statuscode; private String statusmsg; private String version; private String pkgName; private String appName; private String whitelist_code; public String getUpdateMsg() { return updateMsg; } public void setUpdateMsg(String updateMsg) { this.updateMsg = updateMsg; } private String updateMsg; public String getPkgName() { return pkgName; } public void setPkgName(String pkgName) { this.pkgName = pkgName; } public String getAppName() { return appName; } public void setAppName(String appName) { this.appName = appName; } public String getWhitelistCode() { return whitelist_code; } public void setWhitelistCode(String whitelist_code) { this.whitelist_code = whitelist_code; } public String getVersion() { return version; } public void setVersion(String version) { this.version = version; } public String getOs_type() { return os_type; } public void setOs_type(String os_type) { this.os_type = os_type; } public String getApp_type() { return app_type; } public void setApp_type(String app_type) { this.app_type = app_type; } public String getVersion_code() { return version_code; } public void setVersion_code(String version_code) { this.version_code = version_code; } public String getApp_url() { return app_url; } public void setApp_url(String app_url) { this.app_url = app_url; } public String getStatuscode() { return statuscode; } public void setStatuscode(String statuscode) { this.statuscode = statuscode; } public String getStatusmsg() { return statusmsg; } public void setStatusmsg(String statusmsg) { this.statusmsg = statusmsg; } }
MainActivity
package com.example.updateapp; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.util.Log; import android.view.View; import android.view.View.OnClickListener; import android.widget.ImageView; import android.widget.RelativeLayout; import android.widget.Toast; public class MainActivity extends Activity implements OnClickListener { RelativeLayout re_new_version; ImageView new_version; private int versionCode; private GetVersionTask task;// 实现runnable接口 private String downLoadPath; private String newVersion; private String updateMsg; private String statuscode = null; private String statusmsg = null; public static final String STATUSSUCCESS = "000000"; private static final int VERSIONRESULT = 1; private static final int ERROR = 0; private Handler handler = new UIHandler(); /** * b.putString("statuscode",ownLoad.getStatuscode()); * b.putString("statusmsg", ownLoad.getStatusmsg()); * b.putString("new_version", ownLoad.getVersion()); * b.putString("update_msg", ownLoad.getUpdateMsg()); * */ private final class UIHandler extends Handler { public void handleMessage(Message msg) { switch (msg.what) { case VERSIONRESULT: statuscode = msg.getData().getString("statuscode"); statusmsg = msg.getData().getString("statusmsg"); /** * 只要返回就说明需要更新操作app */ if (STATUSSUCCESS.equals(statuscode)) { UpdateApplication.getInstance().setNewVersion(newVersion); UpdateApplication.getInstance().setDownLoadPath( downLoadPath); new_version.setVisibility(View.VISIBLE); Toast.makeText(MainActivity.this, "获取新版本成功", Toast.LENGTH_LONG).show(); } else { Toast.makeText(MainActivity.this, "获取新版本失败", Toast.LENGTH_LONG); new_version.setVisibility(View.INVISIBLE); } break; case ERROR: Toast.makeText(MainActivity.this, "网络请求异常", Toast.LENGTH_LONG) .show(); break; } } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initView(); } private void initView() { re_new_version = (RelativeLayout) findViewById(R.id.re_new_version); re_new_version.setOnClickListener(this); // 是否更新的标志 new_version = (ImageView) findViewById(R.id.new_version); // 获取最新的手机安装的versionCode versionCode = UpdateApplication.getInstance().getVersionCode(); } /** * 去判断是否是最新的版本 */ @Override protected void onResume() { super.onResume(); requestVersion(); } /** * 去服務器請求最新版本,开启线程操作 */ private void requestVersion() { task = new GetVersionTask(); // runnable接口 new Thread(task).start(); } public interface DownloadProgressListener { public void onDownloadSize(int size); public void getVersion(DownLoad ownLoad); } /** * 后台开启,请求服务器 */ private final class GetVersionTask implements Runnable { DownloadProgressListener downloadProgressListener = new DownloadProgressListener() { @Override public void onDownloadSize(int size) { // TODO Auto-generated method stub } @Override public void getVersion(DownLoad ownLoad) { if (ownLoad != null) { Log.i("Safly", "getStatuscode:" + ownLoad.getStatuscode() + ",getStatusmsg" + ownLoad.getStatusmsg() + ",getVersion:" + ownLoad.getVersion() + ",getUpdateMsg:" + ownLoad.getUpdateMsg()); if (ownLoad.getApp_url() != null) { downLoadPath = ownLoad.getApp_url(); Log.e("Safly", "ownLoad.getApp_url()====>" + ownLoad.getApp_url()); } if (ownLoad.getVersion() != null && STATUSSUCCESS.equals(ownLoad.getStatuscode())) { newVersion = ownLoad.getVersion(); } Message msg = new Message(); Bundle b = new Bundle(); if (ownLoad.getStatuscode() != null && ownLoad.getStatusmsg() != null) { b.putString("statusmsg", ownLoad.getStatusmsg()); b.putString("statuscode", ownLoad.getStatuscode()); b.putString("new_version", ownLoad.getVersion()); b.putString("update_msg", ownLoad.getUpdateMsg()); updateMsg = ownLoad.getUpdateMsg(); msg.setData(b); msg.what = VERSIONRESULT;// 1 handler.sendMessage(msg); } else { msg.what = ERROR;// 0 handler.sendMessage(msg); } } } }; @Override public void run() { /** * 去请求服务器 */ String os_type = "android"; String app_type = "3"; String version_code = versionCode + ""; DownLoad downLoad = new DownLoad(); downLoad.setOs_type(os_type); downLoad.setApp_type(app_type); downLoad.setVersion_code(version_code); // 定义一个接口,为了请求完毕后,去外面实现接口方法 RequescurVersion rquest = new RequescurVersion(MainActivity.this, downLoad, downloadProgressListener); rquest.start(); } } @Override public void onClick(View v) { switch (v.getId()) { case R.id.re_new_version: // 下載更新 Intent intent = new Intent(MainActivity.this, NewVersionActivity.class); if (downLoadPath != null && newVersion != null) { intent.putExtra("downLoadPath", downLoadPath); intent.putExtra("version", newVersion); if (updateMsg != null) { intent.putExtra("update_msg", updateMsg); } startActivity(intent); } break; default: break; } } }
RequescurVersion
package com.example.updateapp; import java.io.BufferedReader; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.Reader; import java.io.StringReader; import java.net.Socket; import java.net.URL; import java.net.UnknownHostException; import java.security.KeyManagementException; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.UnrecoverableKeyException; import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; import org.apache.http.conn.ssl.SSLSocketFactory; import android.content.Context; import android.util.JsonReader; import android.util.Log; import com.example.updateapp.MainActivity.DownloadProgressListener; public class RequescurVersion extends Thread { public static final String ROOT_RESPONSE = "Response"; public static final String ROOT_REQUEST = "Request"; public static final String COLON = ": "; public static final String LEFT_ANGLE_BRACKET = "{"; public static final String RIGHT_ANGLE_BRACKET = "}"; private DownloadProgressListener listener; private DownLoad downLoad; private Context context; private String requestJson; private String path = "https://www.imlianxi.com:7771/AppUpdate"; private String statuscode; private String statusmsg; private String app_url; public RequescurVersion(Context context, DownLoad downLoad, DownloadProgressListener downloadProgressListener) { this.downLoad = downLoad; this.listener = downloadProgressListener; this.context = context; requestJson = ProtocolUtil.buildJSONPacketBody(new String[] { "os_type", "app_type", "version_code" }, new Object[] { downLoad.getOs_type(), downLoad.getApp_type(), downLoad.getVersion_code() }); Log.e("Safly", "===============requestJson===================" + requestJson); } @Override public void run() { { try { byte[] requestStringBytes = requestJson.getBytes("UTF-8"); URL url = new URL(path); HttpsURLConnection conn = null; initHttps(); conn = (HttpsURLConnection) url.openConnection(); conn.setConnectTimeout(5 * 1000); conn.setRequestMethod("POST"); conn.setRequestProperty("Content-length", "" + requestStringBytes.length); 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", path); 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.connect(); Log.e("Safly", "===============connect==================="); OutputStream outputStream = conn.getOutputStream(); outputStream.write(requestStringBytes); outputStream.close(); int responseCode = conn.getResponseCode(); if (responseCode == 200) { Log.i("Safly", "connect 200..."); InputStream in = conn.getInputStream(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); int len = 0; byte[] buffer = new byte[1024]; while ((len = in.read(buffer)) != -1) { baos.write(buffer, 0, len); } baos.close(); in.close(); //字节数组转为字符串 final String result = baos.toString(); // BufferedReader respondReader = new BufferedReader( // new InputStreamReader(in, "UTF-8")); // // 输出相应内容 // String temp = ""; // String s = ""; // while ((temp = respondReader.readLine()) != null) { // s = s + temp; // } Log.e("Safly", "response result:" + result); jsonParse(result); // jsonParse(respondReader); if (downLoad != null) { Log.e("Safly", "RequescurVersion getStatuscode:" + downLoad.getStatuscode() + ",getStatusmsg" + downLoad.getStatusmsg() + ",getVersion:" + downLoad.getVersion() + ",getUpdateMsg:" + downLoad.getUpdateMsg()); if (listener != null) { listener.getVersion(downLoad); } } } } catch (Exception e) { Log.i("Safly", "connect wrong"); } } } /** * {"Response":{"body":{"app_url" :"http://www.imlianxi.com/Apk/Android/Vph/VPH1.1.1.20160714_Alpha.apk" ,"context": "1.优化部分用户登录注册失败的问题;\n2.优化部分手机使用联系人导入、删除、显示速度慢的问题;\n3.优化部分手机中第三方应用无法使用相机的问题。" ,"version":"VPH1.1.1.20160714_Alpha"},"head":{"statuscode":"000000", "statusmsg":"success"}}} */ // public void jsonParse(BufferedReader respondReader) { // JsonReader reader = new JsonReader(respondReader); public void jsonParse(String respondReader) { JsonReader reader = new JsonReader(new StringReader(respondReader)); Log.e("Safly", "jsonParse"); try { reader.beginObject(); while (reader.hasNext()) { String nameTag = reader.nextName(); Log.e("Safly", "nameTag---->>>>" + nameTag); if (ROOT_RESPONSE.equals(nameTag) || ROOT_RESPONSE.toLowerCase().equals(nameTag)) { Log.e("Safly", "OOT_RESPONSE.equals(nameTag)"); reader.beginObject(); while (reader.hasNext()) { nameTag = reader.nextName(); Log.e("Safly", "nameTag---->>>>" + nameTag); if ("body".equals(nameTag)) { Log.e("Safly", "nameTag---->>>>" + nameTag); handleReadJSONBody(reader); } else if ("head".equals(nameTag)) { Log.e("Safly", "nameTag---->>>>" + nameTag); handleReadJSONHead(reader); } else { reader.skipValue(); } } reader.endObject(); } } reader.endObject(); } catch (IOException e) { e.printStackTrace(); } } public void handleReadJSONBody(JsonReader reader) { try { reader.beginObject(); while (reader.hasNext()) { String nextName = reader.nextName(); if ("app_url".equals(nextName)) { app_url = reader.nextString(); downLoad.setApp_url(app_url); } else if ("version".equals(nextName)) { downLoad.setVersion(reader.nextString()); } else if ("context".equals(nextName)) { downLoad.setUpdateMsg(reader.nextString()); } else { reader.skipValue(); } } reader.endObject(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } /** * "head":{"statuscode":"000000","statusmsg":"success"} */ private void handleReadJSONHead(JsonReader reader) throws IOException { reader.beginObject(); while (reader.hasNext()) { String nextName = reader.nextName(); if ("statuscode".equals(nextName)) { statuscode = reader.nextString(); downLoad.setStatuscode(statuscode); } else if ("statusmsg".equals(nextName)) { statusmsg = reader.nextString(); downLoad.setStatusmsg(statusmsg); } else { reader.skipValue(); } } reader.endObject(); } private void initHttps() { try { KeyStore trustStore = KeyStore.getInstance(KeyStore .getDefaultType()); trustStore.load(null, null); SSLSocketFactoryEx sf = new SSLSocketFactoryEx(trustStore); HttpsURLConnection.setDefaultSSLSocketFactory(sf.getSSLContext() .getSocketFactory()); HttpsURLConnection .setDefaultHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER); } catch (Exception e) { } } static class SSLSocketFactoryEx extends SSLSocketFactory { SSLContext sslContext = SSLContext.getInstance("TLS"); public SSLSocketFactoryEx(KeyStore truststore) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException { super(truststore); TrustManager tm = new X509TrustManager() { public java.security.cert.X509Certificate[] getAcceptedIssuers() { return null; } @Override public void checkClientTrusted( java.security.cert.X509Certificate[] chain, String authType) throws java.security.cert.CertificateException { } @Override public void checkServerTrusted( java.security.cert.X509Certificate[] chain, String authType) throws java.security.cert.CertificateException { } }; sslContext.init(null, new TrustManager[] { tm }, null); } @Override public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException, UnknownHostException { return sslContext.getSocketFactory().createSocket(socket, host, port, autoClose); } @Override public Socket createSocket() throws IOException { return sslContext.getSocketFactory().createSocket(); } public SSLContext getSSLContext() { return sslContext; } } }
ProtocolUtil
package com.example.updateapp; import android.annotation.SuppressLint; import android.content.Context; import android.net.ConnectivityManager; import android.net.NetworkInfo; /** * simple tools to handle protocol for apps. */ public class ProtocolUtil { public static final String COLON = ": "; public static final String LEFT_ANGLE_BRACKET = "{"; public static final String RIGHT_ANGLE_BRACKET = "}"; public static String buildJSONPacketBody(String[] bodyKey, Object[] bodyValue) { if (( (bodyKey == null || bodyKey.length == 0) && (bodyValue == null || bodyValue.length == 0))) { throw new IllegalArgumentException(); } StringBuffer sb = new StringBuffer(LEFT_ANGLE_BRACKET + "\r\n"); sb.append("\t\"Request\"" + COLON + LEFT_ANGLE_BRACKET + "\r\n"); if (bodyKey != null) { sb.append("\t\t\"body\"" + COLON + LEFT_ANGLE_BRACKET + "\r\n"); for (int i = 0; i < bodyKey.length; i++) { if (bodyKey[i] == null || bodyKey[i].equals("")) { continue; } sb.append("\t\t\t\"" + bodyKey[i].toLowerCase() + "\"" + COLON) .append(JSONUtil.object2json(bodyValue[i])); if(i != bodyKey.length -1) { sb.append(","); } sb.append("\r\n"); } sb.append("\t\t" + RIGHT_ANGLE_BRACKET+ "\r\n"); } sb.append("\t" + RIGHT_ANGLE_BRACKET + "\r\n"); sb.append(RIGHT_ANGLE_BRACKET); clearObjectArray(bodyKey, bodyValue); return sb.toString(); } private static void clearObjectArray( String[] bk, Object[] bv) { if (bk != null && bv != null) { for (int i = 0; i < bk.length; i++) { bk[i] = null; bv[i] = null; } } bk = null; bv = null; } }
JSONUtil
package com.example.updateapp; import java.lang.reflect.Field; import java.math.BigDecimal; import java.math.BigInteger; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import com.google.gson.Gson; public class JSONUtil { /** 对象转换为json */ public static String object2json(Object obj) { StringBuilder json = new StringBuilder(); if (obj == null) { json.append("\"\""); } else if (obj instanceof String || obj instanceof Integer || obj instanceof Float || obj instanceof Boolean || obj instanceof Short || obj instanceof Double || obj instanceof Long || obj instanceof BigDecimal || obj instanceof BigInteger || obj instanceof Byte) { json.append("\"").append(string2json(obj.toString())).append("\""); } else if (obj instanceof Object[]) { json.append(array2json((Object[]) obj)); } else if (obj instanceof List) { json.append(list2json((List<?>) obj)); } else if (obj instanceof Map) { json.append(map2json((Map<?, ?>) obj)); } else if (obj instanceof Set) { json.append(set2json((Set<?>) obj)); } else { json.append(bean2json(obj)); } return json.toString(); } /** 对象转换为json */ public static String bean2json(Object bean) { Gson gson = new Gson(); return gson.toJson(bean); } /** List转换为json */ public static String list2json(List<?> list) { StringBuilder json = new StringBuilder(); json.append("["); if (list != null && list.size() > 0) { for (Object obj : list) { json.append(object2json(obj)); json.append(","); } json.setCharAt(json.length() - 1, ']'); } else { json.append("]"); } return json.toString(); } /** 数组转换为json */ public static String array2json(Object[] array) { StringBuilder json = new StringBuilder(); json.append("["); if (array != null && array.length > 0) { for (Object obj : array) { json.append(object2json(obj)); json.append(","); } json.setCharAt(json.length() - 1, ']'); } else { json.append("]"); } return json.toString(); } /** map转换为json */ public static String map2json(Map<?, ?> map) { StringBuilder json = new StringBuilder(); json.append("{"); if (map != null && map.size() > 0) { for (Object key : map.keySet()) { json.append(object2json(key)); json.append(":"); json.append(object2json(map.get(key))); json.append(","); } json.setCharAt(json.length() - 1, '}'); } else { json.append("}"); } return json.toString(); } /** set转换为json */ public static String set2json(Set<?> set) { StringBuilder json = new StringBuilder(); json.append("["); if (set != null && set.size() > 0) { for (Object obj : set) { json.append(object2json(obj)); json.append(","); } json.setCharAt(json.length() - 1, ']'); } else { json.append("]"); } return json.toString(); } public static String string2json(String s) { if (s == null) return ""; StringBuilder sb = new StringBuilder(); for (int i = 0; i < s.length(); i++) { char ch = s.charAt(i); switch (ch) { case '"': sb.append("\\\""); break; case '\\': sb.append("\\\\"); break; case '\b': sb.append("\\b"); break; case '\f': sb.append("\\f"); break; case '\n': sb.append("\\n"); break; case '\r': sb.append("\\r"); break; case '\t': sb.append("\\t"); break; case '/': sb.append("\\/"); break; default: if (ch >= '\u0000' && ch <= '\u001F') { String ss = Integer.toHexString(ch); sb.append("\\u"); for (int k = 0; k < 4 - ss.length(); k++) { sb.append('0'); } sb.append(ss.toUpperCase()); } else { sb.append(ch); } } } return sb.toString(); } /** * 对象转map * * @param obj * @return */ public static Map<String, Object> objToMap(Object obj) { Map<String, Object> map = new HashMap<String, Object>(); try { /* * BeanInfo beanInfo = Introspector.getBeanInfo(obj.getClass()); * PropertyDescriptor[] propertyDescriptors = beanInfo * .getPropertyDescriptors(); for (PropertyDescriptor property : * propertyDescriptors) { String key = property.getName(); // * 过滤class属性 if (!key.equals("class")) { // 得到property对应的getter方法 * Method getter = property.getReadMethod(); Object value = * getter.invoke(obj); map.put(key, value); } } */ Field[] fields = obj.getClass().getDeclaredFields(); for (Field field : fields) { String key = field.getName(); boolean accessFlag = field.isAccessible(); field.setAccessible(true); Object val = field.get(obj); if (val == null) { val = ""; } map.put(key, val); field.setAccessible(accessFlag); } } catch (Exception e) { e.printStackTrace(); } return map; } }
接下来看下载的部分:
DBOpenHelper
package com.example.updateapp; import android.content.Context; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; public class DBOpenHelper extends SQLiteOpenHelper { private static final String DBNAME = "eric.db"; private static final int VERSION = 1; public DBOpenHelper(Context context) { super(context, DBNAME, null, VERSION); } @Override public void onCreate(SQLiteDatabase db) { // 创建filedownlog表 db.execSQL("CREATE TABLE IF NOT EXISTS filedownlog (id integer primary key autoincrement, downpath varchar(100), threadid INTEGER, downlength INTEGER)"); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { db.execSQL("DROP TABLE IF EXISTS filedownlog"); onCreate(db); } }
FileService
package com.example.updateapp; import java.util.HashMap; import java.util.Map; import android.content.Context; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; /** * 获取每条线程已经下载的文件长度 * */ public class FileService { private DBOpenHelper openHelper; public FileService(Context context) { openHelper = new DBOpenHelper(context); } /** * 获取每条线程已经下载的文件长度 * */ public Map<Integer, Integer> getData(String path) { SQLiteDatabase db = openHelper.getReadableDatabase(); Cursor cursor = db .rawQuery( "select threadid, downlength from filedownlog where downpath=?", new String[] { path }); Map<Integer, Integer> data = new HashMap<Integer, Integer>(); while (cursor.moveToNext()) { data.put(cursor.getInt(0), cursor.getInt(1)); } cursor.close(); db.close(); return data; } /** * 保存每条线程已经下载的文件长度 * */ public void save(String path, Map<Integer, Integer> map) {// int threadid, // int position SQLiteDatabase db = openHelper.getWritableDatabase(); db.beginTransaction(); try { for (Map.Entry<Integer, Integer> entry : map.entrySet()) { db.execSQL( "insert into filedownlog(downpath, threadid, downlength) values(?,?,?)", new Object[] { path, entry.getKey(), entry.getValue() }); } db.setTransactionSuccessful(); } finally { db.endTransaction(); } db.close(); } /** * 实时更新每条线程已经下载的文件长度 */ 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(); } public void delete(String path) { SQLiteDatabase db = openHelper.getWritableDatabase(); db.execSQL("delete from filedownlog where downpath=?", new Object[] { path }); db.close(); } }
NewVersionActivity
package com.example.updateapp; import java.io.File; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import android.app.Activity; import android.app.Notification; import android.app.Notification.Builder; import android.app.NotificationManager; import android.app.PendingIntent; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.net.Uri; import android.os.Bundle; import android.os.Environment; import android.os.Handler; import android.os.Message; import android.util.Log; import android.view.View; import android.view.View.OnClickListener; import android.widget.ImageView; import android.widget.TextView; import android.widget.Toast; import com.example.updateapp.MainActivity.DownloadProgressListener; public class NewVersionActivity extends Activity implements OnClickListener { private ImageView image_back; private TextView tv_button, tv_version, updateMessage; // 通知栏 private NotificationManager updateNotificationManager = null; private Notification updateNotification = null; private DownloadTask task; private ConnectivityManager mConnectivityManager; private NetworkInfo netInfo; private boolean netConnect = true; private FileDownloader loader; private int maxSize;// 初始化文件的大小 private String path; private File savDir; // 下载路径 private String downLoadPath; // 需要更新的版本 private String version; // 需要更新的内容 private String update_msg; private int result = 0; private static final int PROCESSING = 1; private static final int FAILURE = -1; private Intent updateIntent = null; private PendingIntent updatePendingIntent = null; private Builder mBuilder; private Handler handler = new UIHandler(); private final class UIHandler extends Handler { public void handleMessage(Message msg) { switch (msg.what) { case PROCESSING: int size = msg.getData().getInt("size"); float num = (float) size / maxSize; result = (int) (num * 100); // 计算进度 mBuilder.setContentText("下载中..." + result + "%"); mBuilder.setWhen(System.currentTimeMillis()); //更新通知栏 updateNotificationManager.notify(0, mBuilder.build()); tv_button.setEnabled(false); if (size == maxSize) { /** * 下载完毕,通过Intent机制,调出系统安装应用, */ Uri uri = Uri.fromFile(new File(FileDownloader.pathToSave)); updateIntent = new Intent(Intent.ACTION_VIEW); updateIntent.setDataAndType(uri, "application/vnd.android.package-archive"); /** * 启动安装界面,由本界面启动到安装的界面(系统的安装) */ updatePendingIntent = PendingIntent.getActivity( NewVersionActivity.this, 0, updateIntent, 0); mBuilder.setContentIntent(updatePendingIntent); mBuilder.setContentText("下载完成,点击安装"); //更新通知栏 updateNotificationManager.notify(0, mBuilder.build()); maxSize=0; tv_button.setEnabled(true); Toast.makeText(getApplicationContext(), "下载完成", Toast.LENGTH_LONG).show(); } break; case FAILURE: Toast.makeText(getApplicationContext(), "error", Toast.LENGTH_LONG).show(); if (updateNotification != null) { cancelNotification(); } tv_button.setEnabled(true); break; } } } public void cancelNotification() { NotificationManager notificationManager = (NotificationManager) this.getSystemService(Context.NOTIFICATION_SERVICE); notificationManager.cancel(0); } /***************************************** * 注册网络广播,监听网络变化 */ private void registNetChangeReceiver() { IntentFilter mFilter = new IntentFilter(); mFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION); registerReceiver(myNetReceiver, mFilter); } /** * 获取控件 */ private void initRes() { image_back = (ImageView) findViewById(R.id.image_back); image_back.setOnClickListener(this); tv_button = (TextView) findViewById(R.id.tv_button); tv_button.setOnClickListener(this); tv_version = (TextView) findViewById(R.id.tv_version); updateMessage = (TextView) findViewById(R.id.tv_updated_content_detail); updateNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_new_version); registNetChangeReceiver(); initRes(); /** * 获取intent * downLoadPath:http://www.imlianxi.com/Apk/Android/Vph/VPH1.1.1 * .20160714_Alpha.apk, version:VPH1.1.1.20160714_Alpha, * update_msg:1.优化部分用户登录注册失败的问题; 2.优化部分手机使用联系人导入、删除、显示速度慢的问题; * 3.优化部分手机中第三方应用无法使用相机的问题。 */ Intent intent = getIntent(); if (intent != null) { Bundle bundle = intent.getExtras(); if (bundle != null) { // 获取新版本下载路径、版本、更新信息 downLoadPath = bundle.getString("downLoadPath"); version = bundle.getString("version"); update_msg = bundle.getString("update_msg"); Log.i("Safly", "downLoadPath:" + downLoadPath + ",version:" + version + ",update_msg:" + update_msg); if (update_msg != null || !update_msg.equals("")) { /** 更新的内容 */ updateMessage.setText(update_msg); } } else { tv_button.setEnabled(false); } } // 如果处于下载中状态,立即更新不能点击 if (result != 0) tv_button.setEnabled(false); /** 最新的版本信息 */ tv_version.setText(version); } @Override public void onClick(View v) { switch (v.getId()) { case R.id.image_back: finish(); break; case R.id.tv_button: if (!isNet()) { netConnect = false; Toast.makeText(NewVersionActivity.this, "网络不可用", Toast.LENGTH_LONG).show(); return; } path = downLoadPath; String filename = path.substring(path.lastIndexOf('/') + 1); try { // 文件名URL编码(这里是为了将中文进行URL编码) filename = URLEncoder.encode(filename, "UTF-8"); // filename:VPH1.1.1.20160714_Alpha.apk Log.i("Safly", "filename:" + filename); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } // 下載apk的路径 // path:http://www.imlianxi.com/Apk/Android/Vph/VPH1.1.1.20160714_Alpha.apk path = path.substring(0, path.lastIndexOf("/") + 1) + filename; Log.i("Safly", "path:" + path); if (Environment.getExternalStorageState().equals( Environment.MEDIA_MOUNTED)) { savDir = Environment.getExternalStorageDirectory(); // savDir:/storage/emulated/0 Log.i("Safly", "savDir:" + savDir); download(path, savDir); } else { Toast.makeText(getApplicationContext(), "sdcarderror", Toast.LENGTH_LONG).show(); } break; default: break; } } private void download(String path, File savDir) { task = new DownloadTask(path, savDir); new Thread(task).start(); } private final class DownloadTask implements Runnable { private String path; private File saveDir; public DownloadTask(String path, File saveDir) { this.path = path; this.saveDir = saveDir; } public DownloadProgressListener downloadProgressListener = new DownloadProgressListener() { @Override public void onDownloadSize(int size) { Message msg = new Message(); msg.what = PROCESSING; msg.getData().putInt("size", size); if (isNet()) handler.sendMessage(msg); } @Override public void getVersion(DownLoad ownLoad) { } }; public void run() { try { // 设置通知栏显示内容 // 实例化一个文件下载器 loader = new FileDownloader(getApplicationContext(), path, saveDir, 3); if (updateIntent == null && updatePendingIntent == null) { // updateIntent = new Intent(NewVersionActivity.this, // NewVersionActivity.class); updateIntent = new Intent(); // (Context context, int requestCode, Intent intent, int // flags) 点击通知栏,回到自己的页面(没有去跳转其他的页面) updatePendingIntent = PendingIntent.getActivity( NewVersionActivity.this, 0, updateIntent, 0); /************* 创建通知栏下载进度条目 ***************/ mBuilder = new Builder(NewVersionActivity.this); mBuilder.setContentTitle("X-Phone版本更新") .setContentText("下载中..." + result + "%") .setContentIntent(updatePendingIntent) .setSmallIcon(R.drawable.ic_launcher) .setAutoCancel(true); updateNotification = mBuilder.build(); if (maxSize == 0) maxSize = loader.getFileSize(); /********** 更新下载进度条 ************/ updateNotificationManager.notify(0, mBuilder.build()); } /************ 开始下载操作 *******************/ loader.download(downloadProgressListener); } catch (Exception e) { e.printStackTrace(); handler.sendMessage(handler.obtainMessage(FAILURE)); // 发送一条空消息对象 } } } /** * 网络广播类 */ private BroadcastReceiver myNetReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) { if (isNet()) { // 网络连接 String name = netInfo.getTypeName(); if (netInfo.getType() == ConnectivityManager.TYPE_WIFI || netInfo.getType() == ConnectivityManager.TYPE_ETHERNET || netInfo.getType() == ConnectivityManager.TYPE_MOBILE) { if (path != null && savDir != null) download(path, savDir); } } else { exit();// 网络断开 } } } }; public void exit() { if (loader != null) loader.exit(); } /** * 网络是否存在 */ public boolean isNet() { mConnectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); netInfo = mConnectivityManager.getActiveNetworkInfo(); if (netInfo != null && netInfo.isAvailable()) { netConnect = true; return netConnect; } else { netConnect = false; return netConnect; } } @Override protected void onDestroy() { super.onDestroy(); if (myNetReceiver != null) { unregisterReceiver(myNetReceiver); } } }
FileDownloader
package com.example.updateapp; import java.io.File; import java.io.RandomAccessFile; import java.net.HttpURLConnection; import java.net.URL; 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.example.updateapp.MainActivity.DownloadProgressListener; public class FileDownloader { private Context context; private FileService fileService; /* 停止下载 */ private boolean exit = false; /* 已下载文件长度 */ private int downloadSize = 0; /* 原始文件长度 */ private int fileSize; /* 线程数 */ private DownloadThread[] threads; /* 本地保存文件 */ private File saveFile; /* 缓存各线程下载的长度 */ private Map<Integer, Integer> data = new ConcurrentHashMap<Integer, Integer>(); /* 每条线程下载的长度 */ private int block; /* 下载路径 */ private String downloadUrl; public static String pathToSave = null; /** * 获取线程数 */ public int getThreadSize() { return threads.length; } /** * 退出下载 */ public void exit() { setExit(true); } public void setExit(boolean exit) { this.exit = exit; } public boolean getExit() { return this.exit; } /** * 获取文件大小 */ public int getFileSize() { return fileSize; } public void setFileSize(int fileSize) { this.fileSize = fileSize; } /** * 累计已下载大小 */ protected synchronized void append(int size) { downloadSize += size; } /** * 更新指定线程最后下载的位置 * <p/> * <p/> * 最后下载的位置 */ protected synchronized void update(int threadId, int pos) { this.data.put(threadId, pos); this.fileService.update(this.downloadUrl, threadId, pos); } /**************************** * 构建文件下载器 downloadUrl的网络地址 fileSaveDir是下载完毕文件存放的位置 threadNum线程数量 */ public FileDownloader(Context context, String downloadUrl, File fileSaveDir, int threadNum) { this.context = context; this.downloadUrl = downloadUrl; fileService = new FileService(this.context);// 对下载进度的管理 if (!fileSaveDir.exists()) // 判断目录是否存在,如果不存在,创建目录 fileSaveDir.mkdirs(); this.threads = new DownloadThread[threadNum];// 实例化线程数组 try { URL url = new URL(this.downloadUrl); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setConnectTimeout(5 * 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", 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"); conn.connect(); if (conn.getResponseCode() == 200) { // 响应成功 fileSize = conn.getContentLength();// 根据响应获取文件大小 setFileSize(fileSize); if (this.fileSize <= 0) throw new RuntimeException("Unkown file size "); String filename = getFileName(conn);// 获取网络上需要更新的apk的文件名称 pathToSave = fileSaveDir.getPath() + "/" + filename;// 下载到的目录 this.saveFile = new File(fileSaveDir, filename);// 构建保存文件 Map<Integer, Integer> logdata = fileService .getData(downloadUrl);// 获取下载记录 print("获取下载记录" + logdata.size() + "=====>" + this.data.size()); /** * 如果存在下载记录,就各条线程已经下载的数据长度放入data中 threadid, downlength */ if (logdata.size() > 0) { 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++) { // threadid-->>1 2 3 this.downloadSize += this.data.get(i + 1); } print("已经下载的长度" + this.downloadSize); } // 计算每条线程下载的数据长度 this.block = (this.fileSize % this.threads.length) == 0 ? this.fileSize / this.threads.length : this.fileSize / this.threads.length + 1; print("计算每条线程下载的数据长度" + this.block); } else { throw new RuntimeException("server no response "); } } catch (Exception e) { print("下载出错"); e.printStackTrace(); } } /** * 开始下载文件 */ public int download(DownloadProgressListener listener) throws Exception { try { 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.downloadSize = 0; } /** * 可以根據需求刪除 下載的記錄 */ fileService.delete(this.downloadUrl);// 如果存在下载记录,删除它们,然后重新添加 fileService.save(this.downloadUrl, this.data); for (int i = 0; i < this.threads.length; i++) {// 开启线程进行下载 int downLength = this.data.get(i + 1); if (downLength < this.block && this.downloadSize < this.fileSize) {// 判断线程是否已经完成下载,否则继续下载 if (this.threads[i] == null) { this.threads[i] = new DownloadThread(this, url, this.saveFile, this.block, this.data.get(i + 1), i + 1); } setExit(false); // this.threads[i].setPriority(7); // 设置线程优先级 /** * 下載操作 */ this.threads[i].start(); } else { this.threads[i] = null; } } 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.downloadSize);// 通知目前已经下载完成的数据长度 Log.e("onDownloadSize", "onDownloadSize=====>" + downloadSize); } /** * 可以根據需求 下载完成删除记录 */ // if (downloadSize == this.fileSize) // fileService.delete(this.downloadUrl); } catch (Exception e) { print("connect wrong"); } return this.downloadSize; } /** * 获取网络上最新的apk的文件名 * path:http://www.imlianxi.com/Apk/Android/Vph/VPH1.1.1.20160714_Alpha.apk * 通过方法getHeaderFieldKey可以获得响应头的key值,通过方法getHeaderField可以获得响应头的value值 */ private String getFileName(HttpURLConnection conn) { // VPH1.1.1.20160714_Alpha.apk 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())) { // pattern() 返回正则表达式的字符串形式,其实就是返回Pattern.complile(String // regex)的regex参数 Matcher m = Pattern.compile(".*filename=(.*)").matcher( mine.toLowerCase()); if (m.find()) return m.group(1); } } filename = UUID.randomUUID() + ".tmp";// 默认取一个文件名 } return filename; } private static void print(String msg) { Log.i("Safly", msg); } }
DownloadThread
package com.example.updateapp; import java.io.File; import java.io.InputStream; import java.io.RandomAccessFile; import java.net.HttpURLConnection; import java.net.URL; import android.util.Log; import com.example.updateapp.MainActivity.DownloadProgressListener; public class DownloadThread extends Thread { private static final String TAG = "Safly"; private URL downUrl; private File saveFile; private int block; private FileDownloader downloader; /* 下载开始位置 */ private int threadId = -1; private int downLength; private boolean finish = false; private DownloadProgressListener listener; /** * new DownloadThread(this, url, this.saveFile, this.block, this.data.get(i + 1), i + 1); */ public DownloadThread(FileDownloader 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; } @Override public void run() { if (downLength < block) {// 未下载完成 try { HttpURLConnection http = (HttpURLConnection) downUrl .openConnection(); http.setConnectTimeout(5 * 1000); // 设置连接超时 http.setRequestMethod("GET"); // 设置请求方法,这里是“GET” // 浏览器可接受的MIME类型 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());// 包含一个URL,用户从该URL代表的页面出发访问当前请求的页面。 http.setRequestProperty("Charset", "UTF-8"); // 字符集 int startPos = block * (threadId - 1) + downLength;// 每一個線程下載的开始位置 int endPos = block * threadId - 1;// 结束位置 http.setRequestProperty("Range", "bytes=" + startPos + "-" + endPos);// 设置获取实体数据的范围 // 浏览器类型,如果Servlet返回的内容与浏览器类型有关则该值非常有用。 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[102400]; int offset = 0; print("Thread " + this.threadId + " start download from position " + startPos); // 随机访问文件 RandomAccessFile threadfile = new RandomAccessFile( this.saveFile, "rwd"); // 定位到pos位置 threadfile.seek(startPos); while (!downloader.getExit() && (offset = inStream.read(buffer, 0, 102400)) != -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; } catch (Exception e) { this.downLength = -1; print("Thread " + this.threadId + ":" + e); } } } private static void print(String msg) { Log.i(TAG, msg); } public boolean isFinish() { return finish; } /** * 已经下载的内容大小 */ public long getDownLength() { return downLength; } }