android7.0后文件的访问权限提高了,不能直接使用file://的方式来共享文件了,应该使用
content://URI的方式来共享文件,并使用FileProvider类来授权。
先在AndroidManifest.xml添加权限
<uses-permission android:name="android.permission.DOWNLOAD_WITHOUT_NOTIFICATION" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT" />
首先新建在xml文件夹下新建一个文件,名字随意,我这边新建为file_paths.xml。里面这样写道
<?xml version="1.0" encoding="utf-8"?>
<paths>
<files-path
name="files"
path="/" />
<cache-path
name="cache"
path="/" />
<external-path
name="external"
path="/" />
<external-files-path
name="external_file_path"
path="/" />
<external-cache-path
name="external_cache_path"
path="/" />
<!--<external-media-path
name="external-media-path"
path=""/>-->
<!--
files-path: 该方式提供在应用的内部存储区的文件/子目录的文件。
它对应Context.getFilesDir返回的路径:eg:”/data/data/com.jph.simple/files”。
cache-path: 该方式提供在应用的内部存储区的缓存子目录的文件。
它对应getCacheDir返回的路径:eg:“/data/data/com.jph.simple/cache”;
external-path: 该方式提供在外部存储区域根目录下的文件。
它对应Environment.getExternalStorageDirectory返回的路径:
external-cache-path: 该方式提供在应用的外部存储区根目录的下的文件。
它对应Context#getExternalFilesDir(String) Context.getExternalFilesDir(null)
返回的路径。eg:”/storage/emulated/0/Android/data/com.jph.simple/files”
-->
</paths>
如果我下载的文件放在getFilesDir里,就用cache-path就行。 其中 name="cache"的cache是别名,可以随便自定义的,而path="/" 里的/则表示 允许访问getFilesDir下所有文件。这上面基本所有路径访问全加上去,反正也没什么不好的。接下来就在AndroidManifest.xml的添加application里添加如下内容
<provider
android:name="android.support.v4.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>
androidx的话换成"androidx.core.content.FileProvider"
其中${applicationId}是你app的包名id,android:resource="@xml/file_paths"中的file_paths是你刚才在xml文件夹下新建的文件。
接下来是获取uri
public static Uri getUri(Context context, File file) {
Uri uri;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {//大于7.0时
uri = FileProvider.getUriForFile(context, context.getPackageName() + ".provider", file);
//context.getPackageName() + ".provider"就是你刚才加到AndroidManifest.xml文件里的provider里的android:authorities的值
} else {
uri = Uri.fromFile(file);
}
return uri;
}
不单单app升级安装,以后所有用到uri的地方以后都可以用getUri(Context context, File file) 获取到授权的正确的Uri。最后贴下app下载完后制动弹出安装的代码
private void isAPK(String filePath) {//filePath:安装包的本地地址链接
File file = new File(filePath);
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);//重要,不然安装成功后第一次打开会崩溃
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
// 7.0 以上
Uri apkUri = ContentUriUtil.getUri(activity, file);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.setDataAndType(apkUri, "application/vnd.android.package-archive");
} else {
// 7.0以下
Uri uri = Uri.fromFile(file);
intent.setDataAndType(uri, "application/vnd.android.package-archive");
}
activity.startActivity(intent);
}
题外话,implementation 'com.liulishuo.filedownloader:library:1.6.8',一个挺好用的下载框架
本文参考:https://blog.csdn.net/MingHuang2017/article/details/82830727
用上述框架做的android升级的Helper
import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.support.v7.app.AlertDialog;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import com.eluton.base.BaseApplication;
import com.eluton.bean.gson.UpdateGsonBean;
import com.eluton.url.Api;
import com.eluton.url.OkhttpUtil;
import com.eluton.util.ContentUriUtil;
import com.eluton.util.LogUtil;
import com.eluton.yltdemo.R;
import com.liulishuo.filedownloader.BaseDownloadTask;
import com.liulishuo.filedownloader.FileDownloadLargeFileListener;
import com.liulishuo.filedownloader.FileDownloadSampleListener;
import com.liulishuo.filedownloader.FileDownloader;
import java.io.File;
public class UpdateHelper {
private Activity activity;
private String downUrl;
private final String downPath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/Download/开课通.apk";
public UpdateHelper(Activity activity) {
this.activity = activity;
}
private AlertDialog updateDialog;
private TextView wantTv;
private void showUpdate() {
if (updateDialog == null) {
AlertDialog.Builder builder = new AlertDialog.Builder(activity, R.style.AlertDialog);
View view = LayoutInflater.from(activity).inflate(R.layout.dialog_update, null);
TextView ensure = view.findViewById(R.id.ensure);
TextView cancel = view.findViewById(R.id.cancel);
ImageView warnImg = view.findViewById(R.id.img);
wantTv = view.findViewById(R.id.notice);
if (updateGsonBean != null)
wantTv.setText("新版本:" + updateGsonBean.getAndroidVersionName());
cancel.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (updateDialog != null)
updateDialog.dismiss();
if (TaskId != 0) {
FileDownloader.getImpl().pause(TaskId);
}
}
});
ensure.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
updateApk();
}
});
builder.setView(view);
builder.setCancelable(false);
updateDialog = builder.create();
}
if (!updateDialog.isShowing())
updateDialog.show();
}
private UpdateGsonBean updateGsonBean;
public void checkUpdate(final boolean needTip) {
new Api() {
@Override
public void bindView(OkhttpUtil.HttpBean httpBean, boolean state) {
if (state && httpBean.getCode() == 200) {
updateGsonBean = BaseApplication.gson.fromJson(httpBean.getContent(), UpdateGsonBean.class);
if (Integer.parseInt(updateGsonBean.getAndroidVersion()) > BaseApplication.versionCodes) {//BaseApplication.versionCodes
downUrl = updateGsonBean.getAndroidDownload();
LogUtil.i("url:" + downUrl);
showUpdate();
} else {
if (needTip)
Toast.makeText(activity, "当前已经是最新版本", Toast.LENGTH_SHORT).show();
}
} else {
if (needTip)
Toast.makeText(activity, httpBean.getContent() + "", Toast.LENGTH_SHORT).show();
}
}
}.Update();
}
private int TaskId;
private boolean isUpdate;//第一次更新时删掉旧安装包的标志符,保证下载最新的安装包
private void updateApk() {
if (!isUpdate) {
isUpdate = true;
File file = new File(downPath);
if (file.exists()) {
file.delete();
}
}
BaseDownloadTask task = FileDownloader.getImpl().create(downUrl).setPath(downPath).setListener(new FileDownloadSampleListener() {
@Override
protected void progress(BaseDownloadTask task, int soFarBytes, int totalBytes) {
int num = (int) (soFarBytes * 100 / totalBytes);
wantTv.setText("下载进度:" + num + "%");
}
@Override
protected void error(BaseDownloadTask task, Throwable e) {
Toast.makeText(activity, e.getMessage() + "", Toast.LENGTH_SHORT).show();
}
@Override
protected void completed(BaseDownloadTask task) {
wantTv.setText("下载完成");
installApk();
}
});
TaskId = task.getId();
task.start();
}
private void installApk() {
File file = new File(downPath);
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);//重要,不然安装成功后第一次打开会崩溃
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
// 7.0 以上
Uri apkUri = ContentUriUtil.getUri(activity, file);//
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.setDataAndType(apkUri, "application/vnd.android.package-archive");
} else {
// 7.0以下
Uri uri = Uri.fromFile(file);
intent.setDataAndType(uri, "application/vnd.android.package-archive");
}
activity.startActivity(intent);
}
}
其中自定义了对话框,R.style.AlertDialog和xml视图的内容如下
<style name="AlertDialog" parent="android:style/Theme.Dialog">
<item name="android:background">@android:color/transparent</item>
<item name="android:windowBackground">@android:color/transparent</item>
<item name="android:windowNoTitle">true</item>
</style>
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="315dp"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:background="@drawable/shape_r6_white"
android:orientation="vertical">
<ImageView
android:id="@+id/img"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:scaleType="center"
android:src="@mipmap/live_notice" />
<TextView
android:id="@+id/notice"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginLeft="20dp"
android:layout_marginTop="16dp"
android:layout_marginRight="20dp"
android:layout_marginBottom="16dp"
android:gravity="center"
android:lineSpacingMultiplier="1.2"
android:textColor="@color/black_1e1e1e"
android:textSize="15sp" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@color/green_00b395" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="44dp"
android:orientation="horizontal">
<TextView
android:id="@+id/cancel"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="@drawable/shape_r6lb_white"
android:gravity="center"
android:text="以后再说"
android:textColor="@color/green_00b395"
android:textSize="16sp" />
<TextView
android:id="@+id/ensure"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="@drawable/shape_r6rb_green"
android:gravity="center"
android:text="立即更新"
android:textColor="@color/white"
android:textSize="16sp" />
</LinearLayout>
</LinearLayout>
</RelativeLayout>
image.png
视图的xml中很多属性没写明,反正知道自定义对话框用来进行更新提醒就是,视图什么的自己写个自己喜欢的吧
网友评论