From 4863dfb2e8b936d489ae54ec49febeb6591496a0 Mon Sep 17 00:00:00 2001 From: oupson Date: Sat, 25 Jan 2020 12:10:49 +0100 Subject: [PATCH] Async for ApngDecoder Improvement in app --- .idea/caches/build_file_checksums.ser | Bin 596 -> 596 bytes .../oupson/apng/ExperimentalApngDecoder.kt | 161 ++++++++++++++++++ .../src/main/java/oupson/apng/Loader.kt | 34 ++-- .../oupson/apngcreator/ApngDecoderFragment.kt | 54 ++++++ .../java/oupson/apngcreator/JavaFragment.java | 29 +++- .../java/oupson/apngcreator/MainActivity.kt | 52 +++++- .../src/main/res/layout/activity_creator.xml | 6 + .../main/res/layout/fragment_apng_decoder.xml | 13 ++ .../src/main/res/menu/navigation_menu.xml | 12 +- app-test/src/main/res/values/colors.xml | 2 +- app-test/src/main/res/values/strings.xml | 5 +- 11 files changed, 332 insertions(+), 36 deletions(-) create mode 100644 app-test/src/main/java/oupson/apngcreator/ApngDecoderFragment.kt create mode 100644 app-test/src/main/res/layout/activity_creator.xml create mode 100644 app-test/src/main/res/layout/fragment_apng_decoder.xml diff --git a/.idea/caches/build_file_checksums.ser b/.idea/caches/build_file_checksums.ser index 746bc388d1528e72b18c8459d1786d12db099170..bb552e810b755e6639abb0e690ce3b1a309b9933 100644 GIT binary patch delta 15 Xcmcb@a)o8W43;}eeF_`r>|g`{HMRzi delta 15 Xcmcb@a)o8W43?W7VtY5v*}(_^Ht7b} diff --git a/apng_library/src/main/java/oupson/apng/ExperimentalApngDecoder.kt b/apng_library/src/main/java/oupson/apng/ExperimentalApngDecoder.kt index a53aa1f..6757e25 100644 --- a/apng_library/src/main/java/oupson/apng/ExperimentalApngDecoder.kt +++ b/apng_library/src/main/java/oupson/apng/ExperimentalApngDecoder.kt @@ -5,8 +5,11 @@ import android.graphics.* import android.graphics.drawable.Drawable import android.net.Uri import android.util.Log +import android.widget.ImageView import androidx.annotation.RawRes import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import oupson.apng.chunks.IHDR import oupson.apng.chunks.fcTL @@ -23,6 +26,11 @@ import java.util.zip.CRC32 // TODO DOC CODE class ExperimentalApngDecoder { + interface Callback { + fun onSuccess(drawable : Drawable) + fun onError(error : java.lang.Exception) + } + companion object { // TODO Change TAG private const val TAG = "ExperimentalApngDecoder" @@ -365,6 +373,159 @@ class ExperimentalApngDecoder { decodeApng(FileInputStream(Loader.load(context, url)), speed) } + /** + * Load Apng into an imageView, asynchronously + * @param file File to decode. + * @param imageView Image View. + * @param speed Optional parameter. + * @param callback [ExperimentalApngDecoder.Callback] to handle success and error + */ + @Suppress("unused") + @JvmStatic + @JvmOverloads + fun decodeApngAsyncInto(file : File, imageView : ImageView, speed: Float = 1f, callback: Callback? = null) { + GlobalScope.launch(Dispatchers.IO) { + try { + val drawable = decodeApng(FileInputStream(file), speed) + withContext(Dispatchers.Main) { + imageView.setImageDrawable(drawable) + (drawable as? CustomAnimationDrawable)?.start() + callback?.onSuccess(drawable) + } + } catch (e : java.lang.Exception) { + withContext(Dispatchers.Main) { + callback?.onError(e) + } + } + } + } + + /** + * Load Apng into an imageView, asynchronously + * @param context Context + * @param uri Uri to load + * @param imageView Image View. + * @param speed Optional parameter. + * @param callback [ExperimentalApngDecoder.Callback] to handle success and error + */ + @Suppress("unused") + @JvmStatic + @JvmOverloads + fun decodeApngAsyncInto(context : Context, uri : Uri, imageView: ImageView, speed: Float = 1f, callback: Callback? = null) { + val inputStream = context.contentResolver.openInputStream(uri) ?: throw Exception("Failed to open InputStream, InputStream is null") + GlobalScope.launch(Dispatchers.IO) { + try { + val drawable = decodeApng(inputStream, speed) + withContext(Dispatchers.Main) { + imageView.setImageDrawable(drawable) + (drawable as? CustomAnimationDrawable)?.start() + callback?.onSuccess(drawable) + } + } catch (e : java.lang.Exception) { + withContext(Dispatchers.Main) { + callback?.onError(e) + } + } + } + } + + /** + * Load Apng into an imageView, asynchronously + * @param context Context + * @param res Raw resource to load + * @param imageView Image View. + * @param speed Optional parameter. + * @param callback [ExperimentalApngDecoder.Callback] to handle success and error + */ + @Suppress("unused") + @JvmStatic + @JvmOverloads + fun decodeApngAsyncInto(context : Context, @RawRes res : Int, imageView: ImageView, speed: Float = 1f, callback: Callback? = null) { + GlobalScope.launch(Dispatchers.IO) { + try { + val drawable = decodeApng(context.resources.openRawResource(res), speed) + withContext(Dispatchers.Main) { + imageView.setImageDrawable(drawable) + (drawable as? CustomAnimationDrawable)?.start() + callback?.onSuccess(drawable) + } + } catch (e : java.lang.Exception) { + withContext(Dispatchers.Main) { + callback?.onError(e) + } + } + } + + } + + /** + * Load Apng into an imageView, asynchronously + * @param context Context + * @param url URL to load + * @param imageView Image View. + * @param speed Optional parameter. + * @param callback [ExperimentalApngDecoder.Callback] to handle success and error + */ + @Suppress("unused") + @JvmStatic + @JvmOverloads + fun decodeApngAsyncInto(context : Context, url : URL, imageView: ImageView, speed: Float = 1f, callback: Callback? = null) { + GlobalScope.launch(Dispatchers.IO) { + try { + val drawable = decodeApng(FileInputStream(Loader.load(context, url)), speed) + withContext(Dispatchers.Main) { + imageView.setImageDrawable(drawable) + (drawable as? CustomAnimationDrawable)?.start() + callback?.onSuccess(drawable) + } + } catch (e : java.lang.Exception) { + withContext(Dispatchers.Main) { + callback?.onError(e) + } + } + } + } + + /** + * Load Apng into an imageView, asynchronously + * @param context Context + * @param string URL to load + * @param imageView Image View. + * @param speed Optional parameter. + * @param callback [ExperimentalApngDecoder.Callback] to handle success and error + */ + @Suppress("unused") + @JvmStatic + @JvmOverloads + fun decodeApngAsyncInto(context : Context, string: String, imageView: ImageView, speed: Float = 1f, callback: Callback? = null) { + GlobalScope.launch(Dispatchers.IO) { + try { + if (string.startsWith("http://") || string.startsWith("https://")) { + decodeApngAsyncInto(context, URL(string), imageView, speed, callback) + } else if(File(string).exists()) { + var pathToLoad = if (string.startsWith("content://")) string else "file://$string" + pathToLoad = pathToLoad.replace("%", "%25").replace("#", "%23") + decodeApngAsyncInto(context, Uri.parse(pathToLoad), imageView, speed, callback) + } else if (string.startsWith("file://android_asset/")) { + val drawable = decodeApng(context.assets.open(string.replace("file:///android_asset/", "")), speed) + withContext(Dispatchers.Main) { + imageView.setImageDrawable(drawable) + (drawable as? CustomAnimationDrawable)?.start() + callback?.onSuccess(drawable) + } + } else { + withContext(Dispatchers.Main) { + callback?.onError(java.lang.Exception("Cannot open string")) + } + } + } catch (e : java.lang.Exception) { + withContext(Dispatchers.Main) { + callback?.onError(e) + } + } + } + } + /** * Generate a correct IHDR from the IHDR chunk of the APNG * @param ihdrOfApng The IHDR of the APNG diff --git a/apng_library/src/main/java/oupson/apng/Loader.kt b/apng_library/src/main/java/oupson/apng/Loader.kt index be2e3f6..7b46062 100644 --- a/apng_library/src/main/java/oupson/apng/Loader.kt +++ b/apng_library/src/main/java/oupson/apng/Loader.kt @@ -5,7 +5,6 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import java.io.BufferedInputStream import java.io.File -import java.io.IOException import java.net.URL class Loader { @@ -16,22 +15,23 @@ class Loader { * @param url Url of the file to download * @return [ByteArray] of the file */ - @Throws(IOException::class) - suspend fun load(context: Context, url: URL): File = withContext(Dispatchers.IO) { - val currentDir = context.filesDir - val fileTXT = File(currentDir, "apngLoader.txt") - val filePNG = File(currentDir, "apngLoader.png") - if (fileTXT.exists() && url.toString() == fileTXT.readText()) { - filePNG - } else { - val connection = url.openConnection() - connection.connect() - val input = BufferedInputStream(connection.getInputStream()) - val bytes = input.readBytes() - input.close() - fileTXT.writeText(url.toString()) - filePNG.apply { writeBytes(bytes) } + // @Throws(IOException::class, java.io.FileNotFoundException::class, java.lang.Exception::class) + suspend fun load(context: Context, url: URL) = + withContext(Dispatchers.IO) { + val currentDir = context.filesDir + val fileTXT = File(currentDir, "apngLoader.txt") + val filePNG = File(currentDir, "apngLoader.png") + if (fileTXT.exists() && url.toString() == fileTXT.readText()) { + filePNG + } else { + val connection = url.openConnection() + connection.connect() + val input = BufferedInputStream(connection.getInputStream()) + val bytes = input.readBytes() + input.close() + fileTXT.writeText(url.toString()) + filePNG.apply { writeBytes(bytes) } + } } - } } } \ No newline at end of file diff --git a/app-test/src/main/java/oupson/apngcreator/ApngDecoderFragment.kt b/app-test/src/main/java/oupson/apngcreator/ApngDecoderFragment.kt new file mode 100644 index 0000000..c2e1979 --- /dev/null +++ b/app-test/src/main/java/oupson/apngcreator/ApngDecoderFragment.kt @@ -0,0 +1,54 @@ +package oupson.apngcreator + + +import android.graphics.drawable.Drawable +import android.os.Bundle +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.ImageView +import androidx.fragment.app.Fragment +import oupson.apng.ExperimentalApngDecoder +import java.net.URL + +class ApngDecoderFragment : Fragment() { + companion object { + private const val TAG = "ApngDecoderFragment" + @JvmStatic + fun newInstance() = + ApngDecoderFragment() + } + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + // Inflate the layout for this fragment + val view = inflater.inflate(R.layout.fragment_apng_decoder, container, false) + + val imageView : ImageView = view.findViewById(R.id.apngDecoderImageView) ?: return view + + if (context != null) { + ExperimentalApngDecoder.decodeApngAsyncInto( + this.context!!, + URL("http://littlesvr.ca/apng/images/GenevaDrive.png"), + imageView, + callback = object : ExperimentalApngDecoder.Callback { + override fun onSuccess(drawable: Drawable) { + if (BuildConfig.DEBUG) + Log.i(TAG, "onSuccess()") + } + + override fun onError(error: Exception) { + if (BuildConfig.DEBUG) + Log.e(TAG, "onError", error) + } + }) + } + + return view + } + + +} diff --git a/app-test/src/main/java/oupson/apngcreator/JavaFragment.java b/app-test/src/main/java/oupson/apngcreator/JavaFragment.java index 76fe2d3..5eb91a7 100644 --- a/app-test/src/main/java/oupson/apngcreator/JavaFragment.java +++ b/app-test/src/main/java/oupson/apngcreator/JavaFragment.java @@ -1,6 +1,8 @@ package oupson.apngcreator; +import android.content.Context; +import android.graphics.drawable.Drawable; import android.os.Bundle; import android.util.Log; import android.view.LayoutInflater; @@ -10,9 +12,9 @@ import android.widget.ImageView; import androidx.fragment.app.Fragment; -import kotlin.Unit; -import oupson.apng.ApngAnimator; -import oupson.apng.ApngAnimatorKt; +import org.jetbrains.annotations.NotNull; + +import oupson.apng.ExperimentalApngDecoder; public class JavaFragment extends Fragment { @@ -22,10 +24,16 @@ public class JavaFragment extends Fragment { public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { Log.i(TAG, "onCreateView()"); + View v = inflater.inflate(R.layout.fragment_java, container, false); + String imageUrl = "https://metagif.files.wordpress.com/2015/01/bugbuckbunny.png"; ImageView imageView = v.findViewById(R.id.javaImageView); - if (imageView != null) { + + Context context = this.getContext(); + + if (imageView != null && context != null) { + /* ApngAnimator a = ApngAnimatorKt.loadApng(imageView, imageUrl); a.onLoaded((animator) -> { animator.setOnFrameChangeLister((index) -> { @@ -36,6 +44,19 @@ public class JavaFragment extends Fragment { }); return Unit.INSTANCE; }); + */ + ExperimentalApngDecoder.decodeApngAsyncInto(context, imageUrl, imageView, 1f, new ExperimentalApngDecoder.Callback() { + @Override + public void onSuccess(@NotNull Drawable drawable) { + Log.i(TAG, "Success"); + } + + @Override + public void onError(@NotNull Exception error) { + Log.e(TAG, "Error", error); + } + }); + } return v; } diff --git a/app-test/src/main/java/oupson/apngcreator/MainActivity.kt b/app-test/src/main/java/oupson/apngcreator/MainActivity.kt index 71bb185..c8301fe 100644 --- a/app-test/src/main/java/oupson/apngcreator/MainActivity.kt +++ b/app-test/src/main/java/oupson/apngcreator/MainActivity.kt @@ -16,6 +16,9 @@ import org.jetbrains.anko.startActivity class MainActivity : AppCompatActivity() { + companion object { + private const val TAG = "MainActivity" + } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -39,7 +42,7 @@ class MainActivity : AppCompatActivity() { navigationView.setNavigationItemSelectedListener { menuItem : MenuItem -> when(menuItem.itemId) { - R.id.menu_kotlin_activity -> { + R.id.menu_kotlin_fragment -> { if (selected != 0) { supportFragmentManager.beginTransaction().apply { replace(R.id.fragment_container, KotlinFragment.newInstance()) @@ -48,7 +51,7 @@ class MainActivity : AppCompatActivity() { selected = 0 } } - R.id.menu_java_activity -> { + R.id.menu_java_fragment -> { if (selected != 1) { supportFragmentManager.beginTransaction().apply { replace(R.id.fragment_container, JavaFragment()) @@ -57,6 +60,15 @@ class MainActivity : AppCompatActivity() { selected = 1 } } + R.id.menu_apng_decoder_fragment -> { + if (selected != 2) { + supportFragmentManager.beginTransaction().apply { + replace(R.id.fragment_container, ApngDecoderFragment.newInstance()) + addToBackStack(null) + }.commit() + selected = 2 + } + } } drawer_layout.closeDrawer(GravityCompat.START) @@ -64,12 +76,36 @@ class MainActivity : AppCompatActivity() { return@setNavigationItemSelectedListener true } - supportFragmentManager.beginTransaction().apply { - add(R.id.fragment_container, KotlinFragment.newInstance(), "KotlinFragment") - }.commit() - - - navigationView.setCheckedItem(R.id.menu_kotlin_activity) + if (intent.hasExtra("fragment")) { + when(intent.getStringExtra("fragment")) { + "kotlin" -> { + supportFragmentManager.beginTransaction().apply { + add(R.id.fragment_container, KotlinFragment.newInstance(), "KotlinFragment") + }.commit() + navigationView.setCheckedItem(R.id.menu_kotlin_fragment) + selected = 0 + } + "java" -> { + supportFragmentManager.beginTransaction().apply { + add(R.id.fragment_container, JavaFragment()) + }.commit() + navigationView.setCheckedItem(R.id.menu_java_fragment) + selected = 1 + } + "apng_decoder" -> { + supportFragmentManager.beginTransaction().apply { + add(R.id.fragment_container, ApngDecoderFragment.newInstance()) + }.commit() + navigationView.setCheckedItem(R.id.menu_apng_decoder_fragment) + selected = 2 + } + } + } else { + supportFragmentManager.beginTransaction().apply { + add(R.id.fragment_container, KotlinFragment.newInstance(), "KotlinFragment") + }.commit() + navigationView.setCheckedItem(R.id.menu_kotlin_fragment) + } } private fun setUpBottomAppBarShapeAppearance() { diff --git a/app-test/src/main/res/layout/activity_creator.xml b/app-test/src/main/res/layout/activity_creator.xml new file mode 100644 index 0000000..b1a8bea --- /dev/null +++ b/app-test/src/main/res/layout/activity_creator.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/app-test/src/main/res/layout/fragment_apng_decoder.xml b/app-test/src/main/res/layout/fragment_apng_decoder.xml new file mode 100644 index 0000000..3f69a01 --- /dev/null +++ b/app-test/src/main/res/layout/fragment_apng_decoder.xml @@ -0,0 +1,13 @@ + + + + + \ No newline at end of file diff --git a/app-test/src/main/res/menu/navigation_menu.xml b/app-test/src/main/res/menu/navigation_menu.xml index f67ef07..8124d58 100644 --- a/app-test/src/main/res/menu/navigation_menu.xml +++ b/app-test/src/main/res/menu/navigation_menu.xml @@ -2,12 +2,16 @@ + android:title="@string/menu_title_kotlin_fragment" /> + android:title="@string/menu_title_java_fragment" /> + + \ No newline at end of file diff --git a/app-test/src/main/res/values/colors.xml b/app-test/src/main/res/values/colors.xml index 956c5d3..a345e91 100644 --- a/app-test/src/main/res/values/colors.xml +++ b/app-test/src/main/res/values/colors.xml @@ -16,7 +16,7 @@ #999999 - #ffffff + #FFFFFF #fff diff --git a/app-test/src/main/res/values/strings.xml b/app-test/src/main/res/values/strings.xml index 4391244..2b8fe23 100644 --- a/app-test/src/main/res/values/strings.xml +++ b/app-test/src/main/res/values/strings.xml @@ -10,8 +10,9 @@ Play Pause - Kotlin Activity - Java Activity + Kotlin Fragment + Java Fragment + ApngDecoder Create