在线更新分为以下几个步骤:
1, 通过接口获取线上版本号,versionCode
2, 比较线上的versionCode 和本地的versionCode,弹出更新窗口
3, 下载APK文件(文件下载)
4,安装APK
首先创建 UpdateDownloadListener 接口类对下载的不同状态回调:
/**
* @desciption: 下载不同状态接口回调
*/
public interface UpdateDownloadListener {
/**
* 下载请求开始回调
*/
void onStart();
/**
* 请求成功,下载前的准备回调
*
* @param contentLength 文件长度
* @param downloadUrl 下载地址
*/
void onPrepared(long contentLength, String downloadUrl);
/**
* 进度更新回调
*
* @param progress 进度
* @param downloadUrl 下载地址
*/
void onProgressChanged(int progress, String downloadUrl);
/**
* 下载暂停回调
*
* @param progress 进度
* @param completeSize 已下载的文件大小
* @param downloadUrl 下载地址
*/
void onPaused(int progress, int completeSize, String downloadUrl);
/**
* 下载完成回调
*
* @param completeSize 已下载的文件大小
* @param downloadUrl 下载地址
*/
void onFinished(int completeSize, String downloadUrl);
/**
* 下载失败回调
*/
void onFailure();
}
然后创建 UpdateDownloadRequest 类负责处理文件的下载和线程间的通信:
/**
* @desciption: 负责处理文件的下载和线程间的通信
*/
public class UpdateDownloadRequest implements Runnable {
/**
* 开始下载的位置
*/
private int startPos = 0;
/**
* 下载地址
*/
private String downloadUrl;
/**
* 文件保存路径
*/
private String localFilePath;
/**
* 事件回调
*/
private UpdateDownloadListener mDownloadListener;
private DownloadResponseHandler mDownloadHandler;
/**
* 下载标志位
*/
private boolean isDownloading = false;
/**
* 文件长度
*/
private long mContentLength;
public UpdateDownloadRequest(String downloadUrl, String localFilePath, UpdateDownloadListener downloadListener) {
this.downloadUrl = downloadUrl;
this.localFilePath = localFilePath;
mDownloadListener = downloadListener;
mDownloadHandler = new DownloadResponseHandler();
isDownloading = true;
}
@Override
public void run() {
try {
makeRequest();
} catch (IOException e) {
if (mDownloadHandler != null) {
mDownloadHandler.sendFailureMessage(FailureCode.IO);
}
} catch (InterruptedException e) {
if (mDownloadHandler != null) {
mDownloadHandler.sendFailureMessage(FailureCode.Interrupted);
}
}
}
/**
* 建立连接
*/
private void makeRequest() throws IOException, InterruptedException {
if (!Thread.currentThread().isInterrupted()) {
try {
URL url = new URL(downloadUrl);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setConnectTimeout(5000);
connection.setRequestMethod("GET");
connection.setRequestProperty("Range", "bytes=" + startPos + "-");
connection.setRequestProperty("Connection", "Keep-Alive");
//阻塞当前线程
connection.connect();
mContentLength = connection.getContentLength();
if (!Thread.currentThread().isInterrupted()) {
if (mDownloadHandler != null) {
//完成文件下载,取得与远程文件的流
mDownloadHandler.sendResponseMessage(connection.getInputStream());
}
}
} catch (IOException e) {
if (!Thread.currentThread().isInterrupted()) {
throw e;
}
}
}
}
public boolean isDownloading() {
return isDownloading;
}
public void stopDownloading() {
isDownloading = false;
}
/**
* 下载过程中所有可能出现的异常情况
*/
public enum FailureCode {
//
UnknownHost,
Socket,
SocketTimeout,
ConnectTimeout,
IO,
HttpResponse,
JSON,
Interrupted
}
/**
* 下载文件,并发送消息和回调接口
*/
public class DownloadResponseHandler {
protected static final int SUCCESS_MESSAGE = 0;
protected static final int FAILURE_MESSAGE = 1;
protected static final int START_MESSAGE = 2;
protected static final int FINISH_MESSAGE = 3;
protected static final int NETWORK_OFF = 4;
protected static final int PROGRESS_CHANGED = 5;
protected static final int PAUSED_MESSAGE = 6;
private Handler mHandler;
private int mCompleteSize = 0;
private int progress = 0;
public DownloadResponseHandler() {
if (Looper.myLooper() != null) {
mHandler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
handleSelfMessage(msg);
}
};
}
}
/**
* 发送暂停消息
*/
private void sendPausedMessage() {
sendMessage(obtainMessage(PAUSED_MESSAGE, null));
}
/**
* 发送失败消息
*/
protected void sendFailureMessage(FailureCode failureCode) {
sendMessage(obtainMessage(FAILURE_MESSAGE, new Object[]{failureCode}));
}
private void sendMessage(Message message) {
if (mHandler != null) {
mHandler.sendMessage(message);
} else {
handleSelfMessage(message);
}
}
private Message obtainMessage(int responseMessage, Object response) {
Message msg;
if (mHandler != null) {
msg = mHandler.obtainMessage(responseMessage, response);
} else {
msg = Message.obtain();
msg.what = responseMessage;
msg.obj = response;
}
return msg;
}
private void handleSelfMessage(Message message) {
Object[] response;
switch (message.what) {
default:
break;
case FAILURE_MESSAGE:
response = (Object[]) message.obj;
handleFailureMessage((FailureCode) response[0]);
break;
case PROGRESS_CHANGED:
response = (Object[]) message.obj;
handleProgressChangedMessage((Integer) response[0]);
break;
case PAUSED_MESSAGE:
handlePausedMessage();
break;
case FINISH_MESSAGE:
onFinish();
break;
}
}
/**
* 失败消息的处理逻辑
*/
protected void handleFailureMessage(FailureCode failureCode) {
onFailure(failureCode);
}
/**
* 进度改变消息的处理逻辑
*/
protected void handleProgressChangedMessage(int progress) {
mDownloadListener.onProgressChanged(progress, "");
}
/**
* 暂停消息的处理逻辑
*/
protected void handlePausedMessage() {
mDownloadListener.onPaused(progress, mCompleteSize, "");
}
/**
* 外部接口完成的回调
*/
public void onFinish() {
mDownloadListener.onFinished(mCompleteSize, "");
}
/**
* 外部接口失败的回调
*/
public void onFailure(FailureCode failureCode) {
mDownloadListener.onFailure();
}
/**
* 文件下载方法,发送各种类型的事件
*/
public void sendResponseMessage(InputStream inputStream) {
//文件读写流
RandomAccessFile randomAccessFile = null;
mCompleteSize = 0;
try {
byte[] buffer = new byte[1024];
int length = -1;
int limit = 0;
randomAccessFile = new RandomAccessFile(localFilePath, "rwd");
randomAccessFile.seek(startPos);
boolean isPaused = false;
while ((length = inputStream.read(buffer)) != -1) {
if (isDownloading) {
randomAccessFile.write(buffer, 0, length);
mCompleteSize += length;
if ((startPos + mCompleteSize) < (mContentLength + startPos)) {
progress = (int) (Float.parseFloat(getToPointFloatStr(
(float) (startPos + mCompleteSize) / (mContentLength + startPos))) * 100);
//限制notification更新频率
if (limit % 30 == 0 || progress == 100) {
//在子线程中读取流数据,后转发到主线程中去。
sendProgressChangedMessage(progress);
}
}
limit++;
} else {
isPaused = true;
sendPausedMessage();
break;
}
}
stopDownloading();
if (!isPaused) {
sendFinishMessage();
}
} catch (IOException e) {
sendPausedMessage();
stopDownloading();
e.printStackTrace();
} finally {
try {
if (inputStream != null) {
inputStream.close();
}
if (randomAccessFile != null) {
randomAccessFile.close();
}
} catch (IOException e) {
stopDownloading();
e.printStackTrace();
}
}
}
/**
* 数字格式化
*/
private String getToPointFloatStr(float value) {
DecimalFormat format = new DecimalFormat("0.00");
return format.format(value);
}
/**
* 发送进度改变消息
*/
private void sendProgressChangedMessage(int progress) {
sendMessage(obtainMessage(PROGRESS_CHANGED, new Object[]{progress}));
}
/**
* 发送完成消息
*/
protected void sendFinishMessage() {
sendMessage(obtainMessage(FINISH_MESSAGE, null));
}
}
}
然后创建 UpdateManager 类下载调度管理器,调用UpdateDownloadRequest:
/**
* @desciption: 下载调度管理器,调用UpdateDownloadRequest
*/
public class UpdateManager {
/**
* 线程池
*/
private ExecutorService mExecutorService;
private UpdateDownloadRequest mDownloadRequest;
private UpdateManager() {
//创建cache线程池
mExecutorService = Executors.newCachedThreadPool();
}
public static UpdateManager getInstance() {
return Holder.INSTANCE;
}
public void startDownload(String downloadUrl, String localFilePath, UpdateDownloadListener listener) {
if (mDownloadRequest != null && mDownloadRequest.isDownloading()) {
return;
}
checkLocalFilePath(localFilePath);
mDownloadRequest = new UpdateDownloadRequest(downloadUrl, localFilePath, listener);
Future> future = mExecutorService.submit(mDownloadRequest);
new WeakReference>(future);
}
/**
* 检查文件路径
*/
private void checkLocalFilePath(String localFilePath) {
File path = new File(localFilePath.substring(0,
localFilePath.lastIndexOf("/") + 1));
File file = new File(localFilePath);
if (!path.exists()) {
path.mkdirs();
}
if (!file.exists()) {
try {
file.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public interface InstallPermissionListener {
/**
* 权限申请成功
*/
void permissionSuccess();
/**
* 权限申请失败
*/
void permissionFail();
}
private static class Holder {
private static final UpdateManager INSTANCE = new UpdateManager();
}
}
最后通过 Service 启动下载,创建 UpdateService 类:
/**
* @desciption: 应用更新组件入口,用来启动下载器并更新Notification
*/
public class UpdateService extends Service {
public static final String APK_URL="apk_url";
/**
* 文件存放路径
*/
private String filePath;
/**
* 文件下载地址
*/
private String apkUrl;
private NotificationUtils mNotificationUtils;
@Override
public void onCreate() {
super.onCreate();
filePath = Environment.getExternalStorageDirectory() + "/videobusiness/videobusiness.apk";
mNotificationUtils = new NotificationUtils(this);
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
apkUrl = intent.getStringExtra(APK_URL);
notifyUser(getString(R.string.update_download_start), 0);
startDownload();
return super.onStartCommand(intent, flags, startId);
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
private void notifyUser(String content, int progress) {
mNotificationUtils.sendNotificationProgress(getString(R.string.app_name), content, progress, progress >= 100 ? getContentIntent() : PendingIntent.getActivity(this, 0,
new Intent(), PendingIntent.FLAG_UPDATE_CURRENT));
}
private void startDownload() {
UpdateManager.getInstance().startDownload(apkUrl, filePath, new UpdateDownloadListener() {
@Override
public void onStart() {
}
@Override
public void onPrepared(long contentLength, String downloadUrl) {
}
@Override
public void onProgressChanged(int progress, String downloadUrl) {
notifyUser(getString(R.string.update_download_processing), progress);
}
@Override
public void onPaused(int progress, int completeSize, String downloadUrl) {
notifyUser(getString(R.string.update_download_failed), progress);
deleteApkFile();
//停止服务自身
stopSelf();
}
@Override
public void onFinished(int completeSize, String downloadUrl) {
notifyUser(getString(R.string.update_download_finish), 100);
//停止服务自身
stopSelf();
startActivity(getInstallApkIntent());
}
@Override
public void onFailure() {
notifyUser(getString(R.string.update_download_failed), 0);
deleteApkFile();
//停止服务自身
stopSelf();
}
});
}
private PendingIntent getContentIntent() {
return PendingIntent.getActivity(this, 0,
getInstallApkIntent(), PendingIntent.FLAG_UPDATE_CURRENT);
}
private Intent getInstallApkIntent() {
File apkFile = new File(filePath);
// 通过Intent安装APK文件
final Intent installIntent = new Intent(Intent.ACTION_VIEW);
installIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
installIntent.addCategory(Intent.CATEGORY_DEFAULT);
//兼容7.0
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
Uri apkUri = FileProvider.getUriForFile(this, getPackageName() + ".fileprovider", apkFile);
installIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
installIntent.setDataAndType(apkUri, "application/vnd.android.package-archive");
//兼容8.0
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
boolean hasInstallPermission = this.getPackageManager().canRequestPackageInstalls();
if (!hasInstallPermission) {
InstallPermissionActivity.sListener = new UpdateManager.InstallPermissionListener() {
@Override
public void permissionSuccess() {
installApk();
}
@Override
public void permissionFail() {
Toast.makeText(UpdateService.this, "授权失败,无法安装应用", Toast.LENGTH_LONG).show();
}
};
Intent intent = new Intent(this, InstallPermissionActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
return intent;
}
}
} else {
installIntent.setDataAndType(Uri.parse("file://" + apkFile.getPath()),
"application/vnd.android.package-archive");
}
return installIntent;
}
/**
* 8.0 权限获取后的安装
*/
private void installApk() {
File apkFile = new File(filePath);
// 通过Intent安装APK文件
Intent installIntent = new Intent(Intent.ACTION_VIEW);
installIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
installIntent.addCategory(Intent.CATEGORY_DEFAULT);
Uri apkUri = FileProvider.getUriForFile(this, getPackageName() + ".fileprovider", apkFile);
installIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
installIntent.setDataAndType(apkUri, "application/vnd.android.package-archive");
startActivity(installIntent);
}
/**
* 删除无用apk文件
*/
private void deleteApkFile() {
File apkFile = new File(filePath);
if (apkFile.exists() && apkFile.isFile()) {
apkFile.delete();
}
}
}
我们是以通知的形式更新下载进度条,下面是封装的 Notification 类:
/**
* @desciption: 通知管理
*/
public class NotificationUtils extends ContextWrapper {
public static final String CHANNEL_ID = "default";
private static final String CHANNEL_NAME = "Default Channel";
private static final String CHANNEL_DESCRIPTION = "this is default channel!";
private NotificationManager mManager;
public NotificationUtils(Context base) {
super(base);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
createNotificationChannel();
}
}
@TargetApi(Build.VERSION_CODES.O)
private void createNotificationChannel() {
NotificationChannel channel = new NotificationChannel(CHANNEL_ID, CHANNEL_NAME, NotificationManager.IMPORTANCE_DEFAULT);
//是否绕过请勿打扰模式
channel.canBypassDnd();
//闪光灯
channel.enableLights(true);
//锁屏显示通知
channel.setLockscreenVisibility(VISIBILITY_SECRET);
//闪关灯的灯光颜色
channel.setLightColor(Color.RED);
//桌面launcher的消息角标
channel.canShowBadge();
//是否允许震动
channel.enableVibration(true);
//获取系统通知响铃声音的配置
channel.getAudioAttributes();
//获取通知取到组
channel.getGroup();
//设置可绕过 请勿打扰模式
channel.setBypassDnd(true);
//设置震动模式
channel.setVibrationPattern(new long[]{100, 100, 200});
//是否会有灯光
channel.shouldShowLights();
getManager().createNotificationChannel(channel);
}
private NotificationManager getManager() {
if (mManager == null) {
mManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
}
return mManager;
}
/**
* 发送通知
*/
public void sendNotification(String title, String content) {
NotificationCompat.Builder builder = getNotification(title, content);
getManager().notify(1, builder.build());
}
private NotificationCompat.Builder getNotification(String title, String content) {
NotificationCompat.Builder builder = null;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
builder = new NotificationCompat.Builder(getApplicationContext(), CHANNEL_ID);
} else {
builder = new NotificationCompat.Builder(getApplicationContext());
builder.setPriority(PRIORITY_DEFAULT);
}
//标题
builder.setContentTitle(title);
//文本内容
builder.setContentText(content);
//小图标
builder.setSmallIcon(R.mipmap.ic_launcher);
//设置点击信息后自动清除通知
builder.setAutoCancel(true);
return builder;
}
/**
* 发送通知
*/
public void sendNotification(int notifyId, String title, String content) {
NotificationCompat.Builder builder = getNotification(title, content);
getManager().notify(notifyId, builder.build());
}
/**
* 发送带有进度的通知
*/
public void sendNotificationProgress(String title, String content, int progress, PendingIntent intent) {
NotificationCompat.Builder builder = getNotificationProgress(title, content, progress, intent);
getManager().notify(0, builder.build());
}
/**
* 获取带有进度的Notification
*/
private NotificationCompat.Builder getNotificationProgress(String title, String content,
int progress, PendingIntent intent) {
NotificationCompat.Builder builder = null;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
builder = new NotificationCompat.Builder(getApplicationContext(), CHANNEL_ID);
} else {
builder = new NotificationCompat.Builder(getApplicationContext());
builder.setPriority(PRIORITY_DEFAULT);
}
//标题
builder.setContentTitle(title);
//文本内容
builder.setContentText(content);
//小图标
builder.setSmallIcon(R.mipmap.ic_launcher);
//设置大图标,未设置时使用小图标代替,拉下通知栏显示的那个图标
//设置大图片 BitmpFactory.decodeResource(Resource res,int id) 根据给定的资源Id解析成位图
builder.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher));
if (progress > 0 && progress < 100) {
//一种是有进度刻度的(false),一种是循环流动的(true)
//设置为false,表示刻度,设置为true,表示流动
builder.setProgress(100, progress, false);
} else {
//0,0,false,可以将进度条隐藏
builder.setProgress(0, 0, false);
builder.setContentText("下载完成");
}
//设置点击信息后自动清除通知
builder.setAutoCancel(true);
//通知的时间
builder.setWhen(System.currentTimeMillis());
//设置点击信息后的跳转(意图)
builder.setContentIntent(intent);
return builder;
}
}
使用Service 在manifest中注册
使用方式
Intent intent = new Intent(mContext, UpdateService.class);
//传递apk下载地址
intent.putExtra(UpdateService.APK_URL, apkurl);
mContext.startService(intent);
7.0 适配
在Android 7.0上,对文件的访问权限作出了修改,不能在使用file://格式的Uri 访问文件 ,Android 7.0提供 FileProvider,应该使用这个来获取apk地址,然后安装apk。如下进行简单的适配:
(1) 在res 目录下,新建一个xml 文件夹,在xml 下面创建一个文件provider_paths文件:
(2) 在AndroidManifest.xml清单文件中申明Provider:
(3) Android 7.0上的文件地址获取:
Uri uri = FileProvider.getUriForFile(context,
"packageName.fileProvider",
new File(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), apkFile));
注意:把上面的 packageName 换成你自己的包名,把 apkFile 换成你自己的 apk 所在的文件。 File apkFile = new File(filePath); filePath 是apk存放路径。
适配Android 8.0:未知来源的应用权限
Android8.0以上,未知来源的应用是不可以通过代码来执行安装的(在sd卡中找找到apk,手动安装是可以的),未知应用安装权限的开关被除掉,取而代之的是未知来源应用的管理列表,需要列表里面开启你的应用的未知来源的安装权限。Google这么做是为了防止一开始正经的应用后来开始通过升级来做一些不合法的事情,侵犯用户权益。
1) 在清单文件中申明权限:REQUEST_INSTALL_PACKAGES
(2) 在代码中判断用户是否已经受过权限了,如果已经授权,可以直接安装,如果没有授权,则跳转到授权列表,让用户开启未知来源应用安装权限,开启后,再安装应用。
//兼容8.0
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
boolean hasInstallPermission = this.getPackageManager().canRequestPackageInstalls();
if (!hasInstallPermission) {
InstallPermissionActivity.sListener = new UpdateManager.InstallPermissionListener() {
@Override
public void permissionSuccess() {
installApk();
}
@Override
public void permissionFail() {
Toast.makeText(UpdateService.this, "授权失败,无法安装应用", Toast.LENGTH_LONG).show();
}
};
Intent intent = new Intent(this, InstallPermissionActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
return intent;
}
}
因为授权时需要弹框提示,我们用一个Activity来代理创建了一个Activity:InstallPermissionActivity 来申请权限,用户点击设置后,跳转到权限设置界面,然后我们再onActivityResult 里判断是都授权成功。
InstallPermissionActivity 代码如下:
/**
* @desciption: 兼容Android 8。0 APP 在线更新,权限申请界面
*/
public class InstallPermissionActivity extends BaseActivity {
public static final int INSTALL_PACKAGES_REQUEST_CODE = 1;
public static UpdateManager.InstallPermissionListener sListener;
private AlertDialog mAlertDialog;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 弹窗
if (Build.VERSION.SDK_INT >= 26) {
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.REQUEST_INSTALL_PACKAGES}, INSTALL_PACKAGES_REQUEST_CODE);
} else {
finish();
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
switch (requestCode) {
default:
break;
case INSTALL_PACKAGES_REQUEST_CODE:
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
if (sListener != null) {
sListener.permissionSuccess();
finish();
}
} else {
//startInstallPermissionSettingActivity();
showDialog();
}
break;
}
}
private void showDialog() {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.app_name);
builder.setMessage("为了正常升级 xxx APP,请点击设置按钮,允许安装未知来源应用,本功能只限用于 xxx APP版本升级");
builder.setPositiveButton("设置", new DialogInterface.OnClickListener() {
@RequiresApi(api = Build.VERSION_CODES.O)
@Override
public void onClick(DialogInterface dialogInterface, int i) {
startInstallPermissionSettingActivity();
mAlertDialog.dismiss();
}
});
builder.setNegativeButton("取消", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
if (sListener != null) {
sListener.permissionFail();
}
mAlertDialog.dismiss();
finish();
}
});
mAlertDialog = builder.create();
mAlertDialog.setCancelable(false);
mAlertDialog.show();
}
@RequiresApi(api = Build.VERSION_CODES.O)
private void startInstallPermissionSettingActivity() {
//注意这个是8.0新API
Intent intent = new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES, Uri.parse("package:" + getPackageName()));
startActivityForResult(intent, 1);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == INSTALL_PACKAGES_REQUEST_CODE && resultCode == RESULT_OK) {
// 授权成功
if (sListener != null) {
sListener.permissionSuccess();
}
} else {
// 授权失败
if (sListener != null) {
sListener.permissionFail();
}
}
finish();
}
@Override
protected void onDestroy() {
super.onDestroy();
sListener = null;
}
}
注意:当通过Intent 跳转到未知应用授权列表的时候,一定要加上包名,这样就能直接跳转到你的app下,不然只能跳转到列表。
@RequiresApi(api = Build.VERSION_CODES.O)
private void startInstallPermissionSettingActivity() {
//注意这个是8.0新API
Intent intent = new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES, Uri.parse("package:" + getPackageName()));
startActivityForResult(intent, 1);
}
这里把 InstallPermissionActivity 设置为透明:
#00000000