APK 升级安装

一、需求

  1. 进入 APK 时,弹框提示升级
  2. 进入 APK 后,点击升级按钮提示升级

二、升级流程

  1. 访问服务器端最新 APK 的版本 server_version
  2. 获取本地已安装 APK 的版本 local_version
  3. 对比 server_version 和 local_version 的大小
  4. 若 server_version > local_version,则弹框提示升级
  5. 点击弹框中的取消按钮,不升级
  6. 点击弹框中的升级按钮,开始下载服务器端最新 APK
  7. 弹框提示下载进度
  8. 下载完成,进入安装界面
  9. 点击安装,升级完成

三、升级案例

3.1 服务端

准备:

  1. 记得添加 gson 的 jar 包到 path 路径
  2. 把服务器的 APK 放到 WebContent 目录下

开始:
1、使用 Eclipse 创建 Dynamic Web Project
创建 Dynamic Web Project
2、项目名称 UpdateApk
项目名称
3、项目 UpdateApk 目录结构
UpdateApk 目录结果
4、右键 src–New–Servlet,创建 CheckVersionServlet(用于获取服务器 APK 版本)和UpdateApkServlet(用户下载更新 APK)
5、CheckVersionServlet.java

@WebServlet("/CheckVersionServlet")
public class CheckVersionServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;

	protected void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		PrintWriter out = response.getWriter();
		// 服务器版本
		float serverVersion = ApkInfo.SERVER_VERSION;
		Gson gson = new Gson();
		ApkModel apkModel = new ApkModel();
		apkModel.setData(serverVersion);
		String json = gson.toJson(apkModel);
		out.println(json);
	}

	protected void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		doGet(request, response);
	}

}

6、UpdateApkServlet

@WebServlet("/UpdateApkServlet")
public class UpdateApkServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;

	protected void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		// 设置响应类型
		response.setContentType("text/html;charset=UTF-8");
		// 获取请求参数 user_version,对应客户端 APK 版本
		String user_version_name = request.getParameter("user_version");
		float user_version;
		try {
			// 请求参数 user_version 对应的字符串转为 float 类型的值
			user_version = Float.valueOf(user_version_name);
		} catch (NullPointerException | NumberFormatException e) { // 数字转换异常,则请求参数 user_version 格式无效,不能转为 float 类型
			PrintWriter out = response.getWriter();
			ApkModel apkModel = new ApkModel();
			apkModel.setData("请求参数 user_version 格式无效,不能转为 float 类型");
			Gson gson = new Gson();
			String json = gson.toJson(apkModel);
			out.println(json);
			// 请求参数 user_version 格式无效,直接返回
			return;
		}
		
		// 请求参数 user_version 格式有效,可转为 float 类型
		// 服务器 APK 版本
		float server_version = ApkInfo.SERVER_VERSION;
		// 客户端 APK 版本小于服务器 APK 版本
		if (user_version < server_version) {
			// 服务器 APK 的路径
			String apkPath = getServletContext().getRealPath("/");
			// 服务器 APK 的名称
			String apkName = ApkInfo.SERVER_APK_NAME;
			// 根据 APK 所在路径创建 APK 文件
			File apkFile = new File(apkPath + File.separator + apkName);
			// 文件存在
			if (apkFile.exists()) {
				// 在下载框默认显示的文件名
				String downloadFilename = ApkInfo.SERVER_APK_NAME;
				// 设置在下载框默认显示的文件名
				response.setHeader("Content-Disposition", "attachment;filename=" + downloadFilename);
				
				// 设置文件长度
				response.setContentLength((int) apkFile.length());
				// 指定返回对象是文件流
				response.setContentType("application/octet-stream");

				// 获取 APK 文件的输入流
				InputStream is = new FileInputStream(apkFile);
				BufferedInputStream bis = new BufferedInputStream(is);
				// 缓冲
				byte[] buffer = new byte[bis.available()];
				// 读到缓冲中
				bis.read(buffer);

				// 获取服务端的输出流
				ServletOutputStream os = response.getOutputStream();
				// 把缓冲写到输出流中
				os.write(buffer);
				os.flush();

				// 关闭各种流
				is.close();
				bis.close();
				os.close();
			}
		}

	}

	protected void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		doGet(request, response);
	}

}

7、ApkModel

public class ApkModel {

	/**
	 * 服务器返回给客户端的信息,可能是字符串,也可能是其它数据类型
	 */
	private Object data;
	
	public void setData(Object data) {
		this.data = data;
	}

	public Object getData() {
		return data;
	}
	
}
  1. ApkInfo
public class ApkInfo {

	// 服务器 APK 的版本,假设为 2.0
	public static final float SERVER_VERSION = 2.0f;
	
	// 服务器 APK 的名称,和 WebContent 下 APK 文件名一致
	public static final String SERVER_APK_NAME = "new.apk";
	
}

3.2 客户端

准备:

获取本机 IP:打开 cmd 命令行窗口,输入 ipconfig 后回车,或通过 ‘打开"网络和 Intent"设置’—查看网络属性—IPv4地址查看
获取服务器最新 APK 版本接口:http://你的 IP 地址:8080/UpdateApk/CheckVersionServlet
下载服务器最新 APK 接口:http://你的 IP 地址:8080/UpdateApk/UpdateApkServlet?user_version=x.x(x.x,如1.0)
使用自定义的 HttpUtils 网络访问框架访问网络,可用使用其它的方式访问网络

开始:
1、访问网络,获取服务器最新 APK 的版本,若有最新版本且版本大于已安装版本,则弹框提示升级

/**
 * 检测 APK 是否需要升级
 */
private void checkUpdate() {
    /**
     * 请求网络
     */
    HttpUtils.with(this)
            .url("http://你的 IP 地址:8080/UpdateApk/CheckVersionServlet")
            .execute(new HttpCallBack<ApkInfoModel>() {
                @Override
                public void onSuccess(final ApkInfoModel apkInfoModel) {
                    Log.e("tag", "local: " + ApkInfoUtils.getLocalApkVersion(MainActivity.this) + ", server: " + apkInfoModel.getData());

                    // 成功回调
                    if (apkInfoModel != null && ApkInfoUtils.isNeedUpdate(MainActivity.this, apkInfoModel.getData())) {
                        AlertDialog dialog = new AlertDialog.Builder(MainActivity.this)
                                .setMessage("当前有新版本,是否进行升级?")
                                .setNegativeButton("否", null)
                                .setPositiveButton("是", new DialogInterface.OnClickListener() {
                                    @Override
                                    public void onClick(DialogInterface dialog, int which) {
	                                    // 使用 ApkDownLoader 下载 APK
                                        ApkDownLoader.init(MainActivity.this).downLoadApk(apkInfoModel.getData(), "http://你的 IP 地址:8080/UpdateApk
/UpdateApkServlet");
                                    }
                                }).create();
                        dialog.show();
                    } else {
                        Toast.makeText(MainActivity.this, "不需要升级", Toast.LENGTH_SHORT).show();
                    }
                }

                @Override
                public void onError(Exception e) {
                    // 失败回调
                    Toast.makeText(MainActivity.this, e.getMessage(), Toast.LENGTH_SHORT).show();
                }
            });
}

2、ApkDownLoader

public class ApkDownLoader {

    // APK 下载文件村路径(当前指定 APK 文件路径为 SD 卡根目录下 download 目录,当 Android 版本大于 7.0 时,必须使用 FileProvider 获取文件访问的权限,需要在下面 provider_paths.xml 中设置 path 的值为 download)
    public static final String SD_FOLDER = Environment.getExternalStorageDirectory() + "/download";
    // APK 下载文件
    private static File mApkFile;
    
    // 下载进度提示框
    private static ProgressDialog pd;
    // 最大进度
    private static final int MAX_PROGRESS = 100;

    // Handler:下载中,显示进度消息
    private static final int MSG_SHOW_PROGRESS = 1;
    // Handler:下载完成,安装 APK 消息
    private static final int MSG_INSTALL_APK = 2;
    // Handler 处理下载消息
    private MyHandler handler;

    // 上下文
    private Context mContext;

    private ApkDownLoader(Context context) {
        this.mContext = context;
    }

    /**
     * 初始化 ApkDownLoader
     * @param context 上下文
     * @return ApkDownLoader
     */
    public static ApkDownLoader init(Context context) {
        return new ApkDownLoader(context);
    }

    /**
     * @param serverApkVersion
     * @param url
     */
    public void downLoadApk(float serverApkVersion, final String url) {
        showProgressDialog();

        handler = new MyHandler(this);

        downloadFile(mContext, url);
    }

    /**
     * 显示下载进度框
     */
    private void showProgressDialog() {
        pd = new ProgressDialog(mContext);
        pd.setCancelable(false); // 不能取消
        pd.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
        pd.setMessage("正在下载安装包,请稍后");
        pd.setTitle("版本升级");
        pd.setMax(MAX_PROGRESS); // 最大进度
        pd.create();
        pd.show();
    }

    private void downloadFile(final Context context, String url) {
        /**
         * 请求网络
         */
        HttpUtils.with(mContext).get()
                .url(url)
                .addParam("user_version", "" + ApkInfo.getLocalApkVersion(context))
                .execute(new IEngineCallBack() {

                    @Override
                    public void onSuccess(Response result) {
                        // SD 卡可用,下载最新 APK
                        if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
                            InputStream is = null;
                            OutputStream os = null;
                            // 成功回调
                            try {
                                // 获取输入流
                                is = result.body().byteStream();
                                // 创建最新 APK 文件
                                mApkFile = new File(SD_FOLDER, "new.apk");
                                // APK 保存所在目录不存在
                                if (!mApkFile.getParentFile().exists()) {
                                    // 创建 APK 保存所在目录
                                    mApkFile.getParentFile().mkdirs();
                                }
                                os = new FileOutputStream(mApkFile);
                                int len;
                                int total = 0;
                                byte[] buffer = new byte[1024];
                                while ((len = is.read(buffer)) != -1) {
                                    os.write(buffer, 0, len);
                                    total += len;

                                    // 通过 handler 发送消息,更新下载进度
                                    Message msg = Message.obtain();
                                    msg.what = MSG_SHOW_PROGRESS;
                                    msg.arg1 = total;
                                    handler.sendMessage(msg);
                                }
                            } catch (IOException e) {
                                e.printStackTrace();
                            } finally {
                                // 关闭输出流
                                IOExceptionCloser.close(os);
                                // 关闭输入流
                                IOExceptionCloser.close(is);
                            }
                        } else {
                            // 提示 SD 卡不可用
                            HttpUtils.handler.post(new Runnable() {
                                @Override
                                public void run() {
                                    Toast.makeText(context, "SD 卡不可用", Toast.LENGTH_SHORT).show();
                                }
                            });
                        }
                    }

                    @Override
                    public void onError(Exception e) {
                        Toast.makeText(context, e.getMessage(), Toast.LENGTH_SHORT).show();
                    }
                });
    }

    /**
     * Handler 处理下载消息
     */
    private static class MyHandler extends Handler {

        private WeakReference<ApkDownLoader> reference;

        public MyHandler(ApkDownLoader apkDownLoader) {
            reference = new WeakReference<ApkDownLoader>(apkDownLoader);
        }

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);

            if (reference == null || reference.get() == null) {
                return;
            }

            switch (msg.what) {
                case MSG_SHOW_PROGRESS:
                    // 显示下载进度通知
                    reference.get().showDownloadProgress(msg.arg1);
                case MSG_INSTALL_APK:
                    // 安装 APK
                    reference.get().installApk();
                    break;
            }
        }
    };

    /**
     * 安装 APK
     */
    private void installApk() {
        Intent intent = new Intent(Intent.ACTION_VIEW);
        File file = new File(mApkFile.getAbsolutePath());
        Uri uri;
        // Android 版本大于 7.0,需要使用 FileProvider 获取文件访问的权限
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
            uri = FileProvider.getUriForFile(mContext, "你的应用包名.provider", file);
        }
        // Android 版本小于 7.0,直接使用文件即可
        else {
            uri = Uri.fromFile(file);
        }
        intent.setDataAndType(uri, "application/vnd.android.package-archive");
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        // 启动安装界面进行安装
        mContext.startActivity(intent);
    }

    /**
     * 显示下载进度
     * @param progress 当前进度,最大进度为 100
     */
    private void showDownloadProgress(int progress) {
        // 设置当前下载进度
        pd.setProgress(progress);

        // 进度为 100,下载完成
        if (progress == MAX_PROGRESS) {
            pd.setMessage("下载完毕");
            // 发送下载完成消息
            handler.sendEmptyMessage(MSG_INSTALL_APK);
        }
    }
}

3、ApkInfoModel

public class ApkInfoModel {

    private float data;

    public float getData() {
        return data;
    }
}

4、ApkInfoUtils

public class ApkInfoUtils {

    /**
     * 获取本地 APK 版本
     * @param context 上下文
     * @return 本地 APK 版本
     */
    public static float getLocalApkVersion(Context context) {
        try {
            String localPackage = context.getPackageName();
            PackageInfo packageInfo = context.getPackageManager().getPackageInfo(localPackage, 0);
            return Float.valueOf(packageInfo.versionName);
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }
        return 0;
    }

    /**
     * APK 是否需要升级
     * @param context 上下文
     * @param serverApkVersion 服务器端 APK 版本号
     * @return
     */
    public static boolean isNeedUpdate(Context context, float serverApkVersion) {
        float localApkVersion = getLocalApkVersion(context);
        if (serverApkVersion > localApkVersion) {
            return true;
        }
        return false;
    }
}
  1. IOExceptionCloser
public class IOExceptionCloser {

    public static void close(Closeable io) {
        if (io != null) {
            try {
                io.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

注意

1、Android 7.0 开始需要使用 FileProvider 来获取其他应用的文件访问权限,需要在 AndroidManifest.xml 文件的 application 标签内注册一个 provider

<provider
    android:authorities="你的应用包名.provider" // 必须保证唯一
    android:name="android.support.v4.content.FileProvider"
    android:exported="false"
    android:grantUriPermissions="true">

    <meta-data android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/provider_paths"/>

</provider>

2、provider_paths.xml:存放在 res/xml 下

<?xml version="1.0" encoding="utf-8"?>
<paths>
    <external-path
        name="external_path"
        path="download"/> // path 不写任何内容,默认路径为 SD 卡根目录
</paths>

3、注册相关权限

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>

关于 Android 6.0 运行时权限的处理找个 demo 看一看,然后用一用就好了
至此,APK 升级安装的功能就基本实现了

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Android 11 中,可以使用 PackageInstaller API 来实现应用程序的静默升级。以下是实现静默升级的步骤: 1. 获取应用程序的 APK 文件。 2. 创建 PackageInstaller.SessionParams 对象。 3. 调用 PackageInstaller.createSession() 方法创建一个会话。 4. 通过会话 ID 打开输出流,并将 APK 文件写入输出流中。 5. 启动会话,等待应用程序安装完成。 以下是一个简单的示例代码: ```java private void installPackageSilently(String apkPath) { // 获取应用程序的 APK 文件 File apkFile = new File(apkPath); // 创建 PackageInstaller.SessionParams 对象 PackageInstaller.SessionParams params = new PackageInstaller.SessionParams( PackageInstaller.SessionParams.MODE_FULL_INSTALL); // 调用 PackageInstaller.createSession() 方法创建一个会话 PackageInstaller packageInstaller = context.getPackageManager().getPackageInstaller(); int sessionId = packageInstaller.createSession(params); try { // 通过会话 ID 打开输出流,并将 APK 文件写入输出流中 PackageInstaller.Session session = packageInstaller.openSession(sessionId); OutputStream out = session.openWrite("app", 0, -1); FileInputStream in = new FileInputStream(apkFile); byte[] buffer = new byte[65536]; int c; while ((c = in.read(buffer)) != -1) { out.write(buffer, 0, c); } session.fsync(out); in.close(); out.close(); // 启动会话,等待应用程序安装完成 session.commit(createIntentSender(context, sessionId)); } catch (IOException e) { e.printStackTrace(); } } private IntentSender createIntentSender(Context context, int sessionId) { Intent intent = new Intent(context, getClass()); intent.putExtra(EXTRA_SESSION_ID, sessionId); PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0); return pendingIntent.getIntentSender(); } ``` 需要注意的是,静默升级需要在系统签名的应用程序中运行,并且需要 android.permission.INSTALL_PACKAGES 权限。另外,如果应用程序已经在运行,则静默升级可能会失败。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值