Android应用内升级App

一、主要流程

在这里插入图片描述

二、主要知识点

1.利用OkHttpClient实现网络请求(包括下载和取消下载)

访问http://59.110.162.30/app_updater_version.json,得到更新的apk版本信息.

  1. 定义接口包含所需的方法
/**
* 提供网络接口进行调用:
* 1.接口隔离具体的实现,解耦合
* 2.方便多个开发者并行开发
*/
public interface INetManager {
   void get(String url, INetCallBack callBack,Object tag);

   void download(String url, File targetFile, INetDownloadCallBack downloadCallBack,Object tag);

   void cancel(Object tag);
}
  1. 具体的网络请求实现类使用OkHttpClient框架来实现
/**
* 网络请求具体实现类:使用OKHttpClient框架来实现
*/
public class OkHttpNetManager implements INetManager {
   private static final String TAG = "Selenium";
   private OkHttpClient okHttpClient;
   private Handler mHandler = new Handler(Looper.getMainLooper());

   public OkHttpNetManager() {
       OkHttpClient.Builder builder = new OkHttpClient.Builder();
       okHttpClient = builder.connectTimeout(15, TimeUnit.SECONDS)
               .build();
   }


   @Override
   public void get(String url, final INetCallBack callBack, Object tag) {
       Request.Builder requestBuilder = new Request.Builder();
       Request request = requestBuilder.url(url).tag(tag).get().build();
       Call call = okHttpClient.newCall(request);
       //同步请求
       // Response response = call.execute();
       //异步请求
       call.enqueue(new Callback() {
           @Override
           public void onFailure(Call call, final IOException e) {
               mHandler.post(new Runnable() {
                   @Override
                   public void run() {
                       e.printStackTrace();
                       callBack.failed(e);
                   }
               });
           }

           @Override
           public void onResponse(Call call, final Response response) throws IOException {
               final String result;
               try {
                   result = response.body().string();
                   mHandler.post(new Runnable() {
                       @Override
                       public void run() {
                           callBack.success(result);
                       }
                   });
               } catch (Throwable e) {
                   e.printStackTrace();
                   callBack.failed(e);
               }
           }
       });

   }

   //下载文件
   @Override
   public void download(String url, final File targetFile, final INetDownloadCallBack downloadCallBack, Object tag) {
       Request.Builder requestBuilder = new Request.Builder();
       Request request = requestBuilder.url(url).tag(tag).get().build();
       Call call = okHttpClient.newCall(request);
       //同步请求
       // Response response = call.execute();
       //异步请求
       call.enqueue(new Callback() {
           @Override
           public void onFailure(Call call, IOException e) {
               if (call.isCanceled()) { //任务取消掉后直接return,防止空指针异常
                   return;
               }
               e.printStackTrace();
               downloadCallBack.failed(e);
           }

           @Override
           public void onResponse(Call call, Response response) throws IOException {
               InputStream is = null;
               OutputStream os = null;

               try {
                   is = response.body().byteStream();
                   //总文件大小
                   final long totalSize = response.body().contentLength();
                   if (targetFile != null) {
                   }

                   os = new FileOutputStream(targetFile);
                   byte[] buffer = new byte[1024];
                   int len = 0;
                   //已下载文件大小
                   long downloadSize = 0;
                   //读取网络字节流并写入文件
                   while ((len = is.read(buffer)) != -1) {
                       os.write(buffer, 0, len);
                       os.flush();
                       downloadSize += len;
                       final long finalDownloadSize = downloadSize;
                       mHandler.post(new Runnable() {
                           @Override
                           public void run() {
                               //乘以1.0f防止小数整除大数为0
                               downloadCallBack.progress((int) (((finalDownloadSize * 1.0f) / totalSize) * 100))
                               ;
                           }
                       });
                   }

                   //保险起见为文件设置可读可写可执行
                   targetFile.setExecutable(true, false);
                   targetFile.setReadable(true, false);
                   targetFile.setWritable(true, false);

                   mHandler.post(new Runnable() {
                       @Override
                       public void run() {
                           downloadCallBack.success(targetFile);
                       }
                   });
               } catch (Exception e) {
                   if (call.isCanceled()) {//任务取消掉后直接return,防止空指针异常
                       return;
                   }
                   e.printStackTrace();
                   downloadCallBack.failed(e);
               } finally {
                   //不要忘了关闭流
                   if (is != null) {
                       is.close();
                   }
                   if (os != null) {
                       os.close();
                   }
               }
           }
       });
   }

   //取消下载
   @Override
   public void cancel(Object tag) {
       List<Call> queuedCalls = okHttpClient.dispatcher().queuedCalls();
       List<Call> runningCalls = okHttpClient.dispatcher().runningCalls();

       //取消正在执行中的任务
       if (runningCalls != null) {
           for (Call call : runningCalls) {
               call.cancel();
           }
       }
       //取消排队中的任务
       if (queuedCalls != null) {
           for (Call call : queuedCalls) {
               call.cancel();
           }
       }
   }
}

2.自定义Dialog

  1. 在layout下创建dialog_show_app_info.xml文件,对ui进行设计
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:tools="http://schemas.android.com/tools"
   android:layout_width="400px"
   android:layout_height="wrap_content"
   android:orientation="vertical">

   <ImageView
       android:layout_width="400px"
       android:layout_height="wrap_content"
       android:scaleType="centerCrop"
       android:src="@mipmap/robot" />

   <LinearLayout
       android:layout_width="400px"
       android:layout_height="wrap_content"
       android:background="@android:color/white"
       android:orientation="vertical">

       <TextView
           android:id="@+id/tv_title"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:layout_gravity="center_horizontal"
           android:layout_marginTop="5px"
           android:textAlignment="center"
           android:textColor="@android:color/black"
           android:textSize="40px"
           android:textStyle="bold"
           tools:text="我是标题" />

       <TextView
           android:id="@+id/tv_content"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:layout_marginTop="20px"
           android:layout_gravity="center_horizontal"
           android:textColor="@android:color/black"
           tools:text="我是内容" />

       <Button
           android:id="@+id/btn_update"
           android:layout_width="300px"
           android:layout_height="70px"
           android:layout_gravity="center"
           android:layout_marginTop="20px"
           android:layout_marginBottom="10px"
           android:background="@drawable/shape_show_app_info_dialog_btn"
           android:text="升级"
           android:textColor="@android:color/white"></Button>
   </LinearLayout>
</LinearLayout>

在这里插入图片描述

  1. 继承DialogFragment,在onCreateView()中渲染view.
/**
* 自定义Dialog
*/
public class ShowAppInfoDialog extends DialogFragment {

   public String FILE_SAVE_PATH = Environment.getExternalStorageDirectory().getAbsolutePath() + "/Download/";
   String FILE_NAME = "download.apk";
   public static final String KEY_DOWNLOAD_APP_INFO = "download_app_info";
   public static AppInfo appInfo = null;
   private Handler mHandler = new Handler(Looper.getMainLooper());
   private static final String TAG = "selenium";

   @Override
   public void onCreate(@Nullable Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       //获取AppInfo数据
       Bundle arguments = getArguments();
       if (arguments != null) {
           appInfo = arguments.getParcelable(KEY_DOWNLOAD_APP_INFO);
       }
   }

   @Nullable
   @Override
   public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
       View root = inflater.inflate(R.layout.dialog_show_app_info, null);
       TextView title = root.findViewById(R.id.tv_title);
       TextView content = root.findViewById(R.id.tv_content);
       Button updateBtn = root.findViewById(R.id.btn_update);
       title.setText(appInfo.getTitle());
       content.setText(appInfo.getContent());
       bindEvent(updateBtn);
       return root;
   }

   @Override
   public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
       super.onViewCreated(view, savedInstanceState);
       getDialog().requestWindowFeature(Window.FEATURE_NO_TITLE);
       getDialog().getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
   }

   protected void bindEvent(final Button updateBtn) {
       updateBtn.setOnClickListener(new View.OnClickListener() {
           @Override
           public void onClick(View v) {
               updateBtn.setEnabled(false);
               //4.点击下载
               //先创建文件夹再创建文件
               File dir = new File(FILE_SAVE_PATH);
               if (!dir.exists()) {
                   dir.mkdirs();
               }
               File file = new File(dir, FILE_NAME);
               if (!file.exists()) {
                   try {
                       file.createNewFile();
                   } catch (IOException e) {
                       e.printStackTrace();
                   }
               }
               AppUpdater.getInstance().getNetManager().download(appInfo.getUrl(), file, new INetDownloadCallBack() {

                   @Override
                   public void success(File apkFile) {
                       updateBtn.setEnabled(true);
                       dismiss();
                       //check md5
                       String md5 = AppUtil.getFileMd5(apkFile);
                       Log.i(TAG, "md5: " + md5);
                       if (md5 == null || md5 != appInfo.getMd5()) {
                           //安装下载程序
                           AppUtil.installApk(getActivity(), apkFile);
                       } else {
                           mHandler.post(new Runnable() {
                               @Override
                               public void run() {
                                   Toast.makeText(getActivity(), "下载文件已损坏", Toast.LENGTH_SHORT).show();
                               }
                           });
                       }
                   }

                   @Override
                   public void progress(int progress) {
                       Log.i(TAG, "progress: " + progress);
                       updateBtn.setText(progress + "%");
                   }

                   @Override
                   public void failed(Throwable throwable) {
                       updateBtn.setEnabled(true);
                       mHandler.post(new Runnable() {
                           @Override
                           public void run() {
                               Toast.makeText(getActivity(), "文件下载失败", Toast.LENGTH_SHORT).show();
                           }
                       });
                   }
               }, ShowAppInfoDialog.this);
           }
       });
   }

   public static void show(FragmentActivity activity, AppInfo appInfo) {
       Bundle bundle = new Bundle();
       bundle.putParcelable(KEY_DOWNLOAD_APP_INFO, appInfo);
       ShowAppInfoDialog dialog = new ShowAppInfoDialog();
       dialog.setArguments(bundle);
       dialog.show(activity.getSupportFragmentManager(), "showAppInfoDialog");
   }

   @Override
   public void onDismiss(DialogInterface dialog) {
       super.onDismiss(dialog);
       Log.i(TAG, "onDismiss: canceled");
       AppUpdater.getInstance().getNetManager().cancel(ShowAppInfoDialog.this);
   }
}

3.利用md5对文件做完整性校验

两个文件的md5值相同,可以认为这两个文件完全相同.

  1. 获取本地文件的md5值
 public static String getFileMd5(File file) {
       MessageDigest digest = null;
       FileInputStream is = null;
       try {
           digest = MessageDigest.getInstance("MD5");
           is = new FileInputStream(file);
           byte[] buffer = new byte[1024];
           int len = 0;
           while ((len = is.read(buffer)) != -1) {
               digest.update(buffer, 0, len);
           }
           byte[] result = digest.digest();
           //转为16进制
           return new BigInteger(result).toString(16);
       } catch (Exception e) {
           e.printStackTrace();
           return null;
       } finally {
           try {
               is.close();
           } catch (IOException e) {
               e.printStackTrace();
           }
       }
   }
  1. 在对比版本时,我们还可以获取本地文件的版本号.
     /**
     * 获取当前应用版本
     */
    public static long getVersionCode(Context context) {
        PackageManager packageManager = null;
        PackageInfo packageInfo = null;
        try {
            packageManager = context.getPackageManager();
            packageInfo = packageManager.getPackageInfo(context.getPackageName(), 0);
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }
        if (packageInfo != null) { //版本适配
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
                return packageInfo.getLongVersionCode();
            } else {
                return packageInfo.versionCode;

            }
        }
        return -1;
    }

4.App应用的安装

通过intent让下载的应用自动安装

 /**
     * 安装应用
     *
     */
    public static void installApk(Activity activity, File apkFile) {
        Intent intent = new Intent();
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        intent.setAction(Intent.ACTION_VIEW);
        Uri uri = null;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            uri = FileProvider.getUriForFile(activity, activity.getPackageName() + ".fileprovider", apkFile);
            intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
            intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
        } else {
            uri = Uri.fromFile(apkFile);
        }
        intent.setDataAndType(uri, "application/vnd.android.package-archive");
        activity.startActivity(intent);
    }

三、遇到的一些问题及解决办法

  1. 存储下载的文件到sd卡报错:Android存储文件到SD卡中
  2. 网络请求控制台报错:java.net.UnknownServiceException: CLEARTEXT communication to 59.110.162.30 not permitted by network
  3. 网络请求控制台报错:java.net.SocketException: socket failed: EPERM (Operation not permitted)
  4. 安装文件时报错:android.os.FileUriExposedException: file:///xxxx exposed beyond app through Intent.getData()

四、源码

https://github.com/wantao666/appUpdate

发布了51 篇原创文章 · 获赞 5 · 访问量 2593

猜你喜欢

转载自blog.csdn.net/qq_45453266/article/details/105184053