Android应用开发:实现获取并显示手机中图片文件夹的功能
本文还有配套的精品资源,点击获取
简介:在Android开发中,实现获取手机中包含图片的文件夹并显示是一个常见的需求。该过程包括获取读取外部存储权限、通过MediaStore和ContentResolver查询图片、将结果展示在界面上等步骤。本代码示例将详细介绍如何使用Android API进行图片管理和用户界面构建,并考虑到Android 6.0及以上版本的运行时权限请求。通过本项目,开发者可以增强在Android平台上的图片管理与应用开发能力。
1. Android权限管理:读取外部存储权限
理解Android权限管理的重要性
在Android系统中,权限管理是保护用户数据和设备安全的关键机制。它允许应用按照最小必要原则请求和使用资源,防止恶意软件滥用权限。特别是对于访问外部存储这样的敏感操作,系统要求开发者必须在应用运行时明确请求用户授权。
读取外部存储权限的必要性
读取外部存储权限(READ_EXTERNAL_STORAGE)是许多应用必备权限之一,尤其是在需要访问图片、音乐或文档等文件的应用中。Android系统从Android 6.0(API 23)开始引入运行时权限模型,应用需要在应用运行时明确请求用户授权,而非仅在安装时。
实现动态权限请求的步骤
为了请求读取外部存储权限,应用开发者需要遵循以下步骤: 1. 在应用的 AndroidManifest.xml
文件中声明权限请求:
- 在代码中使用
ActivityCompat.requestPermissions
方法动态请求权限:
if (ContextCompat.checkSelfPermission(thisActivity, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(thisActivity, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, MY_PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE);}
- 处理用户的权限授权结果,继续执行需要权限的操作或提示用户:
@Overridepublic void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { if (requestCode == MY_PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE) { if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { // 权限被授权,继续执行操作 } else { // 权限被拒绝,适当提示用户 } }}
以上代码块提供了权限请求的基本框架,开发者可以根据应用的具体需求进行调整和优化。
2. 使用ContentResolver查询图片
2.1 MediaStore和图片Uri获取
2.1.1 理解MediaStore和图片Uri的概念
在Android系统中, MediaStore
是一个系统级的内容提供器(Content Provider),用于管理设备上的媒体文件,如图片、音频和视频等。它提供了一个统一的接口,使得应用程序可以在不了解文件实际存储路径的情况下,对媒体文件进行查询、获取和管理。
图片的 Uri
(Uniform Resource Identifier)是一个资源标识符,用于唯一地标识设备上的图片文件。通过图片的 Uri
,应用可以获取图片的具体路径或直接通过 ContentResolver
读取图片数据。
2.1.2 如何通过MediaStore查询图片
要通过 MediaStore
查询图片,首先需要获取 ContentResolver
的实例,然后使用 query
方法来执行查询操作。以下是一个简单的示例代码:
ContentResolver contentResolver = getContentResolver();String[] projection = { MediaStore.Images.Media._ID, // 获取图片ID MediaStore.Images.Media.DISPLAY_NAME // 获取图片名称};String selection = null; // 查询条件,这里为null表示查询所有图片String[] selectionArgs = null; // 查询条件参数,与selection配合使用String sortOrder = MediaStore.Images.Media.DATE_ADDED + \" DESC\"; // 排序方式,这里按添加时间降序排列Cursor cursor = contentResolver.query( MediaStore.Images.Media.EXTERNAL_CONTENT_URI, projection, selection, selectionArgs, sortOrder);if (cursor != null && cursor.moveToFirst()) { do { long id = cursor.getLong(cursor.getColumnIndex(MediaStore.Images.Media._ID)); String displayName = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DISPLAY_NAME)); // 处理获取到的图片ID和名称 } while (cursor.moveToNext()); cursor.close();}
2.1.3 处理查询结果和图片Uri
查询结果通常以 Cursor
的形式返回,开发者需要遍历 Cursor
来处理每一行数据。通过 Cursor
获取到的图片ID可以用来构造图片的 Uri
,如下所示:
Uri imageUri = ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, id);
使用这个 imageUri
,应用就可以通过 ContentResolver
获取图片的具体内容,例如图片的缩略图或者原始数据。
2.2 图片信息的进一步处理
2.2.1 分析图片信息
图片信息包含图片的路径、尺寸、格式等。在Android中,可以通过 BitmapFactory.Options
来获取图片的原始宽度和高度,示例如下:
BitmapFactory.Options options = new BitmapFactory.Options();options.inJustDecodeBounds = true; // 不加载图片,仅获取图片信息BitmapFactory.decodeFile(imagePath, options);int width = options.outWidth;int height = options.outHeight;
2.2.2 图片尺寸、类型处理
在处理图片尺寸和类型时,通常需要根据应用场景来调整图片的分辨率或者进行图片格式的转换。例如,可以将高分辨率的图片压缩为适合特定屏幕的分辨率,以节省内存和存储空间,提高应用性能。
2.2.3 图片排序和筛选策略
根据应用场景,可能需要对图片进行排序或筛选。例如,按照拍摄时间进行排序,或者只选择特定类型或尺寸的图片。这可以通过调整 MediaStore
查询中的 sortOrder
和 selection
参数来实现。
以上,我们完成了对 MediaStore
和图片 Uri
获取的基本介绍,及其查询结果的处理。接下来,我们将进一步探讨如何对图片信息进行更深层次的处理,包括分析图片信息、处理图片尺寸和类型,以及排序和筛选策略。
3. 用户界面构建
3.1 使用RecyclerView展示图片
3.1.1 RecyclerView的基本使用和布局
RecyclerView是一个灵活的视图用于在有限的窗口中显示大量数据集。它通过减少视图的重复创建来提供性能提升。其基本使用涉及几个关键组件:RecyclerView本身,一个适配器(Adapter)以及一个布局管理器(LayoutManager)。
布局方面,RecyclerView可以嵌入到任何布局中,而它自己的布局通常是由一个垂直或水平滚动的LinearLayout或GridLayoutManager来管理。其XML配置通常如下:
在代码中,你需要初始化RecyclerView并设置布局管理器:
val recyclerView = findViewById(R.id.recyclerView)recyclerView.layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)
3.1.2 适配器设计和数据绑定
适配器是连接数据和视图的桥梁。每个RecyclerView都需要一个适配器来提供数据和创建视图。以下是一个简单的适配器的示例代码:
class ImageAdapter(private val images: List) : RecyclerView.Adapter() { class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { val imageView: ImageView = itemView.findViewById(R.id.imageView) } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { val view = LayoutInflater.from(parent.context).inflate(R.layout.item_image, parent, false) return ViewHolder(view) } override fun onBindViewHolder(holder: ViewHolder, position: Int) { val imageUri = images[position] // Assuming you have a method to load the image into the ImageView loadImageIntoImageView(holder.imageView, imageUri) } override fun getItemCount() = images.size}
3.1.3 提高RecyclerView的滚动性能
为了提高滚动性能,应该避免在绑定数据时进行复杂的视图操作。使用ViewHolder模式减少视图查找,确保图片加载不会阻塞主线程(比如使用Glide或Picasso库),并且减少过度绘制。
3.2 使用ListView展示图片
3.2.1 ListView的基本使用和布局
虽然现在推荐使用RecyclerView,但ListView在老版本Android中应用广泛。它的基本使用和布局与RecyclerView类似,但管理起来更复杂。XML布局基本如下:
与RecyclerView相比,ListView的适配器和ViewHolder实现更繁琐。
3.2.2 适配器设计和数据绑定
对于ListView,适配器设计和数据绑定的示例代码可能如下:
class ImageListAdapter(private val images: List) : BaseAdapter() { override fun getCount(): Int = images.size override fun getItem(position: Int): Any = images[position] override fun getItemId(position: Int): Long = position.toLong() override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { val view = convertView ?: LayoutInflater.from(context).inflate(R.layout.item_image, parent, false) val imageView = view.findViewById(R.id.imageView) val imageUri = images[position] // Assuming you have a method to load the image into the ImageView loadImageIntoImageView(imageView, imageUri) return view }}
3.2.3 ListView与RecyclerView性能对比
ListView的性能通常不如RecyclerView,因为它需要为每个列表项创建视图而RecyclerView可以重用视图。此外,ListView没有内建的滚动性能优化机制,所以在处理大量数据集或复杂布局时,RecyclerView是更好的选择。
表格对比ListView与RecyclerView
| 功能/特性 | ListView | RecyclerView | |-----------|----------|-------------| | 性能 | 低于RecyclerView,尤其是当列表项复杂或数据量大时 | 高效,通过ViewHolder模式和布局管理器优化性能 | | 布局管理 | 有限,不支持多布局类型 | 支持多样化的布局管理,如LinearLayoutManager、GridLayoutManager | | 功能扩展 | 增加新功能需要从头开始或重写大量代码 | 通过添加不同的Adapter和LayoutManager实现新功能 | | 内存使用 | 较高,因为它不具备RecyclerView的视图回收机制 | 较低,因为它只回收不可见的视图 | | 数据绑定 | 每次滚动都会创建新的视图 | 只有在视图不可见时才回收和重用视图 |
通过对比,可以看出RecyclerView在大多数情况下是更优的选择,特别是在动态数据和复杂布局方面,它提供了更大的灵活性和性能优势。
4. 图片加载库的使用
在Android开发中,合理有效地加载和展示图片是一个常见的需求。为此,开发者们常常依赖于成熟的图片加载库,以优化内存管理、图片缓存和异步加载等复杂问题。本章将介绍两种广泛使用的图片加载库:Glide和Picasso。我们将从它们的基本使用方法开始,深入了解它们的缓存机制,并通过性能对比来辅助我们选择合适的图片加载库。
4.1 Glide图片加载库介绍
4.1.1 Glide的基本使用方法
Glide是一个强大的图片加载库,可以处理图片的下载、缓存以及展示。它支持常见的图片格式,如JPEG、PNG、WebP、GIF等,并且支持多种数据源,如网络、本地存储和资源等。
Glide的基本使用流程通常包括以下几个步骤:
- 添加Glide依赖到你的项目中。在
build.gradle
文件中加入以下代码:
implementation \'com.github.bumptech.glide:glide:4.11.0\'annotationProcessor \'com.github.bumptech.glide:compiler:4.11.0\'
- 初始化Glide。在你的应用中,通常只需要在应用的入口点(如
Application
类)初始化一次:
if (BuildConfig.DEBUG) { StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() .detectDiskReads() .detectDiskWrites() .detectNetwork() // For network use .penaltyLog() .build()); StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder() .detectLeakedSqlLiteObjects() .detectLeakedClosableObjects() .penaltyLog() .penaltyDeath() .build());}Glide.with(this /* context */) .load(\"http://goo.gl/gEgYUd\") // URL to the image .into(imageView); // ImageView to load the image into
上面的代码加载了一个网络图片,并将其显示在指定的 ImageView
中。 Glide.with()
方法需要一个上下文(通常是Activity或Fragment),它返回一个 RequestBuilder
对象,通过调用 .load()
方法加载图片,最后通过 .into()
方法将图片设置到 ImageView
。
4.1.2 Glide的缓存机制和优化
Glide的缓存机制是它强大性能的关键之一。Glide缓存包括内存缓存和磁盘缓存。
-
内存缓存:Glide默认使用
LruResourceCache
来存储最近使用的资源。它根据应用的内存使用情况动态调整缓存大小。 -
磁盘缓存:Glide默认使用
DiskLruCacheWrapper
来缓存图片到设备的存储。默认情况下,缓存大小是应用可用空间的1%。
优化缓存的方法包括:
- 配置Glide的缓存大小。例如,增加磁盘缓存的大小:
DiskCache diskCache = new InternalCacheDiskCacheFactory(this, 10 * 1024 * 1024);GlideBuilder builder = new GlideBuilder(this) .setDiskCache(diskCache);Glide.init(this, builder);
- 使用特定的缓存策略,比如强制只加载网络图片或只从缓存加载图片:
Glide.with(this /* context */) .load(\"http://goo.gl/gEgYUd\") .onlyRetrieveFromCache(true) // 仅从缓存中加载图片 .into(imageView);// 或者,仅从网络加载图片Glide.with(this /* context */) .load(\"http://goo.gl/gEgYUd\") .onlyRetrieveFromCache(false) // 仅从网络加载图片,忽略缓存 .into(imageView);
- 使用
RequestOptions
来自定义加载过程,例如调整图片大小、指定图片格式等:
Glide.with(this) .load(\"http://goo.gl/gEgYUd\") .apply(new RequestOptions().format(DecodeFormat.PREFER_RGB_565)) .into(imageView);
通过以上方法,开发者可以根据具体的使用场景来优化Glide的性能和资源消耗。
4.2 Picasso图片加载库介绍
4.2.1 Picasso的基本使用方法
Picasso是另一个广受欢迎的图片加载库,由Square公司开发。它的设计目标是使得图片加载尽可能简单,但在简单背后它也提供了丰富的功能。
使用Picasso加载图片的基本步骤如下:
- 添加Picasso依赖到你的项目中:
implementation \'com.squareup.picasso:picasso:2.71828\'
- 使用Picasso加载图片:
Picasso.get() // 适用于单例模式 .load(\"http://goo.gl/gEgYUd\") .into(imageView); // 加载图片到ImageView
Picasso的API设计简洁,易于使用。其 load()
方法可以接受图片的URL、文件、资源ID等多种参数。 into()
方法则负责将加载的图片展示到指定的 ImageView
。
4.2.2 Picasso与Glide的性能对比
在实际应用中,开发者常常会考虑使用哪个库会更加高效。性能对比从以下几个方面展开:
-
内存消耗 :Glide和Picasso都很好地管理内存,Glide通过更细粒度的内存缓存控制可能在内存敏感的应用中表现更好。
-
磁盘缓存 :Glide提供了更丰富的缓存配置选项,而Picasso的磁盘缓存配置则相对简单。
-
API易用性 :Picasso API非常简洁,这使得Picasso在快速原型设计和简单的图片加载场景中更受青睐。Glide的API提供了更多的灵活性和控制,适合更复杂的场景。
-
功能特性 :Glide支持GIF和WebP等格式,Glide也支持GIF图片的加载,但是Picasso不支持。
-
社区和维护 :Glide是Google官方推荐的库,而Picasso则是由活跃的社区支持,两者都非常活跃,但Glide更新频率略高。
在选择图片加载库时,应根据实际需求、团队熟悉度以及性能测试结果来决定。
| 特性 | Glide | Picasso | | --- | --- | --- | | 内存管理 | 更细粒度控制,支持 LruResourceCache
| 较简单 | | 磁盘缓存 | 多种可配置选项 | 简单配置 | | API易用性 | 灵活,但有更多参数 | 简洁明了 | | 功能特性 | 支持GIF、WebP等多种格式 | 不支持GIF | | 社区和维护 | Google官方推荐,更新频率高 | 活跃社区支持 |
最后,无论选择哪一个库,它们都比手动管理图片加载要高效和安全。开发者应根据项目需求和个人喜好做出选择。
5. Android项目结构分析
5.1 build.gradle配置文件解析
5.1.1 build.gradle的作用和基本结构
在Android开发中, build.gradle
文件扮演着至关重要的角色。它是一个Gradle构建脚本,负责定义项目的配置信息、依赖关系、构建类型等。每个模块都会有自己的 build.gradle
文件,位于模块的根目录下。开发者通过编辑这些文件来配置模块的构建逻辑,从而控制构建过程。
基本的 build.gradle
文件包含以下几个部分:
-
apply plugin
:定义使用的插件,如\'com.android.application\'
表示这是一个Android应用程序模块。 -
android
:定义Android构建的配置,比如编译SDK版本、构建工具版本、签名配置等。 -
dependencies
:管理项目的依赖关系,包括远程库和本地模块依赖。 -
repositories
:指定依赖库的来源,如Maven中心仓库、本地文件路径等。
下面是一个典型的Android应用程序模块的 build.gradle
文件示例:
// 应用Gradle插件,指定项目为Android应用程序apply plugin: \'com.android.application\'// Android构建配置块android { // 编译使用的SDK版本 compileSdkVersion 30 // 默认构建工具版本 defaultConfig { applicationId \"com.example.myapp\" minSdkVersion 16 // 应用程序支持的最低SDK版本 targetSdkVersion 30 // 应用程序设计的目标SDK版本 versionCode 1 versionName \"1.0\" // 用于测试的构建类型 testInstrumentationRunner \"androidx.test.runner.AndroidJUnitRunner\" } // 指定签名配置信息 signingConfigs { release { // 密钥库路径、密码等配置 } } // 指定构建类型,常见的有debug和release buildTypes { release { // 打包时的一些配置,如开启混淆、关闭日志等 } } // 指定产品风味,用于支持不同的构建配置 flavorDimensions \"version\" productFlavors { demo {} full {} }}// 依赖管理块dependencies { // 本地模块依赖 implementation project(path: \':mylibrary\', configuration: \'default\') // 远程依赖,如AndroidX、第三方库等 implementation \'androidx.appcompat:appcompat:1.2.0\' implementation \'com.google.android.material:material:1.3.0\' // 测试依赖库 testImplementation \'junit:junit:4.12\' androidTestImplementation \'androidx.test.ext:junit:1.1.2\' androidTestImplementation \'androidx.test.espresso:espresso-core:3.3.0\'}
理解 build.gradle
文件的结构和作用是进行Android项目构建和管理的关键。它直接关系到项目的构建质量、依赖库的更新、版本控制以及最终的打包输出。
5.1.2 依赖管理与版本控制
在 build.gradle
文件中,依赖管理是一个核心部分,它定义了项目需要哪些外部库或者本地模块。这些依赖可能是编译时依赖、运行时依赖或者测试时的依赖。在Gradle中,依赖通过 implementation
、 api
、 testImplementation
等关键字进行声明。
-
implementation
:用于声明一个依赖,这个依赖在编译时有效,但不会被其他模块所见。这意味着它不会传递给依赖当前模块的其他模块。这种方式可以提升构建性能,因为Gradle在构建过程中会减少不必要的重新编译。 -
api
:类似于implementation
,但在其他模块对依赖模块进行API访问时非常有用,因为它允许依赖被传递。 -
testImplementation
:用于测试代码中需要的依赖,这些依赖不会包含在发布构建中。
版本控制是通过在依赖声明中指定库的具体版本号来实现的。Gradle允许开发者对依赖进行动态版本控制,例如使用范围匹配符 +
表示“兼容的最新版本”,或者使用特定版本号。
dependencies { implementation \'com.google.code.gson:gson:2.8.6\' implementation \'com.squareup.retrofit2:retrofit:2.9.0\' implementation \'com.squareup.retrofit2:converter-gson:2.9.0\' implementation \'org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.4.10\' testImplementation \'junit:junit:4.12\'}
在维护一个项目时,开发者需要定期更新依赖库,以利用新功能和安全修复。可以手动更新依赖库版本,或者使用工具如 gradle dependencies
来查看依赖树,并使用命令 gradle dependencies --scan
生成依赖报告,以决定是否更新到最新的依赖库。
依赖库版本冲突是Android开发中常见的问题。开发者需要掌握处理依赖冲突的策略,比如使用 force
语句强制使用特定版本的库,或者自定义依赖关系来解决冲突。
configurations.all { resolutionStrategy { force \'com.example:customlibrary:1.2.3\' }}
总之, build.gradle
文件是Android项目配置的核心,对于项目构建过程的每一个方面都有直接的影响。对 build.gradle
文件的优化和有效管理,可以大大提高项目的开发效率和构建质量。
6. 动态权限请求处理
6.1 Android 6.0权限系统概述
6.1.1 动态权限请求的必要性
在Android 6.0(API 23)之前的版本,应用安装时会提示用户一次性授权所有权限。然而,从Android 6.0开始,Google引入了动态权限请求,允许应用在运行时请求用户授权某些权限。这一变更提高了应用的安全性,降低了应用对用户隐私的侵犯风险。
动态权限请求的引入意味着开发者需要在应用中适配新权限系统,合理处理权限被拒绝的情况。这种处理机制确保用户对自己的数据拥有更精细的控制权,可以决定是否授予某些特定权限。
6.1.2 权限请求的API和流程
在Android中,权限请求使用 ActivityCompat.requestPermissions
方法启动一个权限请求对话框。用户响应后,系统会回调 Activity
的 onRequestPermissionsResult
方法,其中包含用户的选择。
权限请求流程如下: 1. 在运行时检查所需的权限是否已被授予。 2. 如果未被授予,使用 ActivityCompat.shouldShowRequestPermissionRationale
方法检查是否应该向用户解释为什么需要这些权限。 3. 如果需要解释,显示自定义对话框或使用 ActivityCompat.requestPermissions
方法请求权限。 4. 根据用户的选择处理权限请求结果。
6.2 实现动态权限请求
6.2.1 权限请求的最佳实践
最佳实践包括: - 在运行时检查和请求必要的权限。 - 提供清晰的用户提示,解释为何需要该权限。 - 优雅地处理权限被拒绝的情况,确保应用在权限不足的情况下也能正常运行。
6.2.2 案例:实现读取外部存储权限的请求
以下是实现读取外部存储权限请求的代码示例,包括必要的检查和请求流程:
// 检查权限是否已被授予if (ContextCompat.checkSelfPermission(thisActivity, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { // 权限未被授予,请求权限 if (ActivityCompat.shouldShowRequestPermissionRationale(thisActivity, Manifest.permission.READ_EXTERNAL_STORAGE)) { // 向用户解释为何需要此权限 // 可以使用对话框或snackbar进行解释 showPermissionRationale(thisActivity); } else { // 权限未被授予且不需要解释,请求权限 ActivityCompat.requestPermissions(thisActivity, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, MY_PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE); }} else { // 权限已被授予,执行需要权限的操作 readFromExternalStorage();}// 请求权限的结果处理@Overridepublic void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (requestCode == MY_PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE) { // 如果请求被取消,则结果数组为空 if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { // 权限被授予,执行操作 readFromExternalStorage(); } else { // 权限被拒绝,进行处理 handlePermissionDenied(); } }}private void handlePermissionDenied() { // 权限被拒绝的处理逻辑}private void showPermissionRationale(Activity activity) { // 提示用户为何需要此权限}private void readFromExternalStorage() { // 执行读取外部存储的操作}
在这段代码中,我们首先检查 READ_EXTERNAL_STORAGE
权限是否已被授予。如果没有,则根据需要向用户解释为何需要此权限,或直接请求权限。如果权限被授予,我们可以执行读取外部存储的操作。
请注意, MY_PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE
是一个整数常量,用于标识权限请求。 readFromExternalStorage
、 showPermissionRationale
和 handlePermissionDenied
是自定义方法,用于执行相应的逻辑操作。
在实现动态权限请求时,切记要遵循Android的权限最佳实践,确保用户体验的流畅性和应用的安全性。
本文还有配套的精品资源,点击获取
简介:在Android开发中,实现获取手机中包含图片的文件夹并显示是一个常见的需求。该过程包括获取读取外部存储权限、通过MediaStore和ContentResolver查询图片、将结果展示在界面上等步骤。本代码示例将详细介绍如何使用Android API进行图片管理和用户界面构建,并考虑到Android 6.0及以上版本的运行时权限请求。通过本项目,开发者可以增强在Android平台上的图片管理与应用开发能力。
本文还有配套的精品资源,点击获取