0%

安卓版本升级功能

import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;

import androidx.annotation.Nullable;
import androidx.core.content.FileProvider;
import androidx.fragment.app.DialogFragment;

import com.htnova.fly.R;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
public class HtUpdateDialogFragment extends DialogFragment {

private ProgressBar progressBar;
private TextView tvProgress;
private Button btnUpdate, btnSkip, btnCancel;

private volatile boolean isCancelled = false;
private final String apkUrl = "http://115.190.154.26/app-release.apk";
private final int serverVersionCode = 2;
private final int currentVersionCode;

public HtUpdateDialogFragment(Context context) {
currentVersionCode = getVersionCode(context);
}

@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.dialog_update, container, false);
initView(view);
return view;
}

private void initView(View view) {
progressBar = view.findViewById(R.id.progress_bar);
tvProgress = view.findViewById(R.id.tv_progress);
btnUpdate = view.findViewById(R.id.btn_update);
btnSkip = view.findViewById(R.id.btn_skip);
btnCancel = view.findViewById(R.id.btn_cancel);

TextView tvInfo = view.findViewById(R.id.tv_update_info);
tvInfo.setText("当前版本:" + currentVersionCode + "\n服务器版本:" + serverVersionCode +
"\n\n更新内容:\n- 修复问题\n- 优化性能");

btnUpdate.setOnClickListener(v -> {
btnUpdate.setVisibility(View.GONE);
btnSkip.setVisibility(View.GONE);
btnCancel.setVisibility(View.VISIBLE);
progressBar.setVisibility(View.VISIBLE);
tvProgress.setVisibility(View.VISIBLE);
startDownload();
});

btnSkip.setOnClickListener(v -> dismiss());

btnCancel.setOnClickListener(v -> {
isCancelled = true;
Toast.makeText(getContext(), "已取消下载", Toast.LENGTH_SHORT).show();
dismiss();
});
}

private void startDownload() {
new Thread(() -> {
HttpURLConnection conn = null;
InputStream is = null;
BufferedOutputStream bos = null;

try {
URL url = new URL(apkUrl);
conn = (HttpURLConnection) url.openConnection();
conn.setConnectTimeout(10000);
conn.setReadTimeout(10000);
conn.connect();

int length = conn.getContentLength();
if (length <= 0) {
showToast("服务器未返回文件大小");
return;
}

is = new BufferedInputStream(conn.getInputStream());
File apkFile = new File(requireContext().getExternalFilesDir(null), "update.apk");
bos = new BufferedOutputStream(new FileOutputStream(apkFile));

byte[] buffer = new byte[8192]; // 8KB缓冲区
int count = 0;
int bytesRead;
int lastProgress = 0;

while ((bytesRead = is.read(buffer)) != -1 && !isCancelled) {
bos.write(buffer, 0, bytesRead);
count += bytesRead;
int progress = (int) (count * 100L / length);

if (progress != lastProgress) {
int finalProgress = progress;
requireActivity().runOnUiThread(() -> {
progressBar.setProgress(finalProgress);
tvProgress.setText("下载进度:" + finalProgress + "%");
});
lastProgress = progress;
}
}

bos.flush();

if (!isCancelled) {
requireActivity().runOnUiThread(() -> {
Toast.makeText(getContext(), "下载完成,准备安装", Toast.LENGTH_SHORT).show();
installApk(apkFile);
dismiss();
});
}

} catch (Exception e) {
Log.e("HtUpdateDialog", "下载出错:" + e.getMessage(), e);
showToast("下载失败:" + e.getMessage());
} finally {
try {
if (is != null) is.close();
if (bos != null) bos.close();
if (conn != null) conn.disconnect();
} catch (Exception ignored) {
}
}
}).start();
}

private void installApk(File apkFile) {
Context context = requireContext();
Uri apkUri;
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
apkUri = FileProvider.getUriForFile(context, context.getPackageName() + ".fileprovider", apkFile);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
} else {
apkUri = Uri.fromFile(apkFile);
}

intent.setDataAndType(apkUri, "application/vnd.android.package-archive");
context.startActivity(intent);
}

private int getVersionCode(Context context) {
try {
return context.getPackageManager().getPackageInfo(context.getPackageName(), 0).versionCode;
} catch (PackageManager.NameNotFoundException e) {
return 0;
}
}

private void showToast(String msg) {
requireActivity().runOnUiThread(() ->
Toast.makeText(getContext(), msg, Toast.LENGTH_LONG).show());
}
}

R.layout.dialog_update

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:padding="20dp"
android:layout_width="match_parent"
android:layout_height="wrap_content">

<TextView
android:id="@+id/tv_update_info"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="发现新版本..."
android:textSize="16sp" />

<ProgressBar
android:id="@+id/progress_bar"
style="@android:style/Widget.ProgressBar.Horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:max="100"
android:visibility="gone" />

<TextView
android:id="@+id/tv_progress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="下载进度:0%"
android:visibility="gone"
android:paddingTop="10dp" />

<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="20dp">

<Button
android:id="@+id/btn_update"
android:layout_weight="1"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="立即更新" />

<Button
android:id="@+id/btn_skip"
android:layout_weight="1"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="跳过" />

<Button
android:id="@+id/btn_cancel"
android:layout_weight="1"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="取消下载"
android:visibility="gone"/>
</LinearLayout>
</LinearLayout>

application中添加

1
2
3
4
5
6
7
8
9
10
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>

xml文件夹下新建file_paths.xml

1
2
3
4
5
6
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-files-path
name="update_apk"
path="." />
</paths>

使用new HtUpdateDialogFragment(this).show(getSupportFragmentManager(), “update_dialog”);