Android Studio 为自己的应用程序添加自动更新功能

最近在完善自己开发的移动办公系统,为系统添加了检测版本以及更新版本的功能,检测版本实现很简单,只需要对比服务器返回的版本号就知道需不需要更新,而自动更新功能则相对复杂点,这里记录下。

先来看下后端是如何下载应用程序的。

/*
	 * app下载
	 */
	@Override
	public String appDownload(Params params,HttpServletResponse response) {
		JSONObject jsonObject = new JSONObject();
		//从参数中获取文件路径和名称(MD5加密)
		String path = ((HttpServletRequest)params.get("Request")).getSession().getServletContext().getRealPath("apk\\release");
		String appName = FileUtils.getAllFileName(path).get(0);
		//String downloadPath = "C:\\oasystemapp\\oasystem.apk";
		String downloadPath = path + "\\" + appName;
		logger.info("下载路径:"+path);
		logger.info("下载文件名称:"+appName);
		//得到要下载的文件
        File file = new File(downloadPath);
        if (!file.exists()) {
        	jsonObject.put("code", CodeState.ERROR_CODE);
        	jsonObject.put("message", "您要下载的资源已被删除!");
            return jsonObject.toString();
		}
        String filename = null;
        try {
          //转码,免得文件名中文乱码  
          filename = URLEncoder.encode("oasystem","UTF-8");
          //设置文件下载头  
          response.addHeader("Content-Disposition", "attachment;filename=" + appName);    
          //1.设置文件ContentType类型,这样设置,会自动判断下载文件类型    
          response.setContentType("multipart/form-data"); 
          response.setContentLength((int)file.length());//设置文件大小(需要转换成int类型)
          // 读取要下载的文件,保存到文件输入流
          FileInputStream in = new FileInputStream(downloadPath);
          // 创建输出流
          OutputStream out = response.getOutputStream();
          // 创建缓冲区
          byte buffer[] = new byte[1024]; 
          int len = 0;
          //循环将输入流中的内容读取到缓冲区当中
          while((len = in.read(buffer)) > 0){
          	out.write(buffer, 0, len);
          }
          //关闭文件输入流
          in.close();
          // 关闭输出流
          out.close();
        
        }
        catch(Exception e) {}

		jsonObject.put("code", CodeState.SUCCESS_CODE);
		jsonObject.put("message", "");
		return jsonObject.toString();
	}

流程很简单,首先构建下载文件的路径,接着对文件名称进行以UTF-8的格式进行编码,接着设置请求头相关的信息,这里注意下需要设置文件的大小,因为我们更新app是需要显示进度条的,必须要知道文件总共有多大。接着设置缓冲区大小等等,然后进行文件下载。

在Android使用progressDialog来显示进度条,在AsycTask中进行网络请求,并且通过AsycTask来实现在子线程中更新进度UI来显示下载进度,具体用法如下。

public class APPVersionCodeUtils {
    // 外存sdcard存放路径
    private static final String FILE_PATH = Environment.getExternalStorageDirectory()+"/"+"update";
    // 下载应用存放全路径
    private static final String FILE_NAME = FILE_PATH + "/" + "oasystem.apk";
    // 准备安装新版本应用标记
    private static final int INSTALL_TOKEN = 1;
    //Log日志打印标签
    private static final String TAG = "Update_log";
    private Context context;
    //获取新版APK的默认地址
    private String apk_downUrl = URLCommon.host+"/downloadAPP";//下载apk文件的url(可修改为自己的文件下载url)
    // 下载应用的进度条
    private ProgressDialog progressDialog;

    /**
     * 显示下载进度对话框
     */
    public void showDownloadDialog(Context context) {
        this.context = context;
        progressDialog = new ProgressDialog(context);
        progressDialog.setTitle("正在下载...");
        progressDialog.setCanceledOnTouchOutside(false);
        progressDialog.setCancelable(false);
        progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
        new downloadAsyncTask().execute();
    }

    /**
     * 下载新版本apk
     */
    private class downloadAsyncTask extends AsyncTask<Void, Integer, Integer> {
        @Override
        protected void onPreExecute() {
            Log.e(TAG, "执行至--onPreExecute");
            progressDialog.show();
        }

        @Override
        protected Integer doInBackground(Void... params) {
            Log.e(TAG, "执行至--doInBackground");
            URL url;
            HttpURLConnection connection = null;
            InputStream in = null;
            FileOutputStream out = null;

            try {
                url = new URL(apk_downUrl);
                connection = (HttpURLConnection) url.openConnection();
                in = connection.getInputStream();
                int fileLength = connection.getContentLength();
                Log.i("下载文件大小",String.valueOf(fileLength));
                File file_path = new File(FILE_PATH);
                if (!file_path.exists()) {
                    file_path.mkdir();
                }
                Log.e("下载文件到目录中",FILE_NAME);
                out = new FileOutputStream(new File(FILE_NAME));//为指定的文件路径创建文件输出流
                byte[] buffer = new byte[1024 * 1024];
                int len = 0;
                int readLength = 0;
                while ((len = in.read(buffer)) != -1) {
                    out.write(buffer, 0, len);//从buffer的第0位开始读取len长度的字节到输出流
                    readLength += len;
                    int curProgress = (int) (((float) readLength / fileLength) * 100);
                    Log.e(TAG, "当前下载进度:" + curProgress);
                    publishProgress(curProgress);//更新UI进度
                    if (readLength >= fileLength) {
                        Log.e(TAG, "执行至--readLength >= fileLength");
                        break;
                    }
                }
                out.flush();
                return INSTALL_TOKEN;
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                if (out != null) {
                    try {
                        out.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if (in != null) {
                    try {
                        in.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if (connection != null) {
                    connection.disconnect();
                }
            }
            return null;
        }

        @Override
        protected void onProgressUpdate(Integer... values) {
            Log.e(TAG, "异步更新进度接收到的值:" + values[0]);
            progressDialog.setProgress(values[0]);//设置进度
        }
        @Override
        protected void onPostExecute(Integer integer) {
            //处理完成后安装应用
            progressDialog.dismiss();//关闭进度条
            Log.e("下载文件大小",String.valueOf(new File(FILE_NAME).length()));
            //安装应用
            installApp();
        }
    }

    /**
     * 安装新版本应用
     */
    private void installApp() {
        File file = new File(FILE_NAME);
        //更新包文件
        Intent intent = new Intent();
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        intent.setAction(Intent.ACTION_VIEW);
        if (Build.VERSION.SDK_INT >= 24)
        { // Android7.0及以上版本 Log.d("-->最新apk下载完毕","Android N及以上版本");
            intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
            intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
            Uri contentUri = UpdateProvider.getUriForFile(context, "com.zhang.oa" + ".updateProvider", file);
//参数二:应用包名+".fileProvider"(和步骤二中的Manifest文件中的provider节点下的authorities对应)
            intent.setDataAndType(contentUri, "application/vnd.android.package-archive");
            List<ResolveInfo> resInfoList = context.getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
            for (ResolveInfo resolveInfo : resInfoList) {
                context.grantUriPermission(resolveInfo.activityInfo.packageName, contentUri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
            }
        } else {
            // Android7.0以下版本 Log.d("-->最新apk下载完毕","Android N以下版本");
            intent.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive");
            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        }
        context.startActivity(intent);
    }

    /**
     * 获取当前本地apk的版本
     *
     * @param mContext
     * @return
     */
    public static int getVersionCode(Context mContext) {
        int versionCode = 0;
        try {
            //获取软件版本号,对应AndroidManifest.xml下android:versionCode
            versionCode = mContext.getPackageManager().
                    getPackageInfo(mContext.getPackageName(), 0).versionCode;
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }
        return versionCode;
    }

    /**
     * 获取版本号名称
     *
     * @param context 上下文
     * @return
     */
    public static String getVerName(Context context) {
        String verName = "";
        try {
            verName = context.getPackageManager().
                    getPackageInfo(context.getPackageName(), 0).versionName;
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }
        return verName;
    }
}

介绍下流程,在AsycTask首先执行onPreExecute方法来显示出ProgressDialog组件,接着开始调用doInBackground来进行网络的请求,请求的url为下载最新应用程序的url,可以修改为自己需要的url,接着获取待下载文件的总大小,以及创建本地文件目录,接着通过io流不断的将服务器的应用文件下载到刚刚创建的文件目录内,每次下载的文件大小进行累加来计算下载进度,下载进度的计算方法为(int)(((float) 读取的总长度 / 文件总共大小) * 100),如果我们在后端没有设置文件大小的话,这里将无法获取文件的总大小,紧接着在需要更新文件的地方调用showDownloadDialog方法。

除此之外,还要记录下安装app的方法,自动安装app的功能卡了我有一个下午的时间,第二天总算是解决了,这里打开安装包使用的是FileProvider来实现的,由于Android7.0执行了StrictMode API 政策的原因,因此需要使用FileProvider来实现app的安装,首先在AndroidManifest文件中声明FileProvider,由于我之前做的功能已经包含了FileProvider,我想声明多个FileProvider则必须新建一个类UpdateProvider来继承FileProvider,接着在AndroidManifest中定义我声明的Provider即可,除了声明Provider,还要设置path文件,该文件是xml文件,里面指定共享目录。

类UpdateProvider

public class UpdateProvider extends FileProvider {
    
}

AndroidManifest声明自定义Provider

<provider
            android:name="com.zhang.oa.Provider.UpdateProvider"
            android:authorities="com.zhang.oa.updateProvider"
            android:grantUriPermissions="true"
            android:exported="false">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/filepaths" />
 </provider>

filepaths.xml文件 

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <files-path path="Android/data/com.zhang.oa/" name="files_root" />
    <root-path name="download" path="" />
</paths>

 

 在installApp()中打开apk的方式如下

实现效果图如下所示。 

 

 

 

 

 

 

 

  • 6
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值