1.App版本检测:
要实现App的更新下载,我们上面介绍了,前提是服务器要保存一个App的版本号(通常的方式是保存versionCode,当然你要对比versionName也没关系)。当用户去手动检测版本,或者进入首页自动检测时,第一步是需要请求服务器的版本号,拿到版本号之后与当前App版本号(当前版本号可通过PackageInfo获取)进行对比。服务器返回的版本号大于当前App版本号,证明App已经有更新,那么进入第2步。
2.Apk下载
Apk文件是保存在服务器的。我们可以通过Http流将其下载到本地手机,然后更新安装。Android中下载的方式很多种:HttpUrlConnection,Retrofit,okHttp,以及android原生的下载工具类DownLoadManager 等等。我们采用的方式是Google推荐的下载工具类DownLoadManager。关于DownLoadManager的使用其实很简单,简单概括如下:
(1)通过getSystemService获取DownLoadManager。
(2)初始化DownLoadManager的Request,构建下载请求。
(3)调用DownLoadManager的enqueue异步发起请求,该方法返回值为标识当前下载任务的id,即downloadId。
(4)当下载完成后,系统会发出条件为android.intent.action.DOWNLOAD_COMPLETE的广播,我们可以自定义广播接受器,然后在onReceive中处理下载完成的逻辑即可。
我们使用OkHttp实现版本更新下载:
添加依赖:
compile 'com.google.code.gson:gson:2.8.2' compile 'com.squareup.okhttp3:okhttp:3.9.1'
封装OKHttpUtils
public class OKHttpUtils { private static volatile OKHttpUtils instance; private final OkHttpClient client; private Handler handler = new Handler(); private OKHttpUtils(){ client = new OkHttpClient(); } public static OKHttpUtils getInstance(){ if (null == instance){ synchronized (OKHttpUtils.class){ if (instance==null){ instance = new OKHttpUtils(); } } } return instance; } public void get(String url, Map<String, String> map, final NetCallBack callBack, final Class clazz) { // http://www.wuxirui.com/api/checkversion.php?version=2.0.2&from=android // 1.http://www.wuxirui.com/api/checkversion.php // 2.http://www.wuxirui.com/api/checkversion.php?version=2.0.2 & // 3.http://www.wuxirui.com/api/checkversion.php? if (TextUtils.isEmpty(url)) { return; } StringBuffer sb = new StringBuffer(url); // 是否含有? (2,3) if (url.contains("?")) { // 问号是否是最后一个字符,是3 否2 if (url.endsWith("?")) { for (Map.Entry entry : map.entrySet()) { sb.append(entry.getKey()) .append("=") .append(entry.getValue()) .append("&"); } } else { // 问号不是最后一个字符2 for (Map.Entry entry : map.entrySet()) { sb.append("&") .append(entry.getKey()) .append("=") .append(entry.getValue()); } sb.append("&"); } } else { // 不包含? if (!map.isEmpty()) { sb.append("?"); for (Map.Entry entry : map.entrySet()) { sb.append(entry.getKey()) .append("=") .append(entry.getValue()) .append("&"); } } } sb.deleteCharAt(sb.lastIndexOf("&")); Log.i("zzz", "get url is: " + sb); Request request = new Request.Builder() .url(sb.toString()) .get() .build(); Call call = client.newCall(request); call.enqueue(new Callback() { @Override public void onFailure(Call call, final IOException e) { Log.e("zzz", "onFailure: " + e.getMessage()); handler.post(new Runnable() { @Override public void run() { callBack.onFailed(e); } }); } @Override public void onResponse(Call call, Response response) throws IOException { String result = response.body().string(); Log.i("zzz", "onResponse: " + result); if (!TextUtils.isEmpty(result)) { Gson gson = GsonUtils.getInstance(); final Object o = gson.fromJson(result, clazz); handler.post(new Runnable() { @Override public void run() { callBack.onSuccess(o); } }); } } }); } public void download(String url, final File file, final DownLoadListener listener) { // /sdcard/download/aaa.apk // 父目录是否存在 File parent = file.getParentFile(); if (!parent.exists()) { parent.mkdir(); } // 文件是否存在 if (!file.exists()) { try { file.createNewFile(); } catch (IOException e) { e.printStackTrace(); } } Request request = new Request.Builder() .url(url) .build(); Call call = client.newCall(request); call.enqueue(new Callback() { @Override public void onFailure(Call call, final IOException e) { handler.post(new Runnable() { @Override public void run() { listener.failed(e); } }); } @Override public void onResponse(Call call, Response response) throws IOException { ResponseBody body = response.body(); // 获取文件总长度 long totalLength = body.contentLength(); try { InputStream is = body.byteStream(); FileOutputStream fos = new FileOutputStream(file); byte[] buf = new byte[2048]; int length = 0; int sum = 0; while ((length = is.read(buf, 0, buf.length)) != -1) { sum += length; long progress = sum * 100 / totalLength; listener.progress(progress); fos.write(buf, 0, length); } fos.flush(); is.close(); fos.close(); listener.success(file.getAbsolutePath()); } catch (IOException e) { e.printStackTrace(); } } }); } }定义接口:
NetCallBack
public interface NetCallBack { void onSuccess(Object o); void onFailed(Exception e); }DownLoadListener
public interface DownLoadListener { void success(String path); void failed(Exception e); void progress(long progress); }gson单例模式:
GsonUtils
public class GsonUtils { private static Gson instance; private GsonUtils(){ } public static Gson getInstance(){ if (instance==null){ instance = new Gson(); } return instance; } }VersionUtils
public class VersionUtils { public static String getVersionName(Context context) { String versionName = ""; PackageManager packageManager = context.getPackageManager(); String packageName = context.getPackageName(); try { PackageInfo packageInfo = packageManager.getPackageInfo(packageName, 0); versionName = packageInfo.versionName; } catch (PackageManager.NameNotFoundException e) { e.printStackTrace(); } return versionName; } }MainActivity
public class MainActivity extends AppCompatActivity { private static final String TAG = "MainActivity"; private String updateUrl = "http://www.wuxirui.com/api/checkversion.php"; //http://www.wuxirui.com/download/jinritoutiao.apk private String path = "/download/"; private Button btn; private ProgressBar progressBar; private TextView tv_pro; @SuppressLint("HandlerLeak") private Handler handler = new Handler() { @Override public void handleMessage(Message msg) { super.handleMessage(msg); int i = msg.what; if (i==100){ tv_pro.setVisibility(View.GONE); progressBar.setVisibility(View.GONE); }else { tv_pro.setText(i + "%"); progressBar.setProgress(i); } } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); btn = findViewById(R.id.btn); progressBar = findViewById(R.id.progressBar); tv_pro = findViewById(R.id.tv_pro); String versionName = VersionUtils.getVersionName(this); Log.i("zzz", "versionName: " + versionName); Map<String, String> map = new HashMap<>(); map.put("version", versionName); // if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { File externalStorageDirectory = Environment.getExternalStorageDirectory(); // /sdcard/download/ String absolutePath = externalStorageDirectory.getAbsolutePath(); path = absolutePath + path; } OKHttpUtils.getInstance().get(updateUrl, map, new NetCallBack() { @Override public void onSuccess(Object o) { if (o instanceof MessageBean) { MessageBean info = (MessageBean) o; if (info != null) { if (info.isSuccess()) { MessageBean.ResultBean result1 = info.getResult(); final MessageBean.ResultBean result = info.getResult(); // 有更新 if (result.isHasnewversion()) { boolean isMust = result.isMustupdate(); AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this); builder.setTitle("更新提示") .setMessage(isMust ? "需要立即更新" : "是否需要更新") .setPositiveButton("更新", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int i) { tv_pro.setVisibility(View.VISIBLE); progressBar.setVisibility(View.VISIBLE); String url = result.getUrl(); // http://www.wuxirui.com/download/jinritoutiao.apk if (url.contains(".")) { String typeName = url.substring(url.lastIndexOf(".") + 1); if (url.contains("/")) { String filename = url.substring(url.lastIndexOf("/") + 1, url.lastIndexOf(".")); path = path + filename + "." + typeName; } } // 下载 download(result.getUrl(), new File(path), new DownLoadListener() { @Override public void success(String path) { Log.i(TAG, "success: " + path); // 启动自动安装 installApk(new File(path)); } @Override public void failed(Exception e) { Log.e(TAG, "failed: " + e.getMessage()); } @Override public void progress(long progress) { Log.i(TAG, "progress: " + progress); handler.sendEmptyMessageDelayed((int) progress, 1000); } }); } }); if (!isMust) { builder.setNegativeButton("稍候", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int i) { } }); } builder.create().show(); } } } } } @Override public void onFailed(Exception e) { } }, MessageBean.class); } private void download(String url, File file, DownLoadListener listener) { OKHttpUtils.getInstance().download(url, file, listener); } /** * 安装apk * * @param file */ private void installApk(File file) { Intent intent = new Intent(); intent.setAction(Intent.ACTION_VIEW); intent.addCategory("android.intent.category.DEFAULT"); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive"); startActivity(intent); android.os.Process.killProcess(android.os.Process.myPid()); } }布局文件:
activity_main.xml
xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.bwie.com.myapplication.MainActivity"> <Button android:id="@+id/btn" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="检测版本更新" /> <ProgressBar android:id="@+id/progressBar" style="?android:attr/progressBarStyleHorizontal" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_below="@+id/btn" android:layout_marginTop="45dp" android:max="100" android:visibility="gone" android:progress="0" /> <TextView android:id="@+id/tv_pro" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="0%" android:layout_below="@+id/progressBar" android:layout_centerHorizontal="true" android:visibility="gone" /> RelativeLayout>