feat(notification): 添加 Android 13 通知权限和 Android 11 存储权限适配
- 添加了 Android O+ 通知渠道创建支持 - 实现了 Android 13 POST_NOTIFICATIONS 权限检查 - 添加了 Android 11+ MANAGE_EXTERNAL_STORAGE 权限适配 - 更新了 AndroidManifest 中的权限配置 - 添加了存储权限请求回调处理逻辑
This commit is contained in:
@@ -6,9 +6,16 @@
|
||||
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
<uses-permission
|
||||
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||
android:maxSdkVersion="29" />
|
||||
<uses-permission
|
||||
android:name="android.permission.READ_EXTERNAL_STORAGE"
|
||||
android:maxSdkVersion="29" />
|
||||
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
|
||||
tools:ignore="ScopedStorage" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
|
||||
<application
|
||||
android:name="com.arialyy.simple.base.BaseApplication"
|
||||
@@ -16,6 +23,7 @@
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:networkSecurityConfig="@xml/network_security_config"
|
||||
android:requestLegacyExternalStorage="true"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/AppTheme.NoActionBar"
|
||||
tools:ignore="GoogleAppIndexingWarning">
|
||||
|
||||
@@ -17,11 +17,17 @@
|
||||
package com.arialyy.simple;
|
||||
|
||||
import android.Manifest;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Environment;
|
||||
import android.provider.Settings;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.widget.AppCompatImageView;
|
||||
import androidx.lifecycle.Observer;
|
||||
@@ -29,6 +35,7 @@ import androidx.lifecycle.ViewModelProviders;
|
||||
import androidx.recyclerview.widget.DividerItemDecoration;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.arialyy.frame.permission.OnPermissionCallback;
|
||||
import com.arialyy.frame.permission.PermissionManager;
|
||||
import com.arialyy.frame.util.show.T;
|
||||
@@ -49,6 +56,7 @@ import com.arialyy.simple.core.upload.SFtpUploadActivity;
|
||||
import com.arialyy.simple.databinding.ActivityMainBinding;
|
||||
import com.arialyy.simple.modlue.CommonModule;
|
||||
import com.arialyy.simple.to.NormalTo;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -57,130 +65,175 @@ import java.util.List;
|
||||
* 首页
|
||||
*/
|
||||
public class MainActivity extends BaseActivity<ActivityMainBinding> {
|
||||
public static final String KEY_MAIN_DATA = "KEY_MAIN_DATA";
|
||||
public static final String KEY_MAIN_DATA = "KEY_MAIN_DATA";
|
||||
private static final int REQUEST_MANAGE_EXTERNAL_STORAGE = 1001;
|
||||
|
||||
@Override protected void init(Bundle savedInstanceState) {
|
||||
super.init(savedInstanceState);
|
||||
setSupportActionBar(mBar);
|
||||
mBar.setTitle("Aria Demo");
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(false);
|
||||
@Override
|
||||
protected void init(Bundle savedInstanceState) {
|
||||
super.init(savedInstanceState);
|
||||
setSupportActionBar(mBar);
|
||||
mBar.setTitle("Aria Demo");
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(false);
|
||||
|
||||
getBinding().list.setLayoutManager(new LinearLayoutManager(this));
|
||||
final List<NormalTo> data = new ArrayList<>();
|
||||
final Adapter adapter = new Adapter(this, data);
|
||||
getBinding().list.setAdapter(adapter);
|
||||
getBinding().list.addItemDecoration(
|
||||
new DividerItemDecoration(this, DividerItemDecoration.VERTICAL));
|
||||
getBinding().list.setLayoutManager(new LinearLayoutManager(this));
|
||||
final List<NormalTo> data = new ArrayList<>();
|
||||
final Adapter adapter = new Adapter(this, data);
|
||||
getBinding().list.setAdapter(adapter);
|
||||
getBinding().list.addItemDecoration(
|
||||
new DividerItemDecoration(this, DividerItemDecoration.VERTICAL));
|
||||
|
||||
final CommonModule module = ViewModelProviders.of(this).get(CommonModule.class);
|
||||
module.getMainData(this).observe(this, new Observer<List<NormalTo>>() {
|
||||
@Override public void onChanged(@Nullable List<NormalTo> normalTos) {
|
||||
if (normalTos != null) {
|
||||
data.addAll(normalTos);
|
||||
adapter.notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
RvItemClickSupport.addTo(getBinding().list).setOnItemClickListener(
|
||||
new RvItemClickSupport.OnItemClickListener() {
|
||||
@Override public void onItemClicked(RecyclerView recyclerView, int position, View v) {
|
||||
switch (position) {
|
||||
case 0:
|
||||
module.startNextActivity(MainActivity.this, data.get(position),
|
||||
DownloadActivity.class);
|
||||
break;
|
||||
case 1:
|
||||
module.startNextActivity(MainActivity.this, data.get(position),
|
||||
HttpUploadActivity.class);
|
||||
break;
|
||||
case 2:
|
||||
module.startNextActivity(MainActivity.this, data.get(position),
|
||||
DownloadGroupActivity.class);
|
||||
break;
|
||||
case 3:
|
||||
module.startNextActivity(MainActivity.this, data.get(position),
|
||||
FtpDownloadActivity.class);
|
||||
break;
|
||||
case 4:
|
||||
module.startNextActivity(MainActivity.this, data.get(position),
|
||||
FTPDirDownloadActivity.class);
|
||||
break;
|
||||
case 5:
|
||||
module.startNextActivity(MainActivity.this, data.get(position),
|
||||
FtpUploadActivity.class);
|
||||
break;
|
||||
case 6:
|
||||
module.startNextActivity(MainActivity.this, data.get(position),
|
||||
M3U8VodDLoadActivity.class);
|
||||
break;
|
||||
case 7:
|
||||
module.startNextActivity(MainActivity.this, data.get(position),
|
||||
M3U8LiveDLoadActivity.class);
|
||||
break;
|
||||
case 8: // d_sftp
|
||||
module.startNextActivity(MainActivity.this, data.get(position),
|
||||
SFtpDownloadActivity.class);
|
||||
break;
|
||||
case 9: // u_sftp
|
||||
module.startNextActivity(MainActivity.this, data.get(position),
|
||||
SFtpUploadActivity.class);
|
||||
break;
|
||||
final CommonModule module = ViewModelProviders.of(this).get(CommonModule.class);
|
||||
module.getMainData(this).observe(this, new Observer<List<NormalTo>>() {
|
||||
@Override
|
||||
public void onChanged(@Nullable List<NormalTo> normalTos) {
|
||||
if (normalTos != null) {
|
||||
data.addAll(normalTos);
|
||||
adapter.notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
boolean hasPermission = PermissionManager.getInstance()
|
||||
.checkPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE);
|
||||
if (!hasPermission) {
|
||||
PermissionManager.getInstance().requestPermission(this, new OnPermissionCallback() {
|
||||
@Override public void onSuccess(String... permissions) {
|
||||
}
|
||||
RvItemClickSupport.addTo(getBinding().list).setOnItemClickListener(
|
||||
new RvItemClickSupport.OnItemClickListener() {
|
||||
@Override
|
||||
public void onItemClicked(RecyclerView recyclerView, int position, View v) {
|
||||
switch (position) {
|
||||
case 0:
|
||||
module.startNextActivity(MainActivity.this, data.get(position),
|
||||
DownloadActivity.class);
|
||||
break;
|
||||
case 1:
|
||||
module.startNextActivity(MainActivity.this, data.get(position),
|
||||
HttpUploadActivity.class);
|
||||
break;
|
||||
case 2:
|
||||
module.startNextActivity(MainActivity.this, data.get(position),
|
||||
DownloadGroupActivity.class);
|
||||
break;
|
||||
case 3:
|
||||
module.startNextActivity(MainActivity.this, data.get(position),
|
||||
FtpDownloadActivity.class);
|
||||
break;
|
||||
case 4:
|
||||
module.startNextActivity(MainActivity.this, data.get(position),
|
||||
FTPDirDownloadActivity.class);
|
||||
break;
|
||||
case 5:
|
||||
module.startNextActivity(MainActivity.this, data.get(position),
|
||||
FtpUploadActivity.class);
|
||||
break;
|
||||
case 6:
|
||||
module.startNextActivity(MainActivity.this, data.get(position),
|
||||
M3U8VodDLoadActivity.class);
|
||||
break;
|
||||
case 7:
|
||||
module.startNextActivity(MainActivity.this, data.get(position),
|
||||
M3U8LiveDLoadActivity.class);
|
||||
break;
|
||||
case 8: // d_sftp
|
||||
module.startNextActivity(MainActivity.this, data.get(position),
|
||||
SFtpDownloadActivity.class);
|
||||
break;
|
||||
case 9: // u_sftp
|
||||
module.startNextActivity(MainActivity.this, data.get(position),
|
||||
SFtpUploadActivity.class);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@Override public void onFail(String... permissions) {
|
||||
T.showShort(MainActivity.this, "没有文件读写权限");
|
||||
finish();
|
||||
}
|
||||
}, Manifest.permission.WRITE_EXTERNAL_STORAGE,Manifest.permission.READ_EXTERNAL_STORAGE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override protected int setLayoutId() {
|
||||
return R.layout.activity_main;
|
||||
}
|
||||
|
||||
private static class Adapter extends AbsRVAdapter<NormalTo, Adapter.Holder> {
|
||||
|
||||
Adapter(Context context, List<NormalTo> data) {
|
||||
super(context, data);
|
||||
checkPermission();
|
||||
}
|
||||
|
||||
@Override protected Holder getViewHolder(View convertView, int viewType) {
|
||||
return new Holder(convertView);
|
||||
@Override
|
||||
protected int setLayoutId() {
|
||||
return R.layout.activity_main;
|
||||
}
|
||||
|
||||
@Override protected int setLayoutId(int type) {
|
||||
return R.layout.item_main;
|
||||
/**
|
||||
* Android版本适配权限检查,兼容分区存储
|
||||
* <ul>
|
||||
* <li>Android 11+ (API 30+): 需要 {@link android.Manifest.permission#MANAGE_EXTERNAL_STORAGE},跳转系统设置页授权</li>
|
||||
* <li>Android 6-10 (API 23-29): 使用传统 {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} / {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} 运行时权限</li>
|
||||
* <li>Android 5.x 及以下 (API < 23): 安装时授权,无需运行时处理</li>
|
||||
* </ul>
|
||||
*/
|
||||
@SuppressLint("InlinedApi")
|
||||
private void checkPermission() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
// Android 11+:使用 MANAGE_EXTERNAL_STORAGE 获取所有文件访问权限
|
||||
if (!Environment.isExternalStorageManager()) {
|
||||
Intent intent = new Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION);
|
||||
intent.setData(Uri.parse("package:" + getPackageName()));
|
||||
startActivityForResult(intent, REQUEST_MANAGE_EXTERNAL_STORAGE);
|
||||
}
|
||||
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
// Android 6-10:传统存储权限
|
||||
boolean hasPermission = PermissionManager.getInstance()
|
||||
.checkPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE);
|
||||
if (!hasPermission) {
|
||||
PermissionManager.getInstance().requestPermission(this, new OnPermissionCallback() {
|
||||
@Override
|
||||
public void onSuccess(String... permissions) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFail(String... permissions) {
|
||||
T.showShort(MainActivity.this, "没有文件读写权限");
|
||||
finish();
|
||||
}
|
||||
}, Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override protected void bindData(Holder holder, int position, NormalTo item) {
|
||||
holder.title.setText(item.title);
|
||||
holder.desc.setText(item.desc);
|
||||
holder.image.setImageResource(item.icon);
|
||||
@SuppressLint("InlinedApi")
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
if (requestCode == REQUEST_MANAGE_EXTERNAL_STORAGE) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
if (!Environment.isExternalStorageManager()) {
|
||||
T.showShort(MainActivity.this, "需要授予所有文件管理权限");
|
||||
finish();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class Holder extends AbsHolder {
|
||||
TextView title, desc;
|
||||
AppCompatImageView image;
|
||||
private static class Adapter extends AbsRVAdapter<NormalTo, Adapter.Holder> {
|
||||
|
||||
Holder(View itemView) {
|
||||
super(itemView);
|
||||
title = findViewById(R.id.title);
|
||||
desc = findViewById(R.id.desc);
|
||||
image = findViewById(R.id.image);
|
||||
}
|
||||
Adapter(Context context, List<NormalTo> data) {
|
||||
super(context, data);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Holder getViewHolder(View convertView, int viewType) {
|
||||
return new Holder(convertView);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int setLayoutId(int type) {
|
||||
return R.layout.item_main;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void bindData(Holder holder, int position, NormalTo item) {
|
||||
holder.title.setText(item.title);
|
||||
holder.desc.setText(item.desc);
|
||||
holder.image.setImageResource(item.icon);
|
||||
}
|
||||
|
||||
private static class Holder extends AbsHolder {
|
||||
TextView title, desc;
|
||||
AppCompatImageView image;
|
||||
|
||||
Holder(View itemView) {
|
||||
super(itemView);
|
||||
title = findViewById(R.id.title);
|
||||
desc = findViewById(R.id.desc);
|
||||
image = findViewById(R.id.image);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,9 +16,15 @@
|
||||
|
||||
package com.arialyy.simple.core.download.service;
|
||||
|
||||
import android.Manifest;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.NotificationManager;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Build;
|
||||
import androidx.core.app.NotificationCompat;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import com.arialyy.simple.R;
|
||||
|
||||
/**
|
||||
@@ -31,6 +37,28 @@ public class DownloadNotification {
|
||||
private NotificationCompat.Builder mBuilder;
|
||||
private static final int mNotifiyId = 0;
|
||||
|
||||
private static final String CHANNEL_ID = "aria_download_channel";
|
||||
private static final String CHANNEL_NAME = "下载任务";
|
||||
|
||||
/**
|
||||
* 在 Application.onCreate() 中调用,确保通知渠道在发通知之前已创建。
|
||||
*/
|
||||
public static void createNotificationChannel(Context context) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
NotificationChannel channel = new NotificationChannel(
|
||||
CHANNEL_ID,
|
||||
CHANNEL_NAME,
|
||||
NotificationManager.IMPORTANCE_LOW
|
||||
);
|
||||
channel.setDescription("Aria下载进度通知");
|
||||
NotificationManager manager = (NotificationManager) context.getSystemService(
|
||||
Context.NOTIFICATION_SERVICE);
|
||||
if (manager != null) {
|
||||
manager.createNotificationChannel(channel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public DownloadNotification(Context context) {
|
||||
mContext = context;
|
||||
init();
|
||||
@@ -38,17 +66,34 @@ public class DownloadNotification {
|
||||
|
||||
private void init() {
|
||||
mManager = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
mBuilder = new NotificationCompat.Builder(mContext);
|
||||
// API 26+ 使用带 channelId 的构造函数
|
||||
mBuilder = new NotificationCompat.Builder(mContext, CHANNEL_ID);
|
||||
mBuilder.setContentTitle("Aria Download Test")
|
||||
.setContentText("进度条")
|
||||
.setProgress(100, 0, false)
|
||||
.setSmallIcon(R.mipmap.ic_launcher);
|
||||
mManager.notify(mNotifiyId, mBuilder.build());
|
||||
notifyIfPermitted();
|
||||
}
|
||||
|
||||
public void upload(int progress){
|
||||
public void upload(int progress) {
|
||||
if (mBuilder != null) {
|
||||
mBuilder.setProgress(100, progress, false);
|
||||
notifyIfPermitted();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Android 13+ 需要 POST_NOTIFICATIONS 运行时权限,缺少权限时静默跳过通知。
|
||||
*/
|
||||
@SuppressLint("MissingPermission")
|
||||
private void notifyIfPermitted() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
if (ContextCompat.checkSelfPermission(mContext, Manifest.permission.POST_NOTIFICATIONS)
|
||||
!= PackageManager.PERMISSION_GRANTED) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (mManager != null) {
|
||||
mManager.notify(mNotifiyId, mBuilder.build());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user