
文章目录
一、主要流程
二、主要知识点
1.利用OkHttpClient实现网络请求(包括下载和取消下载)
访问http://59.110.162.30/app_updater_version.json
,得到更新的apk版本信息.
- 定义接口包含所需的方法
/**
* 提供网络接口进行调用:
* 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);
}
- 具体的网络请求实现类使用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
- 在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>
- 继承
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值相同,可以认为这两个文件完全相同.
- 获取本地文件的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();
}
}
}
- 在对比版本时,我们还可以获取本地文件的版本号.
/**
* 获取当前应用版本
*/
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);
}
三、遇到的一些问题及解决办法
- 存储下载的文件到sd卡报错:Android存储文件到SD卡中
- 网络请求控制台报错:java.net.UnknownServiceException: CLEARTEXT communication to 59.110.162.30 not permitted by network
- 网络请求控制台报错:java.net.SocketException: socket failed: EPERM (Operation not permitted)
- 安装文件时报错:android.os.FileUriExposedException: file:///xxxx exposed beyond app through Intent.getData()