import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.provider.Settings;
import androidx.annotation.RequiresApi;
import androidx.core.content.FileProvider;
import java.io.File;
public class AppInstallUtils {
private Activity mAct;
private String mPath;//下载下来后文件的路径
public static int UNKNOWN_CODE = 2018;
public AppInstallUtils(Activity mAct, String mPath) {
this.mAct = mAct;
this.mPath = mPath;
}
public void install() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) startInstallO();
else startInstallN();
}
/**
* android1.x-6.x
*/
private void startInstall() {
Intent install = new Intent(Intent.ACTION_VIEW);
install.setDataAndType(Uri.parse("file://" + mPath), "application/vnd.android.package-archive");
install.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mAct.startActivity(install);
}
/**
* android7.x
*/
private void startInstallN() {
//参数1 上下文, 参数2 在AndroidManifest中的android:authorities值, 参数3 共享的文件
Uri apkUri = FileProvider.getUriForFile(mAct, mAct.getApplicationContext().getPackageName() + ".fileprovider", new File(mPath));
Intent install = new Intent(Intent.ACTION_VIEW);
//由于没有在Activity环境下启动Activity,设置下面的标签
install.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
//添加这一句表示对目标应用临时授权该Uri所代表的文件
install.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
install.setDataAndType(apkUri, "application/vnd.android.package-archive");
mAct.startActivity(install);
}
/**
* android8.x
*/
@RequiresApi(api = Build.VERSION_CODES.O)
private void startInstallO() {
boolean isGranted = mAct.getPackageManager().canRequestPackageInstalls();
if (isGranted) startInstallN();//安装应用的逻辑(写自己的就可以)
else new AlertDialog.Builder(mAct)
.setCancelable(false)
.setTitle("安装应用需要打开未知来源权限,请去设置中开启权限")
.setPositiveButton("确定", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface d, int w) {
Intent intent = new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES);
mAct.startActivityForResult(intent, UNKNOWN_CODE);
}
})
.show();
}
}
public static void openApp(Activity activity,String packageName){
Intent launchIntent = activity.getPackageManager().getLaunchIntentForPackage(packageName);
if (launchIntent != null) {
activity.startActivity(launchIntent);
} else {
// 应用未安装或包名无效
Toast.makeText(activity, "应用未安装或包名无效", Toast.LENGTH_SHORT).show();
}
}
Android7及以上对文件权限
的管控抓的很严格。
需要在AndroidManifest.xml
里面对它进行声明一个ContentProvider
。
由于FileProvider提供了ContentURI的生成方法,所以我们无需在代码中定义写一个它的子类
name
属性是固定的。
authorities
可以自己定义,一般是包名字加上.fileprovider
。
exported
设置为false
,因为通常是拒绝外部直接访问的。
grantUriPermissions
需要为true
,需要授予临时的Uri权限。
<manifest>
<application>
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="cn.psvmc.myapp.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_path" />
</provider>
</application>
</manifest>
假如我们APP的包名是cn.psvmc.myapp
,其中的android:authorities
就在包名的基础上添加.fileprovider
。
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="cn.psvmc.myapp.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_path" />
</provider>
file_path.xml
需要建立在res目录下名为xml的目录下,xml目录需要自己建立。
paths
下可以包含一个或者多个子节点。
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<files-path
name="files"
path="images/" />
<cache-path
name="cache"
path="." />
<external-path
name="external"
path="." />
<external-files-path
name="external-files"
path="." />
<external-cache-path
name="external-cache"
path="." />
<external-media-path
name="external-media"
path="." />
</paths>
这个xml的作用在于为文件生成URI,
其中root-path
、files-path
、cache-path
这些标签代表父路径:
File("/")
Context.getFilesDir()
context.getCacheDir()
Environment.getExternalStorageDirectory()
ContextCompat.getExternalFilesDirs(context, null)[0]
ContextCompat.getExternalCacheDirs(context)[0]
context.getExternalMediaDirs()[0]
path属性代表子路径,name代表为”父路径/子路径”起的名字,
<files-path name="files" path="images/" />
创建file_paths.xml
文件
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<!--定义APP的存放目录-->
<external-path
name="AppInstaller"
path="/Download"></external-path>
</paths>
我们还可以在path中用.
代替所有目录。
//文件路径
File file = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getCanonicalPath() + "/apps/MyApp.apk");
//获取文件对应的content类型Uri
Uri uri = FileProvider.getUriForFile(this, "cn.psvmc.myapp.fileprovider", file);
观察我们生成的Uri示例,上边是我们普通的fileUri下边是我们生成的ContentUri,区别就在于ContentUri没有暴露具体的文件路径。
fileUri地址构成
file://
+文件的全路径
ContentUri地址构成
content://
+android:authorities的值
/paths中匹配的名称
/应用名称
例如:
//普通的fileUri(通过Uri.fromFile(file)获取)
file:///storage/emulated/0/Download/apps/MyApp.apk
//contentUri
content://cn.psvmc.myapp.fileprovider/AppInstaller/apps/MyApp.apk
//文件路径
File file = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getCanonicalPath() + "/MyApp.apk");
Intent intent = new Intent(Intent.ACTION_VIEW);
//获取文件对应的content类型Uri
Uri uri = FileProvider.getUriForFile(this, "cn.psvmc.myapp.fileprovider", file);
intent.setDataAndType(uri, "application/vnd.android.package-archive");
//intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);//可以不加
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivity(intent);