From 9eca197acb241e0de3e77a8ccdf5f1087b43eda6 Mon Sep 17 00:00:00 2001 From: TongTongStudio Date: Sun, 26 Jul 2026 04:36:51 +0800 Subject: [PATCH] =?UTF-8?q?feat(notification):=20=E6=B7=BB=E5=8A=A0=20Andr?= =?UTF-8?q?oid=2013=20=E9=80=9A=E7=9F=A5=E6=9D=83=E9=99=90=E5=92=8C=20Andr?= =?UTF-8?q?oid=2011=20=E5=AD=98=E5=82=A8=E6=9D=83=E9=99=90=E9=80=82?= =?UTF-8?q?=E9=85=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加了 Android O+ 通知渠道创建支持 - 实现了 Android 13 POST_NOTIFICATIONS 权限检查 - 添加了 Android 11+ MANAGE_EXTERNAL_STORAGE 权限适配 - 更新了 AndroidManifest 中的权限配置 - 添加了存储权限请求回调处理逻辑 --- app/src/main/AndroidManifest.xml | 12 +- .../java/com/arialyy/simple/MainActivity.java | 271 +++++++++++------- .../service/DownloadNotification.java | 51 +++- 3 files changed, 220 insertions(+), 114 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 11226f4c..86b0abf2 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -6,9 +6,16 @@ - - + + + + diff --git a/app/src/main/java/com/arialyy/simple/MainActivity.java b/app/src/main/java/com/arialyy/simple/MainActivity.java index 3769f716..8502c8c5 100644 --- a/app/src/main/java/com/arialyy/simple/MainActivity.java +++ b/app/src/main/java/com/arialyy/simple/MainActivity.java @@ -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 { - 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 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 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>() { - @Override public void onChanged(@Nullable List 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>() { + @Override + public void onChanged(@Nullable List 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 { - - Adapter(Context context, List 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版本适配权限检查,兼容分区存储 + *
    + *
  • Android 11+ (API 30+): 需要 {@link android.Manifest.permission#MANAGE_EXTERNAL_STORAGE},跳转系统设置页授权
  • + *
  • Android 6-10 (API 23-29): 使用传统 {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} / {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} 运行时权限
  • + *
  • Android 5.x 及以下 (API < 23): 安装时授权,无需运行时处理
  • + *
+ */ + @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 { - Holder(View itemView) { - super(itemView); - title = findViewById(R.id.title); - desc = findViewById(R.id.desc); - image = findViewById(R.id.image); - } + Adapter(Context context, List 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); + } + } } - } } diff --git a/app/src/main/java/com/arialyy/simple/core/download/service/DownloadNotification.java b/app/src/main/java/com/arialyy/simple/core/download/service/DownloadNotification.java index 1c1e0ee1..6106da51 100644 --- a/app/src/main/java/com/arialyy/simple/core/download/service/DownloadNotification.java +++ b/app/src/main/java/com/arialyy/simple/core/download/service/DownloadNotification.java @@ -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()); } }