您的位置:首页 > 理论基础 > 计算机网络

通过okhttp3下载文件实现APP版本更新

2017-09-26 15:04 465 查看
http://blog.csdn.net/qq_34261214/article/details/77124729

做更新的时候要注意7.0版本的路径安全问题,Caused by: android.os.FileUriExposedException

从Android 7.0开始,不再允许在app中把file:// Uri暴露给其他app,否则应用会抛出FileUriExposedException。原因在于,Google认为使用file://
Uri存在一定的风险。比如,文件是私有的,其他app无法访问该文件,或者其他app没有申请READ_EXTERNAL_STORAGE运行时权限。解决方案是,使用FileProvider生成content:// Uri来替代file:// Uri。


概况

思路是这样的,首先在服务器上把已经签名打包的apk放上去,还有一份TXT文件,文件上写着相关的版本号,然后客户端通过对比版本号决定是否下载文件。下载后就打开安装界面安装。 




第一步

把已经签名打包apk和txt文件放上到服务器上,版本号要和txt文件上的描述一致。Android Studio的版本号除了在manifests上,写上code和name(code是面向开发者,name就是用户所看到的版本号),那么我要更新的是2.0版本,那么我应该写2.0,并且要把访问网络和写入数据权限写上 



还要在build.gradle(Module:app)上更改版本号,如果是用eclipse开发就不需要这一步 




 

TXT文件的内容如下,第一行是版本号,第二行版本描述,第三行是apk的下载链接 




第二步

服务器端已经完成,接下来是代码的实现。 

我这里采用okhttp3+mvp模式实现下载文件,如果有对这种模式不清楚的可以看在MVP模式下使用OkHttp3初试OkHttp3实现登录功能

Model层有更新版本描述实例UpdateInfo和Okhttp3的get和post方法

首先在Presenter上有查询版本号和下载apk这两个方法,这两个都是使用Get请求

在View上就需要有一个对应一个触发更新的按钮,显示最新版本的Toast,显示并询问是否更新的Dialog,和显示进度的ProgressDialog,一个跳转到安装界面的操作


Model层

首先要有updateInfo,一个版本信息的实例
public class UpdateInfo
{
private String version;
private String description;
private String url;

public String getVersion()
{
return version;
}
public void setVersion(String version)
{
this.version = version;
}

public String getDescription()
{
return description;
}
public void setDescription(String description)
{
this.description = description;
}

public String getUrl()
{
return url;
}
public void setUrl(String url)
{
this.url = 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
另一个是Modle,包装着okhttp3的post和get方法,这里只需要get请求
/**
* get请求
* @param address
* @param callback
*/

public void get(String address, okhttp3.Callback callback)
{
OkHttpClient client = new OkHttpClient();
FormBody.Builder builder = new FormBody.Builder();
FormBody body = builder.build();
Request request = new Request.Builder()
.url(address)
.build();
client.newCall(request).enqueue(callback);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17


Presenter层

检查是否需要更新
public void updateAPK() {
model = Model.getInstance();
model.get("TXT文件的url",new okhttp3.Callback(){
@Override
public void onFailure(Call call, IOException e) {

}

@Override
public void onResponse(Call call, Response response) throws IOException {
String responseBody = response.body().string();
StringBuffer sb = new StringBuffer();
BufferedReader reader = null;
UpdateInfo updateInfo = new UpdateInfo();
Log.d("版本", "onResponse: "+responseBody);
reader = new BufferedReader(new StringReader(responseBody));
updateInfo.setVersion(reader.readLine());//把版本信息读出来
updateInfo.setDescription(reader.readLine());
updateInfo.setUrl(reader.readLine());
String new_version = updateInfo.getVersion();
Log.d("版本", "onResponse: "+updateInfo.getVersion());
Log.d("版本描述", "onResponse: "+updateInfo.getDescription());
Log.d("更新链接", "onResponse: "+updateInfo.getUrl());
String now_version = "";
try {
PackageManager packageManager = context.getPackageManager();
PackageInfo packageInfo = packageManager.getPackageInfo(context.getPackageName(),0);
now_version = packageInfo.versionName;//获取原版本号
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}

if(new_version.equals(now_version)){
view.showError("");
Log.d("版本号是", "onResponse: "+now_version);
}else{
view.showUpdateDialog(updateInfo);
}
}
});
}
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
下载apk
public void downFile(final String url) {
Log.d("SettingPresenter", "downFile: ");
model = Model.getInstance();
model.get(url,new okhttp3.Callback(){

@Override
public void onFailure(Call call, IOException e) {
view.downFial();
}
@Override
public void onResponse(Call call, Response response) throws IOException {
InputStream is = null;//输入流
FileOutputStream fos = null;//输出流
try {
is = response.body().byteStream();//获取输入流
long total = response.body().contentLength();//获取文件大小
view.setMax(total);//为progressDialog设置大小
if(is != null){
Log.d("SettingPresenter", "onResponse: 不为空");
File file = new File(Environment.getExternalStorageDirectory(),"Earn.apk");// 设置路径
fos = new FileOutputStream(file);
byte[] buf = new byte[1024];
int ch = -1;
int process = 0;
while ((ch = is.read(buf)) != -1) {
fos.write(buf, 0, ch);
process += ch;
view.downLoading(process);       //这里就是关键的实时更新进度了!
}

}
fos.flush();
// 下载完成
if(fos != null){
fos.close();
}
view.downSuccess();
} catch (Exception e) {
view.downFial();
Log.d("SettingPresenter",e.toString());
} finally {
try {
if (is != null)
is.close();
} catch (IOException e) {
}
try {
if (fos != null)
fos.close();
} catch (IOException e) {
}
}
}

//private void down() {
// progressDialog.cancel();
// }
});
}
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


View层

这一层注意的东西很多,例如Android6.0以上等需要在这里设置一下权限,还有Dialog注意关闭,否则出现窗口泄露问题。话不多说,直接上代码
public class MainActivity extends AppCompatActivity {
//下载进度
private ProgressDialog progressDialog;
private Button button;
private SettingPresenter presenter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

presenter = new SettingPresenter(this,this);
button = (Button) findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (Build.VERSION.SDK_INT >= 23) {//如果是6.0以上的
int REQUEST_CODE_CONTACT = 101;
String[] permissions = {Manifest.permission.WRITE_EXTERNAL_STORAGE};
//验证是否许可权限
for (String str : permissions) {
if (MainActivity.this.checkSelfPermission(str) != PackageManager.PERMISSION_GRANTED) {
//申请权限
MainActivity.this.requestPermissions(permissions, REQUEST_CODE_CONTACT);
return;
}
}
}
presenter.updateAPK();

}
});
}

//显示更新错误
public void showError(final String msg) {
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(MainActivity.this, msg, Toast.LENGTH_SHORT).show();
}
});

}

//显示进度条
public void showUpdateDialog(final UpdateInfo updateInfo) {
runOnUiThread(new Runnable() {
@Override
public void run() {
AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
builder.setIcon(android.R.drawable.ic_dialog_info);
builder.setTitle("请升级APP至版本" + updateInfo.getVersion());
builder.setMessage(updateInfo.getDescription());
builder.setCancelable(false);
builder.setPositiveButton("确定", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
if (Environment.getExternalStorageState().equals(
Environment.MEDIA_MOUNTED)) {
downFile(updateInfo.getUrl());
} else {
Toast.makeText(MainActivity.this,"SD卡不可用,请插入SD卡",Toast.LENGTH_LONG).show();
}
}
});
builder.setNegativeButton("取消", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
}
});
builder.create().show();
}
});

}

//下载apk操作
public void downFile(final String url) {
progressDialog = new ProgressDialog(MainActivity.this);    //进度条,在下载的时候实时更新进度,提高用户友好度
progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
progressDialog.setTitle("正在下载");
progressDialog.setMessage("请稍候...");
progressDialog.setProgress(0);
progressDialog.show();
presenter.downFile(url);
Log.d("SettingActivity", "downFile: ");

}

/**
* 进度条实时更新
* @param i
*/
public void downLoading(final int i) {
runOnUiThread(new Runnable() {
@Override
public void run() {
progressDialog.setProgress(i);
}
});
}

/**
* 下载成功
*/

public void downSuccess() {
runOnUiThread(new Runnable() {
@Override
public void run() {

if (progressDialog != null && progressDialog.isShowing())
{
progressDialog.dismiss();
}

AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
builder.setIcon(android.R.drawable.ic_dialog_info);
builder.setTitle("下载完成");
builder.setMessage("是否安装");
builder.setCancelable(false);
builder.setPositiveButton("确定", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Intent intent = new Intent(Intent.ACTION_VIEW);

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { //aandroid N的权限问题
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
Uri contentUri = FileProvider.getUriForFile(MainActivity.this, "对应的包名.fileprovider", new File(Environment.getExternalStorageDirectory(), "软件名.apk"));//注意修改
intent.setDataAndType(contentUri, "application/vnd.android.package-archive");
} else {
intent.setDataAndType(Uri.fromFile(new File(Environment.getExternalStorageDirectory(), "软件名.apk")), "application/vnd.android.package-archive");
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
startActivity(intent);
}
});
builder.setNegativeButton("取消", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
}
});
builder.create().show();

}
});
}

/**
* 下载失败
*/
public void downFial() {
runOnUiThread(new Runnable() {
@Override
public void run() {
progressDialog.cancel();
Toast.makeText(MainActivity.this,"更新失败",Toast.LENGTH_LONG).show();
}
});
}
public void setMax(final long total) {
runOnUiThread(new Runnable() {
@Override
public void run() {
progressDialog.setMax((int) total);
}
});
}
}
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
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174

注意:在跳转到安装界面时,android 6.0即android N以上的需要设置权限。
首先在Manifests加上
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="对应的包名.fileprovider"
android:grantUriPermissions="true"
android:exported="false"
>
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
1
2
3
4
5
6
7
8
9
10
然后在res中创建xml包,存放file_paths.xml文件
<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-path path="Android/data/com.earn/" name="files_root" />
<external-path path="." name="external_storage_root" />
</paths>
1
2
3
4
5
文章所用Demo这里下载
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: