首先实现代码
效果,从必应的链接下载一个图片,地址如下
"http://cn.bing.com/az/hprichbg/rb/MoriBuilding_EN-US5143587469_1920x1080.jpg"
private void startDownload(final String downloadUrl) {
permissionCheck();
Observable.create(new ObservableOnSubscribe<Integer>() {
@Override
public void subscribe(ObservableEmitter<Integer> emitter) throws Exception {
InputStream in = null;
RandomAccessFile saveFile = null;
File file = null;
long downloadLength = 0;
String fileName = downloadUrl.substring(downloadUrl.lastIndexOf("/"));
String directory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getPath();
Log.i(TAG, "subscribe: directory="+directory);
file = new File(directory + fileName);
if (file.exists()) {
downloadLength = file.length();
}
long contetnLength;
Request request = new Request.Builder().get().url(downloadUrl).build();
Call call = OkHttpUtils.getInstance().newCall(request);
try {
Response response = call.execute();
contetnLength = response.body().contentLength();
if (contetnLength == 0) {
Log.i(TAG, "startDownload: content null");
} else if (contetnLength == downloadLength) {
Log.i(TAG, "startDownload: downloaded");
}
in = response.body().byteStream();
saveFile = new RandomAccessFile(file, "rw");
saveFile.seek(downloadLength);
byte[] b = new byte[1024];
int total = 0;
int len;
while ((len = in.read(b)) != -1) {
total += len;
saveFile.write(b, 0, len);
int progress = (int) ((total + downloadLength) * 100 / contetnLength);
Log.i(TAG, "subscribe: progress="+progress);
emitter.onNext(progress);
}
response.body().close();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (in != null) {
in.close();
}
if (saveFile != null) {
saveFile.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
emitter.onComplete();
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<Integer>() {
@Override
public void onSubscribe(Disposable d) {
progressBar.setVisibility(View.VISIBLE);
}
@Override
public void onNext(Integer integer) {
progressBar.setProgress(integer);
}
@Override
public void onError(Throwable e) {
}
@Override
public void onComplete() {
progressBar.setVisibility(View.GONE);
}
});
}
控制文件的下载位置
有两种,一个是应用关联缓存目录 /storage/emulated/0/Android/data/com.example.*****/cache
这个目录可以跳过权限的申请
getExternalCacheDir().getPath();
getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS).getPath()
一个是SD卡目录 路径是/storage/emulated/0
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getPath()
在4.4以前,写入SD卡需要在Manifest文件中增加权限
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
在6.0以后需要进行运行时权限申请
if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE)!= PackageManager.PERMISSION_GRANTED){
ActivityCompat.requestPermissions(MainActivity.this,new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},1);
}
下载apk并安装
https://blog.csdn.net/cfy137000/article/details/70257912
https://blog.csdn.net/xqkillua/article/details/79114385
填坑:
下载完成以后,在打开安装包的时候,提示报错
Caused by: java.lang.NullPointerException: Attempt to get length of null array
at com.android.packageinstaller.InstallStart.declaresAppOpPermission(InstallStart.java:134)
at com.android.packageinstaller.InstallStart.onCreate(InstallStart.java:81)
原来,在8.0系统以后,系统设置当中的”允许未知来源”安装应用程序的选项被删除,每次安装第三方应用必须手动开启
https://blog.csdn.net/feibendexiaoma/article/details/80093354
目前增加一条权限即可解决
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
问题2 解析安装包出错
有两种原因:
原因1,权限问题,在安卓7.0之后(版本号24),应用目录内的私有文件访问,必须通过内容提供器包装后生成新的URI来访问,不再允许外部程序访问.
安装程序是通过系统应用installer来打开apk文件,因此,除了通过内容提供器包装URI,还应该给installer添加访问权限
参考https://blog.csdn.net/qq_32452623/article/details/78924113
两种做法
方法1,给intent添加临时权限.注意,addFlag在setFlag之后,值允许出现1次setFlag
install.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
install.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
//查询所有符合 intent 跳转目标应用类型的应用
install.setDataAndType(apkUri, "application/vnd.android.package-archive");
方法2,查找打开apk的活动,然后添加访问权限
install.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
// install.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
//查询所有符合 intent 跳转目标应用类型的应用
install.setDataAndType(apkUri, "application/vnd.android.package-archive");
List<ResolveInfo> resInfoList = mActivity.getPackageManager().queryIntentActivities(install, PackageManager.MATCH_DEFAULT_ONLY);
//然后全部授权
for (ResolveInfo resolveInfo : resInfoList) {
String packageName = resolveInfo.activityInfo.packageName;
mActivity.grantUriPermission(packageName, apkUri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
}
方法2的好处在于引用:
"区别在于 context.grantUriPermission()的临时访问权限不会在数据接收页面关闭时自动取消,只有手动调用 revokeUriPermission() 或者整个应用重新启动,才会失效。"
通过intent临时权限:"这个是 Intent 的一个 Flag 值,会临时授予目标引用对所传递 Uri 的临时访问权限。
这个临时访问权限在setResult()数据接收方 Activity 还在栈中时,都是有效的,如果弹出了,临时访问权限自动取消。"
原因2:
有可能文件还没有下载完毕,就启动了installer打开apk,自然失败.
建议通过延时1-3s再启动安装包比较稳妥