GVKun编程网logo

一起来看看 Android 官推 kotlin-first 的图片加载库(android kotlin flow)

36

如果您想了解一起来看看Android官推kotlin-first的图片加载库的相关知识,那么本文是一篇不可错过的文章,我们将对androidkotlinflow进行全面详尽的解释,并且为您提供关于an

如果您想了解一起来看看 Android 官推 kotlin-first 的图片加载库的相关知识,那么本文是一篇不可错过的文章,我们将对android kotlin flow进行全面详尽的解释,并且为您提供关于android -------- Retrofit + RxJava2.0 + Kotlin + MVP 开发的 WanAndroid 项目、Android 11 来袭,一起来看看怎么适配、Android Fresco图片加载库基础使用详解、Android Glide图片加载(加载监听、加载动画)的有价值的信息。

本文目录一览:

一起来看看 Android 官推 kotlin-first 的图片加载库(android kotlin flow)

一起来看看 Android 官推 kotlin-first 的图片加载库(android kotlin flow)

Coil 是一个非常年轻的图片加载库,在 2020 年 10 月 22 日才发布了 1.0.0 版本,但却受到了 Android 官方的推广,在 Android Developers Backstage 这个博客中专门聊过一期。推广的原因比较简单:一方面是这个库确实做得很好,另一方面是这个库完全是用 Kotlin 写的,而且运用了大量 Kotlin 的特性,尤其是协程。所以 Google 嘴上说着不会放弃 Java,但实际上咱们都懂的。

Coil 名字的由来:取 Coroutine Image Loader 首字母得来,可以看出通过 Kotlin 协程来进行图片加载,特点如下:

  • 更快:Coil 在性能上有很多优化,包括内存缓存和磁盘缓存、把缩略图保存在内存中、通过 BitmapPool 循环利用 Bitmap、自动暂停和取消网络请求等
  • 更轻量级:Coil 只有 2000 个方法,跟 Picasso 的方法数差不多,相比 Glide 和 Fresco 要轻量非常多
  • 更容易使用:Coil 的 API 充分利用 Kotlin 的新特性,而且还有丰富的拓展函数,简化和减少了很多样板代码
  • 更流行:Coil 通过 Kotlin 来开发,并且使用包含 Coroutines、okhttp、okio 和 AndroidX Lifecycles 在内的非常多流行的开源库

从 Coil 的特性可以看出,这是一个非常适合个人 App 使用的图片加载库,特别是纯 Kotlin 开发的 App。而且 Coil 里面运用了大量 Kotlin 的新特性以及协程,对于我们学习 Kotlin 有非常大的价值。相比于 glide 和 fresco 有着非常复杂的结构和惊人的代码量,Coil 只有 2000 左右的方法数,所以也很适合进行源码研究和学习。

一、基本使用


Coil 可以在 mavenCentral() 下载

implementation("io.coil-kt:coil:1.1.1") 

Coil 给 ImageView 加了很多拓展函数,所以我们一行代码便能进行图片加载

// URL
imageView.load("https://www.example.com/image.jpg")

// Resource
imageView.load(R.drawable.image)

// File
imageView.load(File("/path/to/image.jpg"))

同时我们也可以使用 lambda 语法轻松进行图片加载的配置

imageView.load("https://www.example.com/image.jpg") {
    crossfade(true)
    placeholder(R.drawable.image)
    transformations(CircleCropTransformation())
}

二、常用的 API


ImageLoader

ImageLoader 是 Coil 中对于图片加载的大管家,负责处理缓存、数据获取、图像解码、请求管理、Bitmap 缓存池、内存管理等工作,一般建议只创建一个 ImageLoader 并在 App 中进行共享,这样性能是最优的。这是因为每个 ImageLoader 都有自己的内存缓存和 Bitmap 缓存池。

我们可以通过构造器来创建和配置 ImageLoader。

val imageLoader = ImageLoader.Builder(context)
    .availableMemoryPercentage(0.25)
    .crossfade(true)
    .build()

同时由于 ImageLoader 是一个接口,也就意味着我们可以非常方便地进行测试,例如可以注入一个 fake 的 ImageLoader,从而每次都返回相同的 drawable。

val fakeImageLoader = object : ImageLoader {

    private val drawable = ColorDrawable(Color.BLACK)

    override fun enqueue(request: ImageRequest): disposable {
        request.target?.onStart(drawable)
        request.target?.onSuccess(drawable)
        return disposable
    }

    override suspend fun execute(request: ImageRequest): ImageResult {
        return SuccessResult(
            drawable = drawable, request = request,
            Metadata = ImageResult.Metadata(
                memoryCacheKey = MemoryCache.Key(""),
                isSampled = false,
                dataSource = DataSource.MEMORY_CACHE,
                isPlaceholderMemoryCacheKeyPresent = false
            )
        )
    }
}

ImageRequest

ImageRequest 为 ImageLoader 加载图片提供所有的必要信息,同时我们也可以使用自定义的 Target 进行处理。

val request = ImageRequest.Builder(context)
    .data("https://www.example.com/image.jpg")
    .target { drawable ->
        // Handle the result.
    }
    .build()
context.imageLoader.enqueue(request)  

ImageRequest 基于 Builder 模式来进行创建,包含了加载图片的各个配置项,这里重点看下最常用的配置项

配置项作用
context外部传入的 Context,一般是 ImageView 包含的 Context
data图片的地址
target图片加载之后的处理类
memoryCachePolicy内存缓存策略
diskCachePolicy磁盘缓存策略
networkCachePolicy网络缓存策略
decoder图片解码器
fetcher将图片地址转换成 BufferedSource 或 Drawable
lifecycle一般是对应 Activity 或 Fragment 的 Lifecycle

disposable

disposable 是调用 load() 方法之后的返回值,主要是用于取消图片加载

interface disposable {

    /**
     * 如果图片加载请求已经完成或者取消,则返回 true
     */
    val isdisposed: Boolean

    /**
     * 取消正在进行的图片加载请求以及释放相关的资源,而且该方法是幂等的
     */
    fun dispose()

    /**
     * 非阻塞式地等待任务结束
     */
    @ExperimentalCoilApi
    suspend fun await()
}

图片变换

图片变换是图片加载库中很常见的功能,Coil 将其抽象成 Transformation 接口,可以看到在 transform() 方法中有一个 BitmapPool 参数,这是因为在实现图形变换的时候往往需要一个 Bitmap,此时可以直接在 BitmapPool 中获取,从而复用已有的 Bitmap。

interface Transformation {
    fun key(): String
    suspend fun transform(pool: BitmapPool, input: Bitmap, size: Size): Bitmap
}

imageView.load("https://www.example.com/image.jpg") {
    transformations(CircleCropTransformation())
}

Coil 主要提供了这几个图片变换的效果

Tranformation功能
BlurTransformation高斯模糊
CircleCropTransformation圆形裁剪
GrayscaleTransformation图片置灰
RoundedCornersTransformation添加圆角

三、功能拓展


Coil 在提供了很多必要功能的基础上,预留了很多的拓展点给开发者实现自定义。Coil 的图片加载主要包括四个主要的模块:

模块作用
Interceptors拦截器,可以对图片加载请求进行观察、转换和重试
Mappers映射器,实现不同数据类型之间的转换
Fetchers抓取器,将图片地址转换成 BufferedSource 或 Drawable
Decoders解码器,实现各种图像格式的解码

Interceptors

Coil 的 Interceptor 无疑是借鉴了 okhttp 的设计思路,极大方便了后续的功能拓展,例如我们可以给 Coil 添加一个自定义的缓存层

class CustomCacheInterceptor(
    private val context: Context,
    private val cache: LruCache<String, Drawable>
) : Interceptor {

    override suspend fun intercept(chain: Interceptor.Chain): ImageResult {
        val value = cache.get(chain.request.data.toString())
        if (value != null) {
            return SuccessResult(
                drawable = value.bitmap.toDrawable(context),
                request = chain.request,
                Metadata = Todo()
            )
        }
        return chain.proceed(chain.request)
    }
}

Mappers、Fetchers

外部在调用 load() 时,传入的 String 参数既可能指向本地资源文件,也可能指向网络图片,Mappers 和 Fetchers 搭配使用,可以对资源类型进行区分,举个例子:

imageView.load("android.resource://example.package.name/drawable/image")
imageView.load("https://www.example.com/image.jpg")

StringMapper 会将传入的 String 转换为对应的 Uri。

internal class StringMapper : Mapper<String, Uri> {

    override fun map(data: String) = data.toUri()
}

ResourceUriFetcher 会判断 Uri 的 scheme 类型是否为 android.resource,是的话代表本地资源文件,而 HttpUriFetcher 则判断 Uri 的 scheme 是否为 http 或 https,是的话代表网络图片。

internal class HttpUriFetcher(callFactory: Call.Factory) : HttpFetcher<Uri>(callFactory) {

    override fun handles(data: Uri) = data.scheme == "http" || data.scheme == "https"

    override fun key(data: Uri) = data.toString()

    override fun Uri.toHttpUrl(): HttpUrl = HttpUrl.get(toString())
}
Mapper作用
FileUriMapper将 Uri 转换为 File
StringMapper将 String 转换为 Uri
ResourceIntMapper将 @DrawableRes Int 转换为 Resource Uri
ResouceUriMapper将具有资源名称的 android.resource Uri 映射到包含其资源 ID 的 Uri

Decoders

Android 支持了很多图像格式,但也有很多它不支持的格式(例如:Gif、SVG、视频帧等),所以 Coil 便提供了对应的拓展库

① Gif(GifDecoder 支持所有 API 级别,但速度较慢,imagedecoderDecoder 的加载速度快,但仅在 API 28 及更高版本可用)

implementation("io.coil-kt:coil-gif:1.1.1")
val imageLoader = ImageLoader.Builder(context)
    .componentRegistry {
        if (SDK_INT >= 28) {
            add(imagedecoderDecoder())
        } else {
            add(GifDecoder())
        }
    }
    .build()

② SVG(如果请求的 MIME 类型是 image/svg+xml,则会自动检测并解码所有 SVG)

implementation("io.coil-kt:coil-svg:1.1.1")
val imageLoader = ImageLoader.Builder(context)
    .componentRegistry {
        add(SvgDecoder(context))
    }
    .build()

③ 视频帧(仅支持 File 和 Uri)

implementation("io.coil-kt:coil-video:1.1.1")
val imageLoader = ImageLoader.Builder(context)
    .componentRegistry {
         add(VideoFrameFileFetcher())
         add(VideoFrameUriFetcher())
    }
    .build()

最后

笔者在面试前,从网上收集了一些 Android 开发相关的学习文档、面试题、Android 核心笔记等等文档,进行了复习,在此分享给大家,希望能帮助到大家学习提升,如有需要参考的可以直接去我 GitHub地址:https://github.com/733gh/Android-T3 访问查阅。

android -------- Retrofit + RxJava2.0 + Kotlin + MVP 开发的 WanAndroid 项目

android -------- Retrofit + RxJava2.0 + Kotlin + MVP 开发的 WanAndroid 项目

简介

wanandroid 项目基于 Retrofit + RxJava2.0 + Kotlin + MVP

  implementation ''io.reactivex.rxjava2:rxjava:2.1.3''
    implementation ''com.squareup.retrofit2:retrofit:2.3.0''
    implementation ''com.squareup.retrofit2:converter-gson:2.3.0''
    implementation ''com.squareup.retrofit2:adapter-rxjava2:2.3.0''
    implementation ''io.reactivex.rxjava2:rxandroid:2.0.1''
    implementation ''com.github.bumptech.glide:glide:3.7.0''

    implementation ''cn.bingoogolapple:bga-banner:2.2.4''
    implementation ''com.just.agentweb:agentweb:1.0.3''

    /** loading状态组件 **/
    implementation ''com.github.ybq:Android-SpinKit:1.1.0''

    /**状态栏**/
    implementation ''com.gyf.barlibrary:barlibrary:2.3.0''

    implementation ''com.hyman:flowlayout-lib:1.1.2''

    implementation ''com.alibaba:fastjson:1.1.68.android''

 

有下拉刷新 SmartRefreshLayout

 

效果图      

 

 

 

 

玩 Android 的 Api 是免费的,感谢 WanAndroid 提供的数据来源

鸿洋大佬提供的 API 接口 http://www.wanandroid.com/blog/show/2

 

项目地址: https://github.com/DickyQie/wanandroid

 

Android 11 来袭,一起来看看怎么适配

Android 11 来袭,一起来看看怎么适配

我的公众号程序员徐公,四年中大厂工作经验,回复黑马,领取 Android 学习视频一份,回复徐公666,可以获得我精心整理的简历模板,带你走近大厂。

Android R
终于开始了Android 11的适配工作。记录一下,供需要的人参考。

1. 准备工作

老规矩,首先将我们项目中的 targetSdkVersion 改为 30。或者使用兼容性调试工具,后面我会说到。

2. 存储机制更新

Scoped Storage(分区存储)

具体适配方法和去年的[Android 10
适配攻略](https://weilu.blog.csdn.net/a...

不过需要注意的是,应用targetSdkVersion >= 30,强制执行分区存储机制。之前在AndroidManifest.xml中添加
android:requestLegacyExternalStorage="true"的适配方式已不起作用。

还有一个变化:Android 11 允许使用除 MediaStore API 之外的 API 通过文件路径直接访问共享存储空间中的媒体文件。其中包括:

  • File API。
  • 原生库,例如 fopen()

如果你之前没有适配Android 10,这一点对你来说是个好消息。Android 10在AndroidManifest.xml中添加
android:requestLegacyExternalStorage="true"来适配,Android 11上直接使用File
API访问媒体文件。不得不说,等等党的胜利?

不过,使用原始文件路径直接访问共享存储空间中的媒体文件会重定向到 MediaStore
API,这次重定向会造成性能影响(随机读写慢一倍左右)。而且直接使用原始文件路径,并不会比使用 MediaStore API
有更多优势,因此官方强烈建议直接使用 MediaStore API。

MANAGE_EXTERNAL_STORAGE

当然还有一种简单粗暴的适配方法,获取外部存储管理权限。如果你的应用是手机管家、文件管理器这类需要访问大量文件的app,可以申请MANAGE_EXTERNAL_STORAGE权限,将用户引导至系统设置页面开启。代码如下:

<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
        tools:ignore="ScopedStorage" />


public static void checkStorageManagerPermission(Context context) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R &&
                !Environment.isExternalStorageManager()) {

        Intent intent = new Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION);
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        context.startActivity(intent);
    }
}

在这里插入图片描述

需要注意的是即使你有了MANAGE_EXTERNAL_STORAGE权限,也无法访问Android/data/ 目录下的文件。

对于MANAGE_EXTERNAL_STORAGE权限,国内使用应该没有什么影响。但是在Google
Play上需要说明为什么已有的SAFMediaStore不满足你的应用需求,审核通过才允许上架使用。所以一般情况下,我个人不推荐你为了适配简单,直接申请使用MANAGE_EXTERNAL_STORAGE权限。

其他细节变更见文档:[Android 11
中的存储机制更新](https://developer.android.goo...

相关api变更及使用推荐郭霖大神的这篇:[Android 11新特性,Scoped
Storage又有了新花样](https://guolin.blog.csdn.net/...

存储访问框架 (SAF)变更

Android 11对SAF添加以下限制:

  • 使用 ACTION_OPEN_DOCUMENT_TREEACTION_OPEN_DOCUMENT,无法浏览到Android/data/Android/obb/ 目录及其所有子目录。
  • 使用 ACTION_OPEN_DOCUMENT_TREE无法授权访问存储根目录、Download文件夹。

REQUEST_INSTALL_PACKAGES

在8.0的适配中,我们安装apk包之前需要申请“安装未知来源应用”的权限。一般来说首次是跳转到授权页面让用户手动开启,然后返回app进行安装。

在Android 11中当用户开启“安装未知来源应用”的权限,app就会被杀死。该行为与强制分区存储有关,因为持有
REQUEST_INSTALL_PACKAGES 权限的应用可以访问其他应用的Android/obb 目录。

好在用户授予权限之后,虽然app会被杀死,但是 安装页面依然会弹出

目前对于这一变更我没有发现可以适配处理的方式,详细介绍见:[Android
11特性调整:安装外部来源应用需要重启APP](https://news.51cto.com/art/20...


这里补充一下,因为其他应用无法访问应用的Android/data/
Android/obb/目录及其所有子目录。所以需要注意保存在这里面的文件是否会被其他程序访问。

比如我在用系统的裁切功能时,因为设置的MediaStore.EXTRA_OUTPUT文件是私有目录下的,导致裁剪后的图片无法正确生成。所以需要针对android
11进行适配:

String fileName = System.currentTimeMillis() + ".jpg";
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
    // 裁剪无法访问App的私有目录,所以可以保存至公有目录
    ContentValues values = new ContentValues();
    values.put(MediaStore.Images.Media.DISPLAY_NAME, fileName);
    values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg");
    values.put(MediaStore.Images.Media.RELATIVE_PATH, Environment.DIRECTORY_PICTURES + "/Crop");
    Uri uri = this.getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
    intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
} else {
    ...
}

或者保存至Android/media共享文件目录,这样不用适配版本。

String fileName = System.currentTimeMillis() + ".jpg";
File file = new File(this.getExternalMediaDirs()[0].getAbsolutePath() + File.separator + fileName);
intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(file));

当然如果你是自己实现的裁剪功能,那么不受影响。

3.权限变化

单次权限授权

从 Android 11
开始,每当应用请求与位置信息、麦克风或摄像头相关的权限时,面向用户的权限对话框会包含仅限这一次选项。如果用户在对话框中选择此选项,系统会向应用授予临时的单次授权。

单次权限授权

单次权限授权的应用可以在一段时间内访问相关数据,具体时间取决于应用的行为和用户的操作:

  • 当应用的 Activity 可见时,应用可以访问相关数据。
  • 如果用户将应用转为后台运行,应用可以在短时间内继续访问相关数据。
  • 如果您在 Activity 可见时启动了一项前台服务,并且用户随后将您的应用转到后台,那么您的应用可以继续访问相关数据,直到该前台服务停止。
  • 如果用户撤消单次授权(例如在系统设置中撤消),无论您是否启动了前台服务,应用都无法访问相关数据。与任何权限一样,如果用户撤消了应用的单次授权,应用进程就会终止。

当用户下次打开应用并且应用中的某项功能请求访问位置信息、麦克风或摄像头时,系统会再次提示用户授予权限。

如果你之前就是使用权限时才请求相关权限,那么这一变更对于你的应用没有影响。

请求位置权限

这部分在Android 10的适配有过调整,当时规则如下:

请求ACCESS_FINE_LOCATION
ACCESS_COARSE_LOCATION权限表示在前台时拥有访问设备位置信息的权限。在请求弹框中,选择“始终允许”表示前后台都可以获取位置信息,选择“仅在应用使用过程中允许”只表示拥有前台的权限。

在Android 11中,请求弹框中取消了“始终允许”这一选项。也就是说 默认不会授予你后台访问设备位置信息的权限
。如果尝试请求ACCESS_BACKGROUND_LOCATION权限的同时请求任何其他权限,系统会抛出异常,不会向应用授予其中的任一权限。

官方给出的适配建议及原因如下:

建议应用对位置权限执行递增请求,先请求前台位置信息访问权限,再请求后台位置信息访问权限。执行递增请求可以为用户提供更大的控制权和透明度,因为他们可以更好地了解应用中的哪些功能需要后台位置信息访问权限。

总结一下得出两点:

  • 先请求前台位置信息访问权限,再请求后台位置信息访问权限。
  • 单独请求后台位置信息访问权限,不要与其他权限一同请求。

这里还需要注意不同目标平台应用在Android 11上的表现:

  • Android 10 为目标平台的应用 允许同时访问前后台的位置信息权限,但同样不会有“始终允许”这一选项。
  1. 没有前后台的位置信息权限时:

没有前后台的位置信息权限

  1. 有前台的位置信息权限时:

有前台的位置信息权限

  • Android 11 为目标平台的应用
  1. 没有前后台的位置信息权限时,只能先请求前台的位置信息权限:

在这里插入图片描述

  1. 有前台的位置信息权限,请求后台的位置信息时系统会跳转到下面的设置页面。

定位权限设置页
选择“始终允许”表示具有前后台位置信息访问权限,如果用户拒绝两次应用定位访问请求(直接返回等),后面请求相同权限都会被直接提示请求失败。(这里就需要我们给用户以引导了)

这里解释一下“拒绝两次”,这是Android 11
上添加的权限对话框的可见性,以前我们点击了“不再询问”表示拒绝授权。现在还包含类似上面这种转到系统设置,然后点返回按钮,也算是拒绝授权。当然,用户按返回按钮关闭权限对话框,此操作不算。

总结一下,与Android 10的区别就是将后台权限的申请分离了出来,增加了用户“拒绝”的条件,避免了应用重复请求用户已拒绝的权限。

软件包可见性

软件包可见性是Android
11上提升系统隐私安全性的一个新特性。它的作用是限制app随意获取其他app的信息和安装状态。避免病毒软件、间谍软件利用,引发网络钓鱼、用户安装信息泄露等安全事件。

获取自动可见应用的列表,可以执行命令adb shell dumpsys package queries,找到 forceQueryable
部分。下面是在vivo iqoo手机的执行结果。

Queries:
  system apps queryable: false
  forceQueryable:
    [com.android.BBKCrontab,com.vivo.fingerprint,com.vivo.epm,com.vivo.abe,com.vivo.fingerprintengineer,com.vivo.contentcatcher,com.vivo.floatingball,com.vivo.agent,com.vivo.nightpearl,android,com.wapi.wapicertmanage,com.vivo.vms,co
m.android.providers.settings,com.vivo.upslide,com.vivo.assistant,com.vivo.vivokaraoke,com.vivo.fingerprintui,com.android.wallpaperbackup,com.bbk.facewake,com.vivo.faceunlock,com.vivo.doubleinstance,com.vivo.audiofx,com.iqoo.powersav
ing,com.bbk.SuperPowerSave,com.vivo.vibrator4d,com.vivo.smartunlock,com.vivo.globalanimation,com.vivo.appfilter,com.vivo.voicewakeup,com.vivo.minscreen,com.android.bbklog,com.mobile.cos.iroaming,com.vivo.networkstate,com.vivo.daemon
Service,com.vivo.smartshot,com.vivo.vtouch,com.android.networkstack.tethering.inprocess,com.android.localtransport,com.vivo.pem,com.vivo.wifiengineermode,com.android.server.telecom,com.vivo.gamecube,com.vivo.aiengine,com.vivo.multin
lp,com.vivo.smartmultiwindow,com.vivo.permissionmanager,com.qti.diagservices,com.vivo.bsptest,com.qti.snapdragon.qdcm_ff,com.vivo.dr,com.vivo.sps,com.android.dynsystem,com.vivo.setupwizard,com.vivo.gamewatch,com.android.keychain,com
.vivo.faceui,com.android.networkstack.inprocess,com.android.location.fused,com.android.inputdevices,com.android.settings,com.iqoo.engineermode,com.vivo.fuelsummary]
    [com.qualcomm.uimremoteserver,com.vivo.devicereg,com.qti.qualcomm.deviceinfo,com.volte.config,com.android.mms.service,com.android.ons,com.qualcomm.qcrilmsgtunnel,com.vivo.sim.contacts,com.qualcomm.qti.uimGbaApp,com.qualcomm.qti.
modemtestmode,com.android.stk,com.android.vendors.bridge.softsim,com.qualcomm.uimremoteclient,com.qti.qualcomm.datastatusnotification,com.qualcomm.qti.uim,com.android.phone,com.qualcomm.qti.dynamicddsservice,com.qualcomm.qti.telepho
nyservice,com.android.cellbroadcastservice,com.android.providers.telephony,com.qti.dpmserviceapp,com.android.incallui]
    [com.android.vivo.tws.vivotws,com.android.bluetooth]
    com.android.nfc
    com.android.se
    com.android.networkstack.permissionconfig
    com.android.shell
    com.android.providers.media.module
    com.android.wifi.resources.overlay.common
    com.android.theme.icon_pack.filled.themepicker
    com.android.theme.icon_pack.circular.themepicker
    com.android.server.telecom.overlay.common
......

可以看到都是系统应用包名,所以我们的三方应用默认是不可见的。此项变更影响比较多的是分享支付一类需要与其他应用交互的功能。下面举一个简单的例子:

private static boolean hasActivity(Context context, Intent intent) {
    PackageManager packageManager = context.getPackageManager();
    return packageManager.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY).size() > 0;
}

public void test() {
    Intent intent = new Intent();
    intent.setClassName("com.tencent.mm", "com.tencent.mm.ui.tools.ShareImgUI");
    Log.d("hasActivity:", hasActivity(this, intent) + "");
}

hasActivity方法中通过queryIntentActivities来判断此页面是否存在。但是在`targetSdkVersion >=
30中,这些三方默认都是不可见的。所以都会返回false。类似方法getInstalledPackagesgetPackageInfo`也受到相应的限制。

解决方法很简单,在AndroidManifest.xml 中添加queries元素,里面添加需要可见的应用包名。

<manifest package="com.example.app">
    <queries>
        <package android:name="com.tencent.mm" /> <- 指定微信包名
    </queries>
    ...
</manifest>

我在适配中用到的还有下面的包名,我们可以按需添加:

<queries>
    <!-- 微博 -->
    <package android:name="com.sina.weibo" />
    <!-- QQ -->
    <package android:name="com.tencent.mobileqq" />
    <!-- 支付宝 -->
    <package android:name="com.eg.android.AlipayGphone" /> 
    <!-- AlipayHK -->
    <package android:name="hk.alipay.wallet" />
</queries>

除了直接添加包名的方式外,我们可以按intent和provider来添加:

<manifest package="com.example.app">
    <queries>
        <intent>
            <action android:name="android.intent.action.SEND" />
            <data android:mimeType="image/jpeg" />
        </intent>

        <provider android:authorities="com.example.settings.files" />
    </queries>
    ...
</manifest>

具体的规则参见:管理软件包可见性

当然,还有一种简单粗暴的方式,可以直接申请权限QUERY_ALL_PACKAGES。如果你的应用需要上架`Google
Play`,那么可能要注意相关政策。为了尊重用户隐私,建议我们的应用按正常工作所需的最小软件包可见性来适配。

有一点需要说明一下,我们日常使用的startActivity
方法不受系统软件包可见性行为的影响,即使hasActivity为false,一样可以跳转。如果我们在做跳转前,进行类似hasActivity的判断,那么会受影响。

最后需要注意的是 ,使用queries元素需要Android Gradle 插件版本是 4.1及以上
,因为旧版本的插件并不兼容此元素,出现合并 manifest 的错误。

前台服务类型

Android 10中,在前台服务访问位置信息,需要在对应的service中添加 location 服务类型。

同样的,Android 11中,在前台服务访问摄像头或麦克风,需要在对应的service中添加cameramicrophone 服务类型。

<manifest>
    ...
   <service 
       android:name="MyService"
       android:foregroundServiceType="microphone|camera" />
</manifest>

这一限制的变更,使得程序无法在后台启动服务访问摄像头和麦克风。如需使用,只能是前台开启前台服务。除非有如下情况:

  • 服务由系统组件启动。
  • 服务是通过应用小部件启动。
  • 服务是通过与通知交互启动的。
  • 服务是PendingIntent启动的,它是从另一个可见的应用程序发送过来的。
  • 服务由一个应用程序启动,该应用是一个DPC,且在设备所有者模式下运行。
  • 服务由一个提供VoiceInteractionService的应用启动。
  • 服务由一个具有START_ACTIVITIES_FROM_BACKGROUND权限的应用启动。

权限自动重置

如果应用以 Android 11 或更高版本为目标平台并且数月未使用,系统会通过自动重置用户已授予应用的运行时敏感权限来保护用户数据。如下图所示:

权限自动重置

注意上图中有一个启动自动重置的开关。如果我们的应用有特殊需要,可以引导用户关闭它。示例代码如下:

public void checkAutoRevokePermission(Context context) {
    // 判断是否开启
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R &&
            !context.getPackageManager().isAutoRevokeWhitelisted()) {
        // 跳转设置页    
        Intent intent = new Intent(Intent.ACTION_AUTO_REVOKE_PERMISSIONS);
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        intent.setData(Uri.fromParts("package", context.getPackageName(), null));
        context.startActivity(intent);
    }
}

SYSTEM_ALERT_WINDOW权限

这部分我在适配中没有用到,直接照搬文档:

在 Android 11 中,系统会根据请求自动向某些类型的应用授予 SYSTEM_ALERT_WINDOW 权限:

  • 系统会自动向具有 ROLE_CALL_SCREENING 且请求 SYSTEM_ALERT_WINDOW 的所有应用授予该权限。如果应用失去 ROLE_CALL_SCREENING,就会失去该权限。
  • 系统会自动向通过 MediaProjection 截取屏幕且请求 SYSTEM_ALERT_WINDOW 的所有应用授予该权限,除非用户已明确拒绝向应用授予该权限。当应用停止截取屏幕时,就会失去该权限。此用例主要用于游戏直播应用。

这些应用无需发送 ACTION_MANAGE_OVERLAY_PERMISSION 以获取 SYSTEM_ALERT_WINDOW
权限,它们只需直接请求 SYSTEM_ALERT_WINDOW 即可。

MANAGE_OVERLAY_PERMISSION intent 始终会将用户转至系统权限屏幕

从 Android 11 开始,ACTION_MANAGE_OVERLAY_PERMISSION intent
始终会将用户转至顶级设置屏幕,用户可在其中授予或撤消应用的 SYSTEM_ALERT_WINDOW 权限。intent 中的任何 package:
数据都会被忽略。

在更低版本的 Android 中,ACTION_MANAGE_OVERLAY_PERMISSION intent
可以指定一个软件包,它会将用户转至应用专用屏幕以管理权限。从 Android 11
开始将不再支持此功能,而是必须由用户先选择要授予或撤消哪些应用的权限。此变更可以让权限的授予更有目的性,从而达到保护用户的目的。

读取手机号

如果你是通过TelecomManagergetLine1Number方法,或TelephonyManagergetMsisdn方法获取电话号码。那么在Android
11中需要增加READ_PHONE_NUMBERS权限。使用其他方法不受限。

<manifest>
    <!-- 如果应用仅在 Android 10及更低版本中使用该权限,可以添加 maxSdkVersion="29" -->
    <uses-permission android:name="android.permission.READ_PHONE_STATE"
                     android:maxSdkVersion="29" />
    <uses-permission android:name="android.permission.READ_PHONE_NUMBERS" />
</manifest>

4.其他行为变更

自定义view的Toast

Android 11 为目标平台的应用,从后台发送自定义view的Toast消息系统会进行屏蔽。 前台使用不受影响
Toast相应的setViewgetView也已经废弃不建议使用。

如果要在后台使用,推荐使用默认的toast或Snackbar替代。

APK签名

Android 11 为目标平台的应用,仅通过v1 签名的应用无法在Android 11的设备上安装或更新。必须使用v2或更高版本进行签名。

同时Android 11 添加了对 APK [签名方案
v4](https://developer.android.goo... 的支持。

AsyncTask

AsyncTask在Android 11已经不建议使用,建议迁移至kotlin的协程。

此外Handler未指定Looper的构造方法也已不建议使用。
Handler源码

建议明确指定Looper

private Handler handler = new Handler(Looper.myLooper());
// 或
private Handler handler = new Handler(Looper.getMainLooper());

状态栏高度

发现系统为Android 11的手机上targetSdkVersion
是30时获取状态栏高度为0,低于30获取值正常。。。因此需要使用WindowMetrics 适配一下:

public static int getStatusBarHeight(Context context) {

     if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
        WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        WindowMetrics windowMetrics = wm.getCurrentWindowMetrics();
        WindowInsets windowInsets = windowMetrics.getWindowInsets();
        Insets insets = windowInsets.getInsetsIgnoringVisibility(WindowInsets.Type.navigationBars() | WindowInsets.Type.displayCutout());
        return insets.top;
    }       
    
    ....
}

WindowMetrics是Android
11新增的类,用于获取窗口边界,同样可以用来获取导航栏高度。

5.新增工具

兼容性调试工具

以往我们做适配的时候,需要先将我们项目中的 targetSdkVersion
修改为对应版本。这就导致你适配过程中有可能受到其他变更的影响,而这个新增的兼容性调试工具可以让你在不升级targetSdkVersion的情况下,针对每项变更逐个开启适配。

使用方法:

  • 开发者选项中找到应用兼容性变更选项。
  • 点击进入找到你需要调试的应用
  • 在变更列表中,找到想要开启或关闭的变更,然后点击相应的开关。

在这里插入图片描述
上面第一行DEFAULT_SCOPED_STORAGE就是启用分区储存,这些常量详细的含义见:[Android 11
变更列表](https://developer.android.goo...

对于兼容性调试工具详细的使用方法见:兼容性框架工具,这里限于篇幅就不展开说了。

无线调试

Android 11的开发者选项中添加了一个无线调试的功能。类似于连接蓝牙耳机功能,可以无需USB连接线进行日常开发调试工作。(区别于以前的Android
WIFI ADB,这个是真无线,哈哈)

无线调试

使用方法:

  • 开发者选项中找到无线调试并打开。
  • 首次配对需点击“使用配对码配对设备”
  • 运行 adb pair ipaddr:port后输入配对码进行连接。

注意事项:

  • 保持电脑和手机在一个网络。
  • Platform Tools 版本需大于30.0。可使用adb --version查看。

在这里插入图片描述

不过我自己体验下来,感觉连接不是很稳定,不知是AS的问题还是手机问题。同时锁屏后也会断开连接,体验不是很好。。。期待后续的优化吧。


本篇内容有点多。总结一下,Android
11在权限上的变更比较多,但如果你一直遵守申请权限相关的最佳做法,那么基本上不需要额外的适配工作。

最后强调一下,对于 单次授权,权限对话框的可见性,SYSTEM_ALERT_WINDOW 权限,安装apk 这些变更只要在Android
11上就会生效,不论你是否适配Android
11。对于其他变更和API(相机、5G、瀑布屏、键盘等),因为我暂时没有遇到,也就没有列出,有需要的可以点击文末的官方文档链接查看。

截止发这篇博客时,我手机上只发现哔哩哔哩已经适配了Android 11。大多数停留在28、29,更有甚者还在26(Android 8.0
国内上架的最低适配标准)。

所以我顺便附上之前写的Android 9、10的适配攻略:

  • Android 9.0 适配指南
  • Android 10 适配攻略

在这里插入图片描述

可能本篇你暂时也用不上,你可以不用,但是不能没有。点赞收藏一波不过分吧~~

参考

  • Android 11官方文档
  • Android 11 中的存储机制更新
  • 微信开发平台 - Android 11 系统策略更新
  • OPPO - Android 11 应用兼容性适配指导

如果觉得对你有所帮助的话,可以关注我的微信公众号程序员徐公

  1. 公众号程序员徐公回复黑马,获取 Android 学习视频
  2. 公众号程序员徐公回复徐公666,获取简历模板,教你如何优化简历,走进大厂
  3. 公众号程序员徐公回复面试,可以获得面试常见算法,剑指 offer 题解
  4. 公众号程序员徐公回复马士兵,可以获得马士兵学习视频一份

在这里插入图片描述

Android Fresco图片加载库基础使用详解

Android Fresco图片加载库基础使用详解

前言

图片加载在 Android开发项目中是必不可少的,为了降低开发周期和难度,我们经常会选用一些图片加载的开源库,而Android发展到现在图片加载开源库也越来越多了,下面介绍 Fresco开源图片加载库.

简介

Fresco是由Facebook开源的一个图片加载库,Fresco是一个功能强大的系统,用于在Android应用程序中显示图像.

功能介绍以及基础使用

1.配置

  • 在 build.gradle 中配置:
dependencies {
  // 其他依赖
  compile ''com.facebook.fresco:fresco:0.12.0''
}
  • 下面的依赖需要根据需求添加:
dependencies {
  // 在 API < 14 上的机器支持 WebP 时,需要添加
  compile ''com.facebook.fresco:animated-base-support:0.12.0''

  // 支持 GIF 动图,需要添加
  compile ''com.facebook.fresco:animated-gif:0.12.0''

  // 支持 WebP (静态图+动图),需要添加
  compile ''com.facebook.fresco:animated-webp:0.12.0''
  compile ''com.facebook.fresco:webpsupport:0.12.0''

  // 仅支持 WebP 静态图,需要添加
  compile ''com.facebook.fresco:webpsupport:0.12.0''
}
  • Application中初始化Fresco
[MyApplication.java]
public class MyApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        Fresco.initialize(this);
    }
}
  • 在 AndroidManifest.xml 中指定你的 Application 类
  <manifest
    ...
    >
    <uses-permission android:name="android.permission.INTERNET" />
    <application
      ...
      android:label="@string/app_name"
      android:name=".MyApplication"
      >
      ...
    </application>
    ...
  </manifest>
  • 添加网络权限
<uses-permission android:name="android.permission.INTERNET"/>
  • 在xml布局文件中, 加入SimpleDraweeView:
<com.facebook.drawee.view.SimpleDraweeView
    android:id="@+id/my_image_view"
    android:layout_width="130dp"
    android:layout_height="130dp"
    fresco:placeholderImage="@drawable/my_drawable"
  />
  • 开始加载图片
Uri uri = Uri.parse("https://raw.githubusercontent.com/facebook/fresco/gh-pages/static/logo.png");
SimpleDraweeView draweeView = (SimpleDraweeView) findViewById(R.id.my_image_view);
draweeView.setImageURI(uri);

剩下的,Fresco会替你完成:

显示占位图直到加载完成;
下载图片;
缓存图片;
图片不再显示时,从内存中移除;
等等等等。


2.基本功能介绍&使用

  • 可配置的所有选项
<com.facebook.drawee.view.SimpleDraweeView
  android:id="@+id/my_image_view"
  android:layout_width="20dp"
  android:layout_height="20dp"
  fresco:fadeDuration="300"  //淡入淡出动画持续时间
  fresco:actualImageScaleType="focusCrop"  //实际图片缩放类型
  fresco:placeholderImage="@color/wait_color"  //占位符
  fresco:placeholderImageScaleType="fitCenter"  //占位符图片缩放类型
  fresco:failureImage="@drawable/error"  //加载失败显示图片
  fresco:failureImageScaleType="centerInside"  //缩放类型
  fresco:retryImage="@drawable/retrying"  //重新加载图片
  fresco:retryImageScaleType="centerCrop"  
  fresco:progressBarImage="@drawable/progress_bar"   //正在加载图片
  fresco:progressBarImageScaleType="centerInside"   //缩放类型
  fresco:progressBarAutoRotateInterval="1000"    //正在加载图片自动旋转的时间间隔,直到图片加载成功停止旋转
  fresco:backgroundImage="@color/blue"    //背景图片
  fresco:overlayImage="@drawable/watermark"  // 叠加图
  fresco:pressedStateOverlayImage="@color/red"
  fresco:roundAsCircle="false" //圆形图
  fresco:roundedCornerRadius="1dp"   //圆角图&半径
  fresco:roundTopLeft="true"  //左上角
  fresco:roundTopRight="false"//右上角圆
  fresco:roundBottomLeft="false"//左下角
  fresco:roundBottomRight="true"//右下角
  fresco:roundWithOverlayColor="@color/corner_color"  // 圆形&圆角边框颜色
  fresco:roundingBorderWidth="2dp"  // 圆形&圆角边框宽度
  fresco:roundingBorderColor="@color/border_color"     //圆形或圆角图像底下的叠加颜色
/>
必须声明 android:layout_width 和 android:layout_height。如果没有在XML中声明这两个属性,将无法正确加载图像。

Drawees 不支持 wrap_content 属性。

所下载的图像可能和占位图尺寸不一致,如果设置出错图或者重试图的话,这些图的尺寸也可能和所下载的图尺寸不一致。

如果大小不一致,假设使用的是 wrap_content,图像下载完之后,View将会重新layout,改变大小和位置。这将会导致界面跳跃。


至此,Fresco的基本功能介绍&使用就讲解完毕了,感谢阅读

参考文章

  • 官方文档
  • Android图片加载神器之Fresco-加载图片基础

欢迎关注作者darryrzhong,更多干货等你来拿哟.

请赏个小红心!因为你的鼓励是我写作的最大动力!

更多精彩文章请关注
  • 个人博客:darryrzhong
  • 掘金
  • 简书
  • SegmentFault
  • 慕课网手记

Android Glide图片加载(加载监听、加载动画)

Android Glide图片加载(加载监听、加载动画)

本文实例为大家分享了Android Glide图片加载的具体代码,供大家参考,具体内容如下

1.普通用法

Glide.with(context)
.load(url)
.into(view);

with中可以放context、activity、fragment。。;当放activity、fragment时glide会根据生命周期来加载图片。推荐使用activity。

2.设置加载中和加载失败的图片

Glide.with(context)
.load(url)
.placeholder(R.drawable.loading) //占位符 也就是加载中的图片,可放个gif
.error(R.drawable.Failed) //失败图片
.into(view);

3.添加图片淡入加载的效果

.crossFade()

4.用 animate() 自定义动画

从资源中的动画:

回到代码,第一个选项是传一个 Android 资源 id,即动画的资源。一个简单的例子是每个 Android 系统都提供的:slide-in-left(从左滑入)动画, android.R.anim.slide_in_left 。下面这段代码是这个动画的 XML 描述:

<?xml version="1.0" encoding="utf-8"?> 
<set xmlns:android="http://schemas.android.com/apk/res/android"> 
  <translate android:fromXDelta="-50%p" android:toXDelta="0"
      android:duration="@android:integer/config_mediumAnimTime"/>
  <alpha android:fromAlpha="0.0" android:toAlpha="1.0"
      android:duration="@android:integer/config_mediumAnimTime" />
</set>

当然你可以创建你自己的 XML 动画。比如一个小的缩放动画,图片刚开始小的,然后逐渐增大到原尺寸。

<?xml version="1.0" encoding="utf-8"?> 
<set xmlns:android="http://schemas.android.com/apk/res/android" 
   android:fillAfter="true">

  <scale
    android:duration="@android:integer/config_longAnimTime"
    android:fromXScale="0.1"
    android:fromYScale="0.1"
    android:pivotX="50%"
    android:pivotY="50%"
    android:toXScale="1"
    android:toYScale="1"/>
</set> 

这两个动画都可以用到 Glide 建造者中:

Glide 
  .with( context )
  .load( eatFoodyImages[0] )
  .animate( android.R.anim.slide_in_left ) // or R.anim.zoom_in
  .into( imageView1 );

在图片从网络加载完并准备好之后将从左边滑入。

通过自定义类实现动画

这个很简单,你只需实现 void animate(View view) 方法。这个视图对象是整个 target 视图。如果它是一个自定义的视图,你要找到你的视图的子元素,并且做些必要的动画。

来看个简单的例子。假设你想要实现一个渐现动画,你得需要创建这样的动画对象:

ViewPropertyAnimation.Animator animationObject = new ViewPropertyAnimation.Animator() { 
  @Override
  public void animate(View view) {
    // if it's a custom view class,cast it here
    // then find subviews and do the animations
    // here,we just use the entire view for the fade animation
    view.setAlpha( 0f );

    ObjectAnimator fadeAnim = ObjectAnimator.ofFloat( view,"alpha",0f,1f );
    fadeAnim.setDuration( 2500 );
    fadeAnim.start();
  }
};

接下来,你需要在 Glide 请求中去设置这个动画:

Glide 
  .with( context )
  .load( eatFoodyImages[1] )
  .animate( animationObject )
  .into( imageView2 );

当然,在 animate(View view) 中你的动画对象方法中, 你可以做任何你想要对视图做的事情。自由的用你的动画创建吧。

如果你要在你的自定义视图中实现,你只需要创建这个视图对象,然后在你的自定义视图中创建你的自定义方法。

5.添加加载完成监听

Glide.with(ShowImgActivity.this)
   .load(urlString)
   .centerCrop()
   .error(R.drawable.Failed)
   .crossFade()
   .into(new GlideDrawableImageViewTarget(imageView) {
 @Override
   public void onResourceReady(GlideDrawable drawable,GlideAnimation anim) {
   super.onResourceReady(drawable,anim);
   //在这里添加一些图片加载完成的操作
  }
)};

6.图片缓存机制

Glide缓存策略

Glide默认开启磁盘缓存和内存缓存,当然也可以对单张图片进行设置特定的缓存策略。
设置图片不加入到内存缓存

Glide 
  .with( context )
  .load( eatFoodyImages[0] )
  .skipMemoryCache( true )
  .into( imageViewInternet );

设置图片不加入到磁盘缓存

Glide 
  .with( context )
  .load( eatFoodyImages[0] )
  .diskCacheStrategy( diskCacheStrategy.NONE )
  .into( imageViewInternet );

Glide支持多种磁盘缓存策略:

diskCacheStrategy.NONE :不缓存图片
diskCacheStrategy.soURCE :缓存图片源文件
diskCacheStrategy.RESULT:缓存修改过的图片
diskCacheStrategy.ALL:缓存所有的图片,默认

图片加载优先级

Glide支持为图片加载设置优先级,优先级高的先加载,优先级低的后加载:

private void loadImageWithHighPriority() { 
  Glide
    .with( context )
    .load( UsageExampleListViewAdapter.eatFoodyImages[0] )
    .priority( Priority.HIGH )
    .into( imageViewHero );
}

private void loadImagesWithLowPriority() { 
  Glide
    .with( context )
    .load( UsageExampleListViewAdapter.eatFoodyImages[1] )
    .priority( Priority.LOW )
    .into( imageViewLowPrioLeft );

  Glide
    .with( context )
    .load( UsageExampleListViewAdapter.eatFoodyImages[2] )
    .priority( Priority.LOW )
    .into( imageViewLowPrioRight );
}

7.加载圆角图片

/**
 * 圆形图
 *
 * Created by <lzh> on 2016/7/29.
 */
public class GlideCircleTransform extends BitmapTransformation {
  public GlideCircleTransform(Context context) {
    super(context);
  }

  @Override
  protected Bitmap transform(BitmapPool pool,Bitmap toTransform,int outWidth,int outHeight) {
    return circleCrop(pool,toTransform);
  }

  private static Bitmap circleCrop(BitmapPool pool,Bitmap source) {
    if (source == null) return null;
    int size = Math.min(source.getWidth(),source.getHeight());
    int x = (source.getWidth() - size) / 2;
    int y = (source.getHeight() - size) / 2;
    // Todo this Could be acquired from the pool too
    Bitmap squared = Bitmap.createBitmap(source,x,y,size,size);
    Bitmap result = pool.get(size,Bitmap.Config.ARGB_8888);
    if (result == null) {
      result = Bitmap.createBitmap(size,Bitmap.Config.ARGB_8888);
    }
    Canvas canvas = new Canvas(result);
    Paint paint = new Paint();
    paint.setShader(new BitmapShader(squared,BitmapShader.TileMode.CLAMP,BitmapShader.TileMode.CLAMP));
    paint.setAntiAlias(true);
    float r = size / 2f;
    canvas.drawCircle(r,r,paint);
    return result;
  }

  @Override
  public String getId() {
    return getClass().getName();
  }
}


**然后使用的时候只要加上这句话就行了
.transform(new GlideCircleTransform(context))**

Glide.with(mContext)
        .load(imageUrl)
        .transform(new GlideCircleTransform(mContext))
        .into(holder.imageView);


注意事项:

不能直接给要使用glide的imageview设置tag;

因为glide在加载图片的时候用到了tag,会造成冲突,并报错;

当要用到tag写逻辑代码的时候,可以这样

.setTag(R.string.xxx,xxx);并.getTag(R.string.xxx);

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持编程小技巧。

今天关于一起来看看 Android 官推 kotlin-first 的图片加载库android kotlin flow的介绍到此结束,谢谢您的阅读,有关android -------- Retrofit + RxJava2.0 + Kotlin + MVP 开发的 WanAndroid 项目、Android 11 来袭,一起来看看怎么适配、Android Fresco图片加载库基础使用详解、Android Glide图片加载(加载监听、加载动画)等更多相关知识的信息可以在本站进行查询。

本文标签: