From 1f480997601b097c00c335e19ed1d9cbace3f4c0 Mon Sep 17 00:00:00 2001 From: sidsharma2002 Date: Thu, 13 Apr 2023 06:30:31 +0530 Subject: [PATCH 1/4] frag prob 1 --- .../example/androidconcepts/MainActivity.kt | 34 ++++++++ .../problems/problem1/FragProbOneFragment.kt | 83 +++++++++++++++++++ app/src/main/res/layout/activity_main.xml | 42 +--------- 3 files changed, 121 insertions(+), 38 deletions(-) create mode 100644 app/src/main/java/com/example/androidconcepts/fragments/problems/problem1/FragProbOneFragment.kt diff --git a/app/src/main/java/com/example/androidconcepts/MainActivity.kt b/app/src/main/java/com/example/androidconcepts/MainActivity.kt index 542ecfb..ec557a2 100644 --- a/app/src/main/java/com/example/androidconcepts/MainActivity.kt +++ b/app/src/main/java/com/example/androidconcepts/MainActivity.kt @@ -2,13 +2,47 @@ package com.example.androidconcepts import android.os.Bundle import androidx.appcompat.app.AppCompatActivity +import com.example.androidconcepts.fragments.problems.problem1.FragProbOneFragment class MainActivity : AppCompatActivity() { + enum class CURRENT_BUILD { + FRAG_PROBLEM_1 + } + + // change this to modify app's state. + private val currentBuild = CURRENT_BUILD.FRAG_PROBLEM_1 + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) // AndroidThreadSpammer().execute() + + if (currentBuild == CURRENT_BUILD.FRAG_PROBLEM_1) { + supportFragmentManager.beginTransaction() + .replace(R.id.root_fragment_container, FragProbOneFragment()) + .commit() + } + } + + override fun onBackPressed() { + +// SOLUTION : manually remove the unintended fragments attached. + +// if (currentBuild == CURRENT_BUILD.FRAG_PROBLEM_1) { +// val currentFragment = supportFragmentManager.findFragmentById(R.id.root_fragment_container) +// ?: return + +// Log.d("frag problem", "current frag : " + (currentFragment as FragProbOneFragment).number) + +// this does the main fix, i.e remove currently unintended shown fragment from the fm. + +// supportFragmentManager.beginTransaction().remove(currentFragment).commit() +// super.onBackPressed() // or supportFragmentManager.popBackStackImmediate() // to avoid finishing activity. +// return +// } + + super.onBackPressed() } } \ No newline at end of file diff --git a/app/src/main/java/com/example/androidconcepts/fragments/problems/problem1/FragProbOneFragment.kt b/app/src/main/java/com/example/androidconcepts/fragments/problems/problem1/FragProbOneFragment.kt new file mode 100644 index 0000000..e40b6f6 --- /dev/null +++ b/app/src/main/java/com/example/androidconcepts/fragments/problems/problem1/FragProbOneFragment.kt @@ -0,0 +1,83 @@ +package com.example.androidconcepts.fragments.problems.problem1 + +import android.graphics.Color +import android.os.Bundle +import android.util.Log +import android.view.Gravity +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.FrameLayout +import android.widget.FrameLayout.LayoutParams +import android.widget.Toast +import androidx.fragment.app.Fragment +import com.example.androidconcepts.R +import com.google.android.material.button.MaterialButton + +class FragProbOneFragment : Fragment() { + + private lateinit var button: MaterialButton + + var number = 0 + + fun getInstance(number: Int): FragProbOneFragment { + return FragProbOneFragment().apply { + val bundle = Bundle() + bundle.putInt("number", number) + this.arguments = bundle + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + number = arguments?.getInt("number", 0) ?: 0 + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + val rootView = FrameLayout(requireContext()) + + rootView.layoutParams = ViewGroup.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT + ) + + rootView.setBackgroundColor(Color.WHITE) // hides the other fragments, due to this I never found this issue. + rootView.isClickable = true // prevents clicking through the current fragment. + + button = MaterialButton(requireContext()) + button.text = "Fragment Problem : $number" + val lp = LayoutParams( + LayoutParams.WRAP_CONTENT, + LayoutParams.WRAP_CONTENT + ) + lp.gravity = Gravity.TOP + lp.topMargin = number * 50 + button.layoutParams = lp + + rootView.addView(button) + + return rootView + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + Log.d("frag problem", "frags size : " + requireActivity().supportFragmentManager.fragments.size + + " backstack count : " + requireActivity().supportFragmentManager.backStackEntryCount) + + button.setOnClickListener { + val ft = requireActivity().supportFragmentManager.beginTransaction() + ft.replace(R.id.root_fragment_container, getInstance(number + 1)) + + if (number % 2 == 1) // only add if current fragment number is odd. eg: frag 3 to 4 is added but 4 to 5 isn't. + ft.addToBackStack(null) + + ft.commit() + } + } + +} \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 712b091..e905b89 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -7,43 +7,9 @@ android:background="@color/white" tools:context=".MainActivity"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file From 9f4591f3c6507a5ed52979846e4772ab407b5861 Mon Sep 17 00:00:00 2001 From: sidsharma2002 Date: Thu, 13 Apr 2023 06:48:50 +0530 Subject: [PATCH 2/4] refactor --- app/src/main/java/com/example/androidconcepts/MainActivity.kt | 4 +--- .../fragments/problems/problem1/FragProbOneFragment.kt | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/example/androidconcepts/MainActivity.kt b/app/src/main/java/com/example/androidconcepts/MainActivity.kt index ec557a2..e7eee42 100644 --- a/app/src/main/java/com/example/androidconcepts/MainActivity.kt +++ b/app/src/main/java/com/example/androidconcepts/MainActivity.kt @@ -39,10 +39,8 @@ class MainActivity : AppCompatActivity() { // this does the main fix, i.e remove currently unintended shown fragment from the fm. // supportFragmentManager.beginTransaction().remove(currentFragment).commit() -// super.onBackPressed() // or supportFragmentManager.popBackStackImmediate() // to avoid finishing activity. -// return // } - super.onBackPressed() + super.onBackPressed() // internally does popBackStackImmediate or finish activity. } } \ No newline at end of file diff --git a/app/src/main/java/com/example/androidconcepts/fragments/problems/problem1/FragProbOneFragment.kt b/app/src/main/java/com/example/androidconcepts/fragments/problems/problem1/FragProbOneFragment.kt index e40b6f6..d2c4be7 100644 --- a/app/src/main/java/com/example/androidconcepts/fragments/problems/problem1/FragProbOneFragment.kt +++ b/app/src/main/java/com/example/androidconcepts/fragments/problems/problem1/FragProbOneFragment.kt @@ -20,7 +20,7 @@ class FragProbOneFragment : Fragment() { var number = 0 - fun getInstance(number: Int): FragProbOneFragment { + private fun getInstance(number: Int): FragProbOneFragment { return FragProbOneFragment().apply { val bundle = Bundle() bundle.putInt("number", number) From 787434b3db31d923a5d32e2501679e8834397e22 Mon Sep 17 00:00:00 2001 From: sidsharma2002 Date: Sun, 16 Apr 2023 06:20:14 +0530 Subject: [PATCH 3/4] frag prob 2 --- .../example/androidconcepts/MainActivity.kt | 28 ++++- .../problems/problem2/FragProbTwoFragment.kt | 113 ++++++++++++++++++ 2 files changed, 135 insertions(+), 6 deletions(-) create mode 100644 app/src/main/java/com/example/androidconcepts/fragments/problems/problem2/FragProbTwoFragment.kt diff --git a/app/src/main/java/com/example/androidconcepts/MainActivity.kt b/app/src/main/java/com/example/androidconcepts/MainActivity.kt index e7eee42..0c79921 100644 --- a/app/src/main/java/com/example/androidconcepts/MainActivity.kt +++ b/app/src/main/java/com/example/androidconcepts/MainActivity.kt @@ -3,15 +3,16 @@ package com.example.androidconcepts import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import com.example.androidconcepts.fragments.problems.problem1.FragProbOneFragment +import com.example.androidconcepts.fragments.problems.problem2.FragProbTwoFragment class MainActivity : AppCompatActivity() { enum class CURRENT_BUILD { - FRAG_PROBLEM_1 + FRAG_PROBLEM_1, FRAG_PROBLEM_2 } // change this to modify app's state. - private val currentBuild = CURRENT_BUILD.FRAG_PROBLEM_1 + private val currentBuild = CURRENT_BUILD.FRAG_PROBLEM_2 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -20,17 +21,29 @@ class MainActivity : AppCompatActivity() { // AndroidThreadSpammer().execute() if (currentBuild == CURRENT_BUILD.FRAG_PROBLEM_1) { - supportFragmentManager.beginTransaction() - .replace(R.id.root_fragment_container, FragProbOneFragment()) - .commit() + if (savedInstanceState == null) { + supportFragmentManager.beginTransaction() + .replace(R.id.root_fragment_container, FragProbOneFragment()) + .commit() + } + } else if (currentBuild == CURRENT_BUILD.FRAG_PROBLEM_2) { + if (savedInstanceState == null) { + supportFragmentManager.beginTransaction() + .replace(R.id.root_fragment_container, FragProbTwoFragment()) + .commit() + } } } override fun onBackPressed() { -// SOLUTION : manually remove the unintended fragments attached. +// ------------------------------ +// ---------- for FRAG PROBLEM 1 // if (currentBuild == CURRENT_BUILD.FRAG_PROBLEM_1) { + +// SOLUTION : manually remove the unintended fragments attached. + // val currentFragment = supportFragmentManager.findFragmentById(R.id.root_fragment_container) // ?: return @@ -41,6 +54,9 @@ class MainActivity : AppCompatActivity() { // supportFragmentManager.beginTransaction().remove(currentFragment).commit() // } +// ---------- for FRAG PROBLEM 1 +// ------------------------------ + super.onBackPressed() // internally does popBackStackImmediate or finish activity. } } \ No newline at end of file diff --git a/app/src/main/java/com/example/androidconcepts/fragments/problems/problem2/FragProbTwoFragment.kt b/app/src/main/java/com/example/androidconcepts/fragments/problems/problem2/FragProbTwoFragment.kt new file mode 100644 index 0000000..288f31b --- /dev/null +++ b/app/src/main/java/com/example/androidconcepts/fragments/problems/problem2/FragProbTwoFragment.kt @@ -0,0 +1,113 @@ +package com.example.androidconcepts.fragments.problems.problem2 + +import android.graphics.Color +import android.os.Bundle +import android.util.Log +import android.view.Gravity +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.FrameLayout +import androidx.fragment.app.Fragment +import com.example.androidconcepts.R +import com.google.android.material.button.MaterialButton + +class FragProbTwoFragment : Fragment() { + + private lateinit var button: MaterialButton + + var number = 0 + + private fun getInstance(number: Int): FragProbTwoFragment { + return FragProbTwoFragment().apply { + val bundle = Bundle() + bundle.putInt("number", number) + this.arguments = bundle + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + number = arguments?.getInt("number", 0) ?: 0 + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + val rootView = FrameLayout(requireContext()) + + rootView.layoutParams = ViewGroup.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT + ) + + rootView.setBackgroundColor(Color.WHITE) // hides the other fragments, due to this I never found this issue. + rootView.isClickable = true // prevents clicking through the current fragment. + + button = MaterialButton(requireContext()) + button.text = "Fragment Problem : $number" + val lp = FrameLayout.LayoutParams( + FrameLayout.LayoutParams.WRAP_CONTENT, + FrameLayout.LayoutParams.WRAP_CONTENT + ) + lp.gravity = Gravity.TOP + lp.topMargin = number * 50 + button.layoutParams = lp + + rootView.addView(button) + + return rootView + } + + override fun onSaveInstanceState(outState: Bundle) { + Log.d("frag problem", "$number onSaveInstanceState") + super.onSaveInstanceState(outState) + } + + override fun onStart() { + // state is set to false here. eg : minimize app, state = true, reopen, state = false. + Log.d("frag problem", "$number onStart, isStateSaved : $isStateSaved") + super.onStart() + } + + override fun onResume() { + Log.d("frag problem", "$number onResume, isStateSaved : $isStateSaved") + super.onResume() + } + + override fun onPause() { + + // as far as experimented, isStateSaved is false in onPause. + Log.d("frag problem", "$number onPause, isStateSaved : $isStateSaved") + + super.onPause() + + // very common case in which we do a fragment transaction on a network call result. + // user initiates the network call and minimises the app and before opening again the result is fetched + // and hence a frag transaction is committed. + // then perhaps, the app would crash throwing "java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState". + + button.postDelayed({ + val fm = requireActivity().supportFragmentManager + fm.beginTransaction() + .replace(R.id.root_fragment_container, this.getInstance(number + 1)) + .addToBackStack(null) + .commit() + }, 1500L) + } + + override fun onStop() { + // weirdly, isStateSaved is true here even when onSaveInstanceState is called yet. + // also if I commit frag transactions in onStop then isStateSaved is false. + Log.d("frag problem", "$number onStop, isStateSaved : $isStateSaved") + super.onStop() + } + + override fun onDestroyView() { + Log.d("frag problem", "$number onDestroyView, isStateSaved : $isStateSaved") + super.onDestroyView() + } + +} \ No newline at end of file From dc00b2e37b5bf69ee362f600daf62c38f955d803 Mon Sep 17 00:00:00 2001 From: sidsharma2002 Date: Thu, 24 Aug 2023 20:48:22 +0530 Subject: [PATCH 4/4] experiment with dispatchers performance --- .../androidconcepts/common/Benchmark.kt | 2 +- .../performance/CoroutinePerformanceTest.kt | 65 +++++++++++++++++++ .../performance/ProcessImageUseCase.kt | 13 ++++ 3 files changed, 79 insertions(+), 1 deletion(-) diff --git a/app/src/test/java/com/example/androidconcepts/common/Benchmark.kt b/app/src/test/java/com/example/androidconcepts/common/Benchmark.kt index 6a47d08..caf148c 100644 --- a/app/src/test/java/com/example/androidconcepts/common/Benchmark.kt +++ b/app/src/test/java/com/example/androidconcepts/common/Benchmark.kt @@ -5,6 +5,6 @@ fun measureTime(action: () -> Unit): Long { action.invoke() val endTime = System.currentTimeMillis() val measuredTime = endTime - startTime - println("measure time = " + measuredTime + "ms") + println("measured time = " + measuredTime + "ms") return measuredTime } \ No newline at end of file diff --git a/app/src/test/java/com/example/androidconcepts/coroutines/performance/CoroutinePerformanceTest.kt b/app/src/test/java/com/example/androidconcepts/coroutines/performance/CoroutinePerformanceTest.kt index 06b26bd..2024d28 100644 --- a/app/src/test/java/com/example/androidconcepts/coroutines/performance/CoroutinePerformanceTest.kt +++ b/app/src/test/java/com/example/androidconcepts/coroutines/performance/CoroutinePerformanceTest.kt @@ -1,9 +1,13 @@ package com.example.androidconcepts.coroutines.performance +import com.example.androidconcepts.common.BgThreadPoster +import com.example.androidconcepts.common.measureTime import kotlinx.coroutines.* import org.junit.Test import java.util.concurrent.CountDownLatch import java.util.concurrent.Executors +import java.util.concurrent.Future +import java.util.concurrent.ThreadPoolExecutor import java.util.concurrent.TimeUnit class CoroutinePerformanceTest { @@ -98,4 +102,65 @@ class CoroutinePerformanceTest { joinAll(backgroundJob, userJob) } + + @Test + fun performanceTestOfNThreads() { + val processImageUseCase = ProcessImageUseCase() + val executor = Executors.newFixedThreadPool(8) + + val futures = mutableListOf>() + + measureTime { + repeat(1000) { + val result = executor.submit { + processImageUseCase.processBlocking(10) + } + + futures += result + } + + futures.forEach { + it.get() + } + } + + // RESULTS : + // 1005 Threads, 1000 repeat, 10ms task time -> 1291 ms + // 500 Threads, 1000 repeat, 10ms task time -> 1036 ms + // 100 Threads, 1000 repeat, 10ms task time -> 690 ms + // 50 Threads, 1000 repeat, 10ms task time -> 649 ms + // 8 (no of cores) Threads, 1000 repeat, 10ms task time -> 1262 seconds + // 5 Threads, 1000 repeat, 10ms task time -> 2 seconds + } + + @Test + fun performanceTestOfNThreadedDispatcher() { + val processImageUseCase = ProcessImageUseCase() + val executor = Executors.newFixedThreadPool(5).asCoroutineDispatcher() + val scope = CoroutineScope(executor) + + val jobs = mutableListOf() + + measureTime { + runBlocking { + repeat(1000) { + val result = scope.launch { + processImageUseCase.processBlocking(10) + } + + jobs += result + } + + jobs.joinAll() + } + } + + // RESULTS : + // 1005 Threads, 1000 repeat, 10ms task time -> 1344 ms + // 500 Threads, 1000 repeat, 10ms task time -> 1056 ms + // 100 Threads, 1000 repeat, 10ms task time -> 785 ms + // 50 Threads, 1000 repeat, 10ms task time -> 743 ms + // 8 (no of cores) Threads, 1000 repeat, 10ms task time -> 1326 seconds + // 5 Threads, 1000 repeat, 10ms task time -> 2 seconds + } } \ No newline at end of file diff --git a/app/src/test/java/com/example/androidconcepts/coroutines/performance/ProcessImageUseCase.kt b/app/src/test/java/com/example/androidconcepts/coroutines/performance/ProcessImageUseCase.kt index 0956024..d1df4f1 100644 --- a/app/src/test/java/com/example/androidconcepts/coroutines/performance/ProcessImageUseCase.kt +++ b/app/src/test/java/com/example/androidconcepts/coroutines/performance/ProcessImageUseCase.kt @@ -16,4 +16,17 @@ class ProcessImageUseCase { println("wow, your device has really fast cores") } } + + fun processBlocking(time: Int) { + // simulate algorithm invocation for time ms in a "blocking manner". + // (fake load just to avoid elimination optimization by compilers) + val finishTimeMillis = System.currentTimeMillis() + time + var counter = 0 + while (System.currentTimeMillis() < finishTimeMillis) { + counter++ + } + if (counter > 250000000) { + println("wow, your device has really fast cores") + } + } }