Skip to content

Commit

Permalink
change: using PixelCopyCompat allows for the use of advanced PixelCop…
Browse files Browse the repository at this point in the history
…y when the API is less than 26

change: the implementation of the `takeScreenshot()` method has been changed from `View#getDrawingCache()` to `View#drawToBitmap()`
  • Loading branch information
YenalyLiew committed May 18, 2024
1 parent f5c74fa commit 9cc4a80
Show file tree
Hide file tree
Showing 7 changed files with 155 additions and 30 deletions.
8 changes: 8 additions & 0 deletions .idea/deploymentTargetSelector.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 11 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ dependencyResolutionManagement {

```kotlin
dependencies {
implementation("com.github.YenalyLiew:CircularRevealSwitch:0.4.5")
implementation("com.github.YenalyLiew:CircularRevealSwitch:0.4.6")
}
```

Expand Down Expand Up @@ -181,6 +181,16 @@ builder.setSwitcher();

## 更新

### v0.4.6

1. 使用 PixelCopyCompat,可使在 API 小于 26 时使用先进的 PixelCopy。虽然使用了反射调用,但整体速度还是比 `View#getDrawingCache()` 快不少

图上部分为 PixelCopy 在 API 24 时的表现(平均耗时 40ms 左右),图下部分为 `View#getDrawingCache()` 的表现(平均耗时 60-70ms 左右)

![](./docs/img/pixelcopy_android_7.png)

2. `takeScreenshot()` 方法实现由 `View#getDrawingCache()` 修改为 `View#drawToBitmap()`,速度快一倍以上(平均耗时 30ms 左右)

### v0.4.5

1. 修复多 Activity 下,日间夜间切换动画无法正常播放的问题
Expand Down
12 changes: 11 additions & 1 deletion README_EN.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ Add dependency in your module's build.gradle.kts

```kotlin
dependencies {
implementation("com.github.YenalyLiew:CircularRevealSwitch:0.4.5")
implementation("com.github.YenalyLiew:CircularRevealSwitch:0.4.6")
}
```

Expand Down Expand Up @@ -164,6 +164,16 @@ builder.setSwitcher();

## Updates

### v0.4.6

1. Using PixelCopyCompat allows for the use of advanced PixelCopy when the API is less than 26. Although reflection calls are used, the overall speed is still much faster than `View#getDrawingCache()`.

The top part of the image shows the performance of PixelCopy at API 24 (average time consumption around 40ms), while the bottom part shows the performance of `View#getDrawingCache()` (average time consumption around 60-70ms).

![](./docs/img/pixelcopy_android_7.png)

2. The implementation of the `takeScreenshot()` method has been changed from `View#getDrawingCache()` to `View#drawToBitmap()`, which is more than twice as fast (average time consumption around 30ms).

### v0.4.5

1. Fixed the issue where the day-night switch animation cannot play normally under multiple Activities
Expand Down
2 changes: 1 addition & 1 deletion circularrevealswitch/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ android {

afterEvaluate {
publishing {
val versionName = "0.4.5"
val versionName = "0.4.6"
publications {
create<MavenPublication>("release") {
from(components["release"])
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import android.widget.ImageView
import androidx.core.animation.addListener
import androidx.core.view.children
import androidx.core.view.doOnAttach
import androidx.core.view.drawToBitmap
import androidx.core.view.isInvisible
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
Expand Down Expand Up @@ -178,35 +179,32 @@ abstract class CircularRevealSwitch<T : CRSwitchBuilder<T>>(crSwitchBuilder: T)
*/
protected open fun Window.takeScreenshotCompat(): Bitmap {
val root = decorView.rootView
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val thread = HandlerThread("Screenshot")
try {
val bitmap = Bitmap.createBitmap(root.width, root.height, Bitmap.Config.ARGB_8888)
val thread = HandlerThread("Screenshot")
thread.start()
var isSuccess = false
try {
val latch = CountDownLatch(1)
var time = System.currentTimeMillis()
PixelCopy.request(this, bitmap, { copyResult ->
isSuccess = copyResult == PixelCopy.SUCCESS
latch.countDown()
}, Handler(thread.looper))
isSuccess = latch.await(1000, TimeUnit.MILLISECONDS) && isSuccess
time = System.currentTimeMillis() - time
if (isSuccess) {
if (DEBUG) {
Log.d(TAG, "take screenshot by PixelCopy, time: $time ms")
}
return bitmap
val latch = CountDownLatch(1)
var time = System.currentTimeMillis()
PixelCopyCompat.request(this, bitmap, { copyResult ->
isSuccess = copyResult == PixelCopy.SUCCESS
latch.countDown()
}, Handler(thread.looper))
isSuccess = latch.await(1000, TimeUnit.MILLISECONDS) && isSuccess
time = System.currentTimeMillis() - time
if (isSuccess) {
if (DEBUG) {
Log.d(TAG, "take screenshot by PixelCopy, time: $time ms")
}
return takeScreenshot()
} catch (e: Exception) {
e.printStackTrace()
return takeScreenshot()
} finally {
thread.quit()
return bitmap
}
return takeScreenshot()
} catch (e: Exception) {
e.printStackTrace()
return takeScreenshot()
} finally {
thread.quit()
}
return takeScreenshot()
}

/**
Expand All @@ -216,13 +214,10 @@ abstract class CircularRevealSwitch<T : CRSwitchBuilder<T>>(crSwitchBuilder: T)
*
* @return Bitmap Returns a screenshot of the window.
*/
@Suppress("DEPRECATION")
protected open fun Window.takeScreenshot(): Bitmap {
val root = decorView.rootView
var time = System.currentTimeMillis()
root.isDrawingCacheEnabled = true
val bitmap = Bitmap.createBitmap(root.drawingCache)
root.isDrawingCacheEnabled = false
val bitmap = root.drawToBitmap()
time = System.currentTimeMillis() - time
if (DEBUG) {
Log.d(TAG, "take screenshot by default, time: $time ms")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package com.yenaly.circularrevealswitch

import android.annotation.SuppressLint
import android.graphics.Bitmap
import android.graphics.Rect
import android.os.Build
import android.os.Handler
import android.view.PixelCopy
import android.view.Surface
import android.view.View
import android.view.Window
import androidx.annotation.RequiresApi

object PixelCopyCompat {

fun request(
window: Window, srcRect: Rect?, dest: Bitmap,
listener: PixelCopy.OnPixelCopyFinishedListener,
listenerHandler: Handler,
) {
when {
Build.VERSION.SDK_INT >= 26 -> Api26Impl.request(
window, srcRect, dest,
listener, listenerHandler
)

else -> Api24Impl.request(
window, dest, listener,
listenerHandler
)
}
}

fun request(
window: Window, dest: Bitmap,
listener: PixelCopy.OnPixelCopyFinishedListener,
listenerHandler: Handler,
) = request(window, null, dest, listener, listenerHandler)

@RequiresApi(26)
internal object Api26Impl {
fun request(
window: Window, srcRect: Rect?, dest: Bitmap,
listener: PixelCopy.OnPixelCopyFinishedListener,
listenerHandler: Handler,
) = PixelCopy.request(window, srcRect, dest, listener, listenerHandler)
}

internal object Api24Impl {
fun request(
window: Window, dest: Bitmap,
listener: PixelCopy.OnPixelCopyFinishedListener,
listenerHandler: Handler,
) {
val insets = Rect()
val surface = sourceForWindow(window, insets)
PixelCopy.request(surface, dest, listener, listenerHandler)
}

@SuppressLint("PrivateApi")
private fun sourceForWindow(source: Window?, outInsets: Rect): Surface {
requireNotNull(source) { "source is null" }
requireNotNull(source.peekDecorView()) { "Only able to copy windows with decor views" }
var surface: Surface? = null
val dv = source.peekDecorView()
val root = dv?.let {
View::class.java.getDeclaredField("mAttachInfo").apply {
isAccessible = true
}[it]?.let { attachInfo ->
attachInfo.javaClass.getDeclaredField("mViewRootImpl").apply {
isAccessible = true
}[attachInfo]
}
} // as ViewRootImpl
if (root != null) {
surface = root.javaClass.getDeclaredField("mSurface").apply {
isAccessible = true
}[root] as Surface
val windowAttrs = root.javaClass.getDeclaredField("mWindowAttributes").apply {
isAccessible = true
}[root] // as WindowManager.LayoutParams
val surfaceInsets = windowAttrs.javaClass.getDeclaredField("surfaceInsets").apply {
isAccessible = true
}[windowAttrs] as Rect
// val width = root.javaClass.getDeclaredField("mWidth").apply {
// isAccessible = true
// }[root] as Int
// val height = root.javaClass.getDeclaredField("mHeight").apply {
// isAccessible = true
// }[root] as Int
val width = dv.rootView.width
val height = dv.rootView.height
outInsets.set(
surfaceInsets.left, surfaceInsets.top,
width + surfaceInsets.left, height + surfaceInsets.top
)
}
require(surface != null && surface.isValid) { "Window doesn't have a backing surface!" }
return surface
}
}
}
Binary file added docs/img/pixelcopy_android_7.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 9cc4a80

Please sign in to comment.