├── .gitignore
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── com
│ │ └── techyourchance
│ │ └── multithreading
│ │ ├── DefaultConfiguration.java
│ │ ├── MainActivity.java
│ │ ├── MyApplication.java
│ │ ├── common
│ │ ├── BaseFragment.java
│ │ ├── BaseObservable.java
│ │ ├── ScreensNavigator.java
│ │ ├── ToolbarManipulator.java
│ │ ├── dependencyinjection
│ │ │ ├── ApplicationCompositionRoot.java
│ │ │ └── PresentationCompositionRoot.java
│ │ └── math
│ │ │ └── MathUtils.java
│ │ ├── demonstrations
│ │ ├── atomicity
│ │ │ └── AtomicityDemonstrationFragment.java
│ │ ├── bestjavaimplementation
│ │ │ ├── ComputeFactorialUseCase.java
│ │ │ ├── MyBlockingQueue.java
│ │ │ └── ProducerConsumerBenchmarkUseCase.java
│ │ ├── customhandler
│ │ │ └── CustomHandlerDemonstrationFragment.java
│ │ ├── designasynctask
│ │ │ ├── DesignWithAsyncTaskDemonstrationFragment.java
│ │ │ ├── MyBlockingQueue.java
│ │ │ └── ProducerConsumerBenchmarkUseCase.java
│ │ ├── designcoroutines
│ │ │ ├── DesignWithCoroutinesDemonstrationFragment.kt
│ │ │ ├── MyBlockingQueue.kt
│ │ │ └── ProducerConsumerBenchmarkUseCase.kt
│ │ ├── designrxjava
│ │ │ ├── DesignWithRxJavaDemonstrationFragment.java
│ │ │ ├── MyBlockingQueue.java
│ │ │ └── ProducerConsumerBenchmarkUseCase.java
│ │ ├── designthread
│ │ │ ├── DesignWithThreadsDemonstrationFragment.java
│ │ │ ├── MyBlockingQueue.java
│ │ │ └── ProducerConsumerBenchmarkUseCase.java
│ │ ├── designthreadpool
│ │ │ ├── DesignWithThreadPoolDemonstrationFragment.java
│ │ │ ├── MyBlockingQueue.java
│ │ │ └── ProducerConsumerBenchmarkUseCase.java
│ │ ├── designthreadposter
│ │ │ ├── DesignWithThreadPosterDemonstrationFragment.java
│ │ │ ├── MyBlockingQueue.java
│ │ │ └── ProducerConsumerBenchmarkUseCase.java
│ │ ├── synchronization
│ │ │ └── SynchronizationDemonstration.java
│ │ ├── threadwait
│ │ │ └── ThreadWaitDemonstrationFragment.java
│ │ ├── uihandler
│ │ │ └── UiHandlerDemonstrationFragment.java
│ │ ├── uithread
│ │ │ └── UiThreadDemonstrationFragment.java
│ │ └── visibility
│ │ │ └── VisibilityDemonstration.java
│ │ ├── exercises
│ │ ├── exercise1
│ │ │ └── Exercise1Fragment.java
│ │ ├── exercise10
│ │ │ ├── ComputeFactorialUseCase.kt
│ │ │ ├── Exercise10Fragment.kt
│ │ │ └── tips.txt
│ │ ├── exercise2
│ │ │ └── Exercise2Fragment.java
│ │ ├── exercise3
│ │ │ └── Exercise3Fragment.java
│ │ ├── exercise4
│ │ │ └── Exercise4Fragment.java
│ │ ├── exercise5
│ │ │ └── Exercise5Fragment.java
│ │ ├── exercise6
│ │ │ └── Exercise6Fragment.java
│ │ ├── exercise7
│ │ │ ├── ComputeFactorialUseCase.java
│ │ │ └── Exercise7Fragment.java
│ │ ├── exercise8
│ │ │ ├── ComputeFactorialUseCase.java
│ │ │ └── Exercise8Fragment.java
│ │ └── exercise9
│ │ │ ├── ComputeFactorialUseCase.java
│ │ │ └── Exercise9Fragment.java
│ │ ├── home
│ │ ├── HomeArrayAdapter.java
│ │ ├── HomeFragment.java
│ │ └── ScreenReachableFromHome.java
│ │ └── solutions
│ │ ├── exercise1
│ │ └── SolutionExercise1Fragment.java
│ │ ├── exercise10
│ │ ├── ComputeFactorialUseCase.kt
│ │ └── Exercise10Fragment.kt
│ │ ├── exercise2
│ │ └── SolutionExercise2Fragment.java
│ │ ├── exercise3
│ │ └── SolutionExercise3Fragment.java
│ │ ├── exercise4
│ │ └── SolutionExercise4Fragment.java
│ │ ├── exercise5
│ │ └── SolutionExercise5Fragment.java
│ │ ├── exercise6
│ │ ├── ComputeFactorialUseCase.java
│ │ └── SolutionExercise6Fragment.java
│ │ ├── exercise7
│ │ ├── ComputeFactorialUseCase.java
│ │ └── SolutionExercise7Fragment.java
│ │ ├── exercise8
│ │ ├── ComputeFactorialUseCase.java
│ │ └── SolutionExercise8Fragment.java
│ │ └── exercise9
│ │ ├── ComputeFactorialUseCase.java
│ │ └── SolutionExercise9Fragment.java
│ └── res
│ ├── drawable-hdpi
│ └── ic_arrow_back.png
│ ├── drawable-mdpi
│ └── ic_arrow_back.png
│ ├── drawable-v24
│ └── ic_launcher_foreground.xml
│ ├── drawable-xhdpi
│ └── ic_arrow_back.png
│ ├── drawable-xxhdpi
│ └── ic_arrow_back.png
│ ├── drawable-xxxhdpi
│ └── ic_arrow_back.png
│ ├── drawable
│ └── ic_launcher_background.xml
│ ├── layout
│ ├── activity_main.xml
│ ├── fragment_atomicity_demonstration.xml
│ ├── fragment_custom_looper_demonstration.xml
│ ├── fragment_design_with_coroutines_demonstration.xml
│ ├── fragment_design_with_thread_demonstration.xml
│ ├── fragment_design_with_thread_pool_demonstration.xml
│ ├── fragment_exercise_1.xml
│ ├── fragment_exercise_10.xml
│ ├── fragment_exercise_2.xml
│ ├── fragment_exercise_3.xml
│ ├── fragment_exercise_4.xml
│ ├── fragment_exercise_5.xml
│ ├── fragment_exercise_6.xml
│ ├── fragment_exercise_7.xml
│ ├── fragment_exercise_8.xml
│ ├── fragment_exercise_9.xml
│ ├── fragment_home.xml
│ ├── fragment_thread_wait_demonstration.xml
│ ├── fragment_ui_handler_demonstration.xml
│ ├── fragment_ui_thread_demonstration.xml
│ └── list_item_screen_reachable_from_home.xml
│ ├── mipmap-anydpi-v26
│ ├── ic_launcher.xml
│ └── ic_launcher_round.xml
│ ├── mipmap-hdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-mdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-xhdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-xxhdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-xxxhdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ └── values
│ ├── colors.xml
│ ├── dimens.xml
│ ├── strings.xml
│ └── styles.xml
├── build.gradle
├── fragmenthelper
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── com
│ │ └── techyourchance
│ │ └── fragmenthelper
│ │ ├── FragmentContainerWrapper.java
│ │ ├── FragmentHelper.java
│ │ └── HierarchicalFragment.java
│ └── res
│ └── values
│ └── strings.xml
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── release.keystore
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | #built application files
3 | *.apk
4 | *.ap_
5 |
6 | # files for the dex VM
7 | *.dex
8 |
9 | # Java class files
10 | *.class
11 |
12 | # generated files
13 | bin/
14 | gen/
15 |
16 | # Local configuration file (sdk path, etc)
17 | local.properties
18 |
19 | # Windows thumbnail db
20 | Thumbs.db
21 |
22 | # OSX files
23 | .DS_Store
24 |
25 | # Android Studio
26 | .idea/
27 | .gradle
28 | build/
29 | *.iml
30 | captures/
31 | .externalNativeBuild
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 | apply plugin: 'kotlin-android'
3 |
4 | android {
5 | namespace "com.techyourchance.multithreading"
6 | compileSdk 34
7 | defaultConfig {
8 | applicationId "com.techyourchance.multithreading"
9 | minSdkVersion 24
10 | targetSdkVersion 34
11 | versionCode 1
12 | versionName "1.0"
13 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
14 | }
15 |
16 | signingConfigs {
17 | release {
18 | storeFile file('../release.keystore')
19 | storePassword 'release'
20 | keyAlias 'release'
21 | keyPassword 'release'
22 | }
23 | }
24 |
25 | buildTypes {
26 | release {
27 | minifyEnabled true
28 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
29 | signingConfig signingConfigs.release
30 | }
31 | }
32 | compileOptions {
33 | sourceCompatibility JavaVersion.VERSION_17
34 | targetCompatibility JavaVersion.VERSION_17
35 | }
36 |
37 | }
38 |
39 | dependencies {
40 | implementation project(':fragmenthelper')
41 |
42 | implementation 'androidx.appcompat:appcompat:1.1.0'
43 | implementation 'com.techyourchance:threadposter:1.0.1'
44 |
45 | implementation 'io.reactivex.rxjava2:rxjava:2.2.13'
46 | implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
47 |
48 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
49 | implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.2'
50 | implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.2'
51 | }
52 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
13 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/multithreading/DefaultConfiguration.java:
--------------------------------------------------------------------------------
1 | package com.techyourchance.multithreading;
2 |
3 | public class DefaultConfiguration {
4 |
5 | public static final int DEFAULT_NUM_OF_MESSAGES = 1000;
6 | public static final int DEFAULT_BLOCKING_QUEUE_SIZE = 5;
7 | public static final int DEFAULT_PRODUCER_DELAY_MS = 0;
8 |
9 | public static final int DEFAULT_FACTORIAL_TIMEOUT_MS = 1000;
10 |
11 | }
12 |
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/multithreading/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.techyourchance.multithreading;
2 |
3 | import androidx.annotation.NonNull;
4 | import androidx.appcompat.app.AppCompatActivity;
5 |
6 | import android.os.Bundle;
7 | import android.view.Choreographer;
8 | import android.view.View;
9 | import android.view.ViewGroup;
10 | import android.widget.ImageButton;
11 | import android.widget.TextView;
12 |
13 | import com.techyourchance.fragmenthelper.FragmentContainerWrapper;
14 | import com.techyourchance.multithreading.common.ToolbarManipulator;
15 | import com.techyourchance.multithreading.common.ScreensNavigator;
16 | import com.techyourchance.multithreading.common.dependencyinjection.PresentationCompositionRoot;
17 |
18 | import java.lang.reflect.Field;
19 | import java.lang.reflect.Modifier;
20 |
21 | public class MainActivity extends AppCompatActivity implements
22 | FragmentContainerWrapper,
23 | ToolbarManipulator {
24 |
25 | private PresentationCompositionRoot mPresentationCompositionRoot;
26 | private ScreensNavigator mScreensNavigator;
27 |
28 | private ImageButton mBtnBack;
29 | private TextView mTxtScreenTitle;
30 |
31 | @Override
32 | protected void onCreate(Bundle savedInstanceState) {
33 | super.onCreate(savedInstanceState);
34 | setContentView(R.layout.activity_main);
35 |
36 | mPresentationCompositionRoot = new PresentationCompositionRoot(
37 | this,
38 | ((MyApplication)getApplication()).getApplicationCompositionRoot()
39 | );
40 |
41 | mScreensNavigator = mPresentationCompositionRoot.getScreensNavigator();
42 |
43 | mBtnBack = findViewById(R.id.btn_back);
44 | mTxtScreenTitle = findViewById(R.id.txt_screen_title);
45 |
46 | mBtnBack.setOnClickListener(new View.OnClickListener() {
47 | @Override
48 | public void onClick(View v) {
49 | mScreensNavigator.navigateUp();
50 | }
51 | });
52 |
53 | if (savedInstanceState == null) {
54 | mScreensNavigator.toHomeScreen();
55 | }
56 |
57 | reduceChoreographerSkippedFramesWarningThreshold();
58 | }
59 |
60 | private void reduceChoreographerSkippedFramesWarningThreshold() {
61 | Field field = null;
62 | try {
63 | field = Choreographer.class.getDeclaredField("SKIPPED_FRAME_WARNING_LIMIT" );
64 | field.setAccessible(true);
65 | field.setInt(field, field.getModifiers() & ~Modifier.FINAL);
66 | field.set(null, 1);
67 | } catch (NoSuchFieldException|IllegalAccessException e) {
68 | // probably failed to change Choreographer's field, but it's not critical
69 | }
70 | }
71 |
72 | @Override
73 | public void onBackPressed() {
74 | mScreensNavigator.navigateBack();
75 | }
76 |
77 | @NonNull
78 | @Override
79 | public ViewGroup getFragmentContainer() {
80 | return findViewById(R.id.frame_content);
81 | }
82 |
83 | @Override
84 | public void setScreenTitle(String screenTitle) {
85 | mTxtScreenTitle.setText(screenTitle);
86 | }
87 |
88 | @Override
89 | public void showUpButton() {
90 | mBtnBack.setVisibility(View.VISIBLE);
91 | }
92 |
93 | @Override
94 | public void hideUpButton() {
95 | mBtnBack.setVisibility(View.INVISIBLE);
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/multithreading/MyApplication.java:
--------------------------------------------------------------------------------
1 | package com.techyourchance.multithreading;
2 |
3 | import android.app.Application;
4 |
5 | import com.techyourchance.multithreading.common.dependencyinjection.ApplicationCompositionRoot;
6 |
7 | import static kotlinx.coroutines.DispatchersKt.IO_PARALLELISM_PROPERTY_NAME;
8 |
9 | public class MyApplication extends Application {
10 |
11 | private final ApplicationCompositionRoot mApplicationCompositionRoot =
12 | new ApplicationCompositionRoot();
13 |
14 | public ApplicationCompositionRoot getApplicationCompositionRoot() {
15 | return mApplicationCompositionRoot;
16 | }
17 |
18 | @Override
19 | public void onCreate() {
20 | super.onCreate();
21 | System.setProperty(IO_PARALLELISM_PROPERTY_NAME, String.valueOf(Integer.MAX_VALUE));
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/multithreading/common/BaseFragment.java:
--------------------------------------------------------------------------------
1 | package com.techyourchance.multithreading.common;
2 |
3 | import android.os.Bundle;
4 | import android.view.View;
5 |
6 | import com.techyourchance.fragmenthelper.HierarchicalFragment;
7 | import com.techyourchance.multithreading.MyApplication;
8 | import com.techyourchance.multithreading.common.dependencyinjection.PresentationCompositionRoot;
9 | import com.techyourchance.multithreading.home.HomeFragment;
10 |
11 | import androidx.annotation.NonNull;
12 | import androidx.annotation.Nullable;
13 | import androidx.fragment.app.Fragment;
14 |
15 | public abstract class BaseFragment extends Fragment implements HierarchicalFragment {
16 |
17 | private PresentationCompositionRoot mPresentationCompositionRoot;
18 |
19 | protected final PresentationCompositionRoot getCompositionRoot() {
20 | if (mPresentationCompositionRoot == null) {
21 | mPresentationCompositionRoot = new PresentationCompositionRoot(
22 | requireActivity(),
23 | ((MyApplication)requireActivity().getApplication()).getApplicationCompositionRoot()
24 | );
25 | }
26 | return mPresentationCompositionRoot;
27 | }
28 |
29 | @Nullable
30 | @Override
31 | public Fragment getHierarchicalParentFragment() {
32 | return HomeFragment.newInstance();
33 | }
34 |
35 | @Override
36 | public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
37 | ToolbarManipulator toolbarManipulator = getCompositionRoot().getToolbarManipulator();
38 | toolbarManipulator.setScreenTitle(getScreenTitle());
39 | if (getHierarchicalParentFragment() != null) {
40 | toolbarManipulator.showUpButton();
41 | } else {
42 | toolbarManipulator.hideUpButton();
43 | }
44 | }
45 |
46 | protected abstract String getScreenTitle();
47 |
48 | }
49 |
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/multithreading/common/BaseObservable.java:
--------------------------------------------------------------------------------
1 | package com.techyourchance.multithreading.common;
2 |
3 | import java.util.Collections;
4 | import java.util.HashSet;
5 | import java.util.Set;
6 |
7 | public abstract class BaseObservable {
8 |
9 | private final Object MONITOR = new Object();
10 |
11 | private final Set mListeners = new HashSet<>();
12 |
13 |
14 | public void registerListener(LISTENER_CLASS listener) {
15 | synchronized (MONITOR) {
16 | boolean hadNoListeners = mListeners.size() == 0;
17 | mListeners.add(listener);
18 | if (hadNoListeners && mListeners.size() == 1) {
19 | onFirstListenerRegistered();
20 | }
21 | }
22 | }
23 |
24 | public void unregisterListener(LISTENER_CLASS listener) {
25 | synchronized (MONITOR) {
26 | boolean hadOneListener = mListeners.size() == 1;
27 | mListeners.remove(listener);
28 | if (hadOneListener && mListeners.size() == 0) {
29 | onLastListenerUnregistered();
30 | }
31 | }
32 | }
33 |
34 | protected Set getListeners() {
35 | synchronized (MONITOR) {
36 | return Collections.unmodifiableSet(new HashSet<>(mListeners));
37 | }
38 | }
39 |
40 | protected void onFirstListenerRegistered() {
41 |
42 | }
43 |
44 | protected void onLastListenerUnregistered() {
45 |
46 | }
47 |
48 | }
49 |
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/multithreading/common/ToolbarManipulator.java:
--------------------------------------------------------------------------------
1 | package com.techyourchance.multithreading.common;
2 |
3 | public interface ToolbarManipulator {
4 | void setScreenTitle(String screenTitle);
5 | void showUpButton();
6 | void hideUpButton();
7 | }
8 |
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/multithreading/common/dependencyinjection/ApplicationCompositionRoot.java:
--------------------------------------------------------------------------------
1 | package com.techyourchance.multithreading.common.dependencyinjection;
2 |
3 | import android.os.Handler;
4 | import android.os.Looper;
5 | import android.util.Log;
6 |
7 | import com.techyourchance.fragmenthelper.FragmentContainerWrapper;
8 | import com.techyourchance.fragmenthelper.FragmentHelper;
9 | import com.techyourchance.multithreading.common.ScreensNavigator;
10 | import com.techyourchance.multithreading.common.ToolbarManipulator;
11 |
12 | import java.util.concurrent.SynchronousQueue;
13 | import java.util.concurrent.ThreadFactory;
14 | import java.util.concurrent.ThreadPoolExecutor;
15 | import java.util.concurrent.TimeUnit;
16 |
17 | import androidx.fragment.app.FragmentActivity;
18 |
19 | public class ApplicationCompositionRoot {
20 |
21 | private ThreadPoolExecutor mThreadPoolExecutor;
22 |
23 | public ThreadPoolExecutor getThreadPool() {
24 | if (mThreadPoolExecutor == null) {
25 | mThreadPoolExecutor = new ThreadPoolExecutor(
26 | 10,
27 | Integer.MAX_VALUE,
28 | 10,
29 | TimeUnit.SECONDS,
30 | new SynchronousQueue<>(),
31 | new ThreadFactory() {
32 | @Override
33 | public Thread newThread(Runnable r) {
34 | Log.d("ThreadFactory",
35 | String.format("size %s, active count %s, queue remaining %s",
36 | mThreadPoolExecutor.getPoolSize(),
37 | mThreadPoolExecutor.getActiveCount(),
38 | mThreadPoolExecutor.getQueue().remainingCapacity()
39 | )
40 | );
41 | return new Thread(r);
42 | }
43 | }
44 | );
45 | }
46 | return mThreadPoolExecutor;
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/multithreading/common/dependencyinjection/PresentationCompositionRoot.java:
--------------------------------------------------------------------------------
1 | package com.techyourchance.multithreading.common.dependencyinjection;
2 |
3 | import android.os.Handler;
4 | import android.os.Looper;
5 | import android.util.Log;
6 |
7 | import com.techyourchance.fragmenthelper.FragmentContainerWrapper;
8 | import com.techyourchance.fragmenthelper.FragmentHelper;
9 | import com.techyourchance.multithreading.common.ToolbarManipulator;
10 | import com.techyourchance.multithreading.common.ScreensNavigator;
11 |
12 | import java.util.concurrent.SynchronousQueue;
13 | import java.util.concurrent.ThreadFactory;
14 | import java.util.concurrent.ThreadPoolExecutor;
15 | import java.util.concurrent.TimeUnit;
16 |
17 | import androidx.fragment.app.FragmentActivity;
18 |
19 | public class PresentationCompositionRoot {
20 |
21 | private final FragmentActivity mActivity;
22 | private final ApplicationCompositionRoot mApplicationCompositionRoot;
23 |
24 | public PresentationCompositionRoot(FragmentActivity activity, ApplicationCompositionRoot applicationCompositionRoot) {
25 | mActivity = activity;
26 | mApplicationCompositionRoot = applicationCompositionRoot;
27 | }
28 |
29 | public ScreensNavigator getScreensNavigator() {
30 | return new ScreensNavigator(getFragmentHelper());
31 | }
32 |
33 | private FragmentHelper getFragmentHelper() {
34 | return new FragmentHelper(mActivity, getFragmentContainerWrapper(), mActivity.getSupportFragmentManager());
35 | }
36 |
37 | private FragmentContainerWrapper getFragmentContainerWrapper() {
38 | return (FragmentContainerWrapper) mActivity;
39 | }
40 |
41 | public ToolbarManipulator getToolbarManipulator() {
42 | return (ToolbarManipulator) mActivity;
43 | }
44 |
45 | public Handler getUiHandler() {
46 | return new Handler(Looper.getMainLooper());
47 | }
48 |
49 | public ThreadPoolExecutor getThreadPool() {
50 | return mApplicationCompositionRoot.getThreadPool();
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/multithreading/common/math/MathUtils.java:
--------------------------------------------------------------------------------
1 | package com.techyourchance.multithreading.common.math;
2 |
3 | public class MathUtils {
4 |
5 | /**
6 | * Multiply two longs. Copied from Math.multiplyExact(long, long).
7 | * @throws ArithmeticException in case of an overflow
8 | */
9 | public static long multiplyExact(long x, long y) {
10 | long r = x * y;
11 | long ax = Math.abs(x);
12 | long ay = Math.abs(y);
13 | if (((ax | ay) >>> 31 != 0)) {
14 | // Some bits greater than 2^31 that might cause overflow
15 | // Check the result using the divide operator
16 | // and check for the special case of Long.MIN_VALUE * -1
17 | if (((y != 0) && (r / y != x)) ||
18 | (x == Long.MIN_VALUE && y == -1)) {
19 | throw new ArithmeticException("long overflow");
20 | }
21 | }
22 | return r;
23 | }
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/multithreading/demonstrations/atomicity/AtomicityDemonstrationFragment.java:
--------------------------------------------------------------------------------
1 | package com.techyourchance.multithreading.demonstrations.atomicity;
2 |
3 | import android.annotation.SuppressLint;
4 | import android.os.Bundle;
5 | import android.os.Handler;
6 | import android.os.Looper;
7 | import android.view.LayoutInflater;
8 | import android.view.View;
9 | import android.view.ViewGroup;
10 | import android.widget.Button;
11 | import android.widget.TextView;
12 |
13 | import com.techyourchance.multithreading.R;
14 | import com.techyourchance.multithreading.common.BaseFragment;
15 |
16 | import androidx.annotation.NonNull;
17 | import androidx.annotation.Nullable;
18 | import androidx.fragment.app.Fragment;
19 |
20 |
21 | @SuppressLint("SetTextI18n")
22 | public class AtomicityDemonstrationFragment extends BaseFragment {
23 |
24 | private static final int COUNT_UP_TO = 1000;
25 | private static final int NUM_OF_COUNTER_THREADS = 100;
26 |
27 | public static Fragment newInstance() {
28 | return new AtomicityDemonstrationFragment();
29 | }
30 |
31 | private Button mBtnStartCount;
32 | private TextView mTxtFinalCount;
33 |
34 | private Handler mUiHandler = new Handler(Looper.getMainLooper());
35 |
36 | private volatile int mCount;
37 |
38 | @Nullable
39 | @Override
40 | public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
41 | View view = inflater.inflate(R.layout.fragment_atomicity_demonstration, container, false);
42 |
43 | mTxtFinalCount = view.findViewById(R.id.txt_final_count);
44 |
45 | mBtnStartCount = view.findViewById(R.id.btn_start_count);
46 | mBtnStartCount.setOnClickListener(new View.OnClickListener() {
47 | @Override
48 | public void onClick(View v) {
49 | startCount();
50 | }
51 | });
52 |
53 | return view;
54 | }
55 |
56 | @Override
57 | protected String getScreenTitle() {
58 | return "";
59 | }
60 |
61 | @Override
62 | public void onStart() {
63 | super.onStart();
64 | }
65 |
66 | @Override
67 | public void onStop() {
68 | super.onStop();
69 | }
70 |
71 | private void startCount() {
72 | mCount = 0;
73 | mTxtFinalCount.setText("");
74 | mBtnStartCount.setEnabled(false);
75 |
76 | for (int i = 0; i < NUM_OF_COUNTER_THREADS; i++) {
77 | startCountThread();
78 | }
79 |
80 | mUiHandler.postDelayed(new Runnable() {
81 | @Override
82 | public void run() {
83 | mTxtFinalCount.setText(String.valueOf(mCount));
84 | mBtnStartCount.setEnabled(true);
85 | }
86 | }, NUM_OF_COUNTER_THREADS * 20);
87 | }
88 |
89 | private void startCountThread() {
90 | new Thread(new Runnable() {
91 | @Override
92 | public void run() {
93 | for (int i = 0; i < COUNT_UP_TO; i++) {
94 | mCount++;
95 | }
96 | }
97 | }).start();
98 | }
99 |
100 | }
101 |
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/multithreading/demonstrations/bestjavaimplementation/MyBlockingQueue.java:
--------------------------------------------------------------------------------
1 | package com.techyourchance.multithreading.demonstrations.bestjavaimplementation;
2 |
3 | import java.util.LinkedList;
4 | import java.util.Queue;
5 |
6 | /**
7 | * Simplified implementation of blocking queue.
8 | */
9 | class MyBlockingQueue {
10 |
11 | private final Object QUEUE_LOCK = new Object();
12 |
13 | private final int mCapacity;
14 | private final Queue mQueue = new LinkedList<>();
15 |
16 | private int mCurrentSize = 0;
17 |
18 | MyBlockingQueue(int capacity) {
19 | mCapacity = capacity;
20 | }
21 |
22 | /**
23 | * Inserts the specified element into this queue, waiting if necessary
24 | * for space to become available.
25 | *
26 | * @param number the element to add
27 | */
28 | public void put(int number) {
29 | synchronized (QUEUE_LOCK) {
30 | while (mCurrentSize >= mCapacity) {
31 | try {
32 | QUEUE_LOCK.wait();
33 | } catch (InterruptedException e) {
34 | return;
35 | }
36 | }
37 |
38 | mQueue.offer(number);
39 | mCurrentSize++;
40 | QUEUE_LOCK.notifyAll();
41 | }
42 | }
43 |
44 | /**
45 | * Retrieves and removes the head of this queue, waiting if necessary
46 | * until an element becomes available.
47 | *
48 | * @return the head of this queue
49 | */
50 | public int take() {
51 | synchronized (QUEUE_LOCK) {
52 | while (mCurrentSize <= 0) {
53 | try {
54 | QUEUE_LOCK.wait();
55 | } catch (InterruptedException e) {
56 | return 0;
57 | }
58 | }
59 | mCurrentSize--;
60 | QUEUE_LOCK.notifyAll();
61 | return mQueue.poll();
62 | }
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/multithreading/demonstrations/bestjavaimplementation/ProducerConsumerBenchmarkUseCase.java:
--------------------------------------------------------------------------------
1 | package com.techyourchance.multithreading.demonstrations.bestjavaimplementation;
2 |
3 | import com.techyourchance.multithreading.DefaultConfiguration;
4 | import com.techyourchance.multithreading.common.BaseObservable;
5 | import com.techyourchance.threadposter.BackgroundThreadPoster;
6 | import com.techyourchance.threadposter.UiThreadPoster;
7 |
8 | public class ProducerConsumerBenchmarkUseCase extends BaseObservable {
9 |
10 | public static interface Listener {
11 | void onBenchmarkCompleted(Result result);
12 | }
13 |
14 | public static class Result {
15 | private final long mExecutionTime;
16 | private final int mNumOfReceivedMessages;
17 |
18 | public Result(long executionTime, int numOfReceivedMessages) {
19 | mExecutionTime = executionTime;
20 | mNumOfReceivedMessages = numOfReceivedMessages;
21 | }
22 |
23 | public long getExecutionTime() {
24 | return mExecutionTime;
25 | }
26 |
27 | public int getNumOfReceivedMessages() {
28 | return mNumOfReceivedMessages;
29 | }
30 | }
31 |
32 | private static final int NUM_OF_MESSAGES = DefaultConfiguration.DEFAULT_NUM_OF_MESSAGES;
33 | private static final int BLOCKING_QUEUE_CAPACITY = DefaultConfiguration.DEFAULT_BLOCKING_QUEUE_SIZE;
34 |
35 | private final Object LOCK = new Object();
36 |
37 | private final UiThreadPoster mUiThreadPoster = new UiThreadPoster();
38 | private final BackgroundThreadPoster mBackgroundThreadPoster = new BackgroundThreadPoster();
39 |
40 | private final MyBlockingQueue mBlockingQueue = new MyBlockingQueue(BLOCKING_QUEUE_CAPACITY);
41 |
42 | private int mNumOfFinishedConsumers;
43 |
44 | private int mNumOfReceivedMessages;
45 |
46 | public void startBenchmarkAndNotify() {
47 | mBackgroundThreadPoster.post(() -> {
48 |
49 | mNumOfReceivedMessages = 0;
50 | mNumOfFinishedConsumers = 0;
51 | long startTimestamp = System.currentTimeMillis();
52 |
53 | // producers init thread
54 | mBackgroundThreadPoster.post(() -> {
55 | for (int i = 0; i < NUM_OF_MESSAGES; i++) {
56 | startNewProducer(i);
57 | }
58 | });
59 |
60 | // consumers init thread
61 | mBackgroundThreadPoster.post(() -> {
62 | for (int i = 0; i < NUM_OF_MESSAGES; i++) {
63 | startNewConsumer();
64 | }
65 | });
66 |
67 | waitForAllConsumersToFinish();
68 |
69 | Result result;
70 | synchronized (LOCK) {
71 | result = new Result(
72 | System.currentTimeMillis() - startTimestamp,
73 | mNumOfReceivedMessages
74 | );
75 | }
76 |
77 | notifySuccess(result);
78 |
79 | });
80 |
81 | }
82 |
83 | private void waitForAllConsumersToFinish() {
84 | synchronized (LOCK) {
85 | while (mNumOfFinishedConsumers < NUM_OF_MESSAGES) {
86 | try {
87 | LOCK.wait();
88 | } catch (InterruptedException e) {
89 | return;
90 | }
91 | }
92 | }
93 | }
94 |
95 | private void startNewProducer(final int index) {
96 | mBackgroundThreadPoster.post(() -> {
97 | try {
98 | Thread.sleep(DefaultConfiguration.DEFAULT_PRODUCER_DELAY_MS);
99 | } catch (InterruptedException e) {
100 | return;
101 | }
102 | mBlockingQueue.put(index);
103 | });
104 | }
105 |
106 | private void startNewConsumer() {
107 | mBackgroundThreadPoster.post(() -> {
108 | int message = mBlockingQueue.take();
109 | synchronized (LOCK) {
110 | if (message != -1) {
111 | mNumOfReceivedMessages++;
112 | }
113 | mNumOfFinishedConsumers++;
114 | LOCK.notifyAll();
115 | }
116 | });
117 | }
118 |
119 | private void notifySuccess(Result result) {
120 | mUiThreadPoster.post(() -> {
121 | for (Listener listener : getListeners()) {
122 | listener.onBenchmarkCompleted(result);
123 | }
124 | });
125 | }
126 |
127 |
128 | }
129 |
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/multithreading/demonstrations/customhandler/CustomHandlerDemonstrationFragment.java:
--------------------------------------------------------------------------------
1 | package com.techyourchance.multithreading.demonstrations.customhandler;
2 |
3 | import android.annotation.SuppressLint;
4 | import android.os.Bundle;
5 | import android.util.Log;
6 | import android.view.LayoutInflater;
7 | import android.view.View;
8 | import android.view.ViewGroup;
9 | import android.widget.Button;
10 |
11 | import com.techyourchance.multithreading.R;
12 | import com.techyourchance.multithreading.common.BaseFragment;
13 |
14 | import java.util.concurrent.BlockingQueue;
15 | import java.util.concurrent.LinkedBlockingQueue;
16 |
17 | import androidx.annotation.NonNull;
18 | import androidx.annotation.Nullable;
19 | import androidx.fragment.app.Fragment;
20 |
21 |
22 | @SuppressLint("SetTextI18n")
23 | public class CustomHandlerDemonstrationFragment extends BaseFragment {
24 |
25 | private static final int SECONDS_TO_COUNT = 5;
26 |
27 | public static Fragment newInstance() {
28 | return new CustomHandlerDemonstrationFragment();
29 | }
30 |
31 | private Button mBtnSendJob;
32 |
33 | private CustomHandler mCustomHandler;
34 |
35 | @Nullable
36 | @Override
37 | public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
38 | View view = inflater.inflate(R.layout.fragment_custom_looper_demonstration, container, false);
39 |
40 | mBtnSendJob = view.findViewById(R.id.btn_send_job);
41 | mBtnSendJob.setOnClickListener(new View.OnClickListener() {
42 | @Override
43 | public void onClick(View v) {
44 | sendJob();
45 | }
46 | });
47 |
48 | return view;
49 | }
50 |
51 | @Override
52 | protected String getScreenTitle() {
53 | return "";
54 | }
55 |
56 | @Override
57 | public void onStart() {
58 | super.onStart();
59 | mCustomHandler = new CustomHandler();
60 | }
61 |
62 | @Override
63 | public void onStop() {
64 | super.onStop();
65 | mCustomHandler.stop();
66 | }
67 |
68 | private void sendJob() {
69 | mCustomHandler.post(new Runnable() {
70 | @Override
71 | public void run() {
72 | for (int i=0; i < SECONDS_TO_COUNT; i++) {
73 | try {
74 | Thread.sleep(1000);
75 | } catch (InterruptedException e) {
76 | return;
77 | }
78 | Log.d("CustomHandler", "iteration: " + i);
79 | }
80 | }
81 | });
82 | }
83 |
84 | private class CustomHandler {
85 |
86 | private final Runnable POISON = new Runnable() {
87 | @Override
88 | public void run() {}
89 | };
90 |
91 | private final BlockingQueue mQueue = new LinkedBlockingQueue<>();
92 |
93 | public CustomHandler() {
94 | initWorkerThread();
95 | }
96 |
97 | private void initWorkerThread() {
98 | new Thread(new Runnable() {
99 | @Override
100 | public void run() {
101 | Log.d("CustomHandler", "worker (looper) thread initialized");
102 | while (true) {
103 | Runnable runnable;
104 | try {
105 | runnable = mQueue.take();
106 | } catch (InterruptedException e) {
107 | return;
108 | }
109 | if (runnable == POISON) {
110 | Log.d("CustomHandler", "poison data detected; stopping working thread");
111 | return;
112 | }
113 | runnable.run();
114 | }
115 | }
116 | }).start();
117 | }
118 |
119 | public void stop() {
120 | Log.d("CustomHandler", "injecting poison data into the queue");
121 | mQueue.clear();
122 | mQueue.add(POISON);
123 | }
124 |
125 | public void post(Runnable job) {
126 | mQueue.add(job);
127 | }
128 | }
129 | }
130 |
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/multithreading/demonstrations/designasynctask/DesignWithAsyncTaskDemonstrationFragment.java:
--------------------------------------------------------------------------------
1 | package com.techyourchance.multithreading.demonstrations.designasynctask;
2 |
3 | import android.os.Bundle;
4 | import android.view.LayoutInflater;
5 | import android.view.View;
6 | import android.view.ViewGroup;
7 | import android.widget.Button;
8 | import android.widget.ProgressBar;
9 | import android.widget.TextView;
10 |
11 | import com.techyourchance.multithreading.R;
12 | import com.techyourchance.multithreading.common.BaseFragment;
13 |
14 | import androidx.annotation.NonNull;
15 | import androidx.annotation.Nullable;
16 | import androidx.fragment.app.Fragment;
17 |
18 | public class DesignWithAsyncTaskDemonstrationFragment extends BaseFragment implements ProducerConsumerBenchmarkUseCase.Listener {
19 |
20 | public static Fragment newInstance() {
21 | return new DesignWithAsyncTaskDemonstrationFragment();
22 | }
23 |
24 | private Button mBtnStart;
25 | private ProgressBar mProgressBar;
26 | private TextView mTxtReceivedMessagesCount;
27 | private TextView mTxtExecutionTime;
28 |
29 | private ProducerConsumerBenchmarkUseCase mProducerConsumerBenchmarkUseCase;
30 |
31 | @Override
32 | public void onCreate(@Nullable Bundle savedInstanceState) {
33 | super.onCreate(savedInstanceState);
34 | mProducerConsumerBenchmarkUseCase = new ProducerConsumerBenchmarkUseCase();
35 | }
36 |
37 | @Nullable
38 | @Override
39 | public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
40 | View view = inflater.inflate(R.layout.fragment_design_with_thread_demonstration, container, false);
41 |
42 | mBtnStart = view.findViewById(R.id.btn_start);
43 | mProgressBar = view.findViewById(R.id.progress);
44 | mTxtReceivedMessagesCount = view.findViewById(R.id.txt_received_messages_count);
45 | mTxtExecutionTime = view.findViewById(R.id.txt_execution_time);
46 |
47 | mBtnStart.setOnClickListener(v -> {
48 | mBtnStart.setEnabled(false);
49 | mTxtReceivedMessagesCount.setText("");
50 | mTxtExecutionTime.setText("");
51 | mProgressBar.setVisibility(View.VISIBLE);
52 |
53 | mProducerConsumerBenchmarkUseCase.startBenchmarkAndNotify();
54 | });
55 |
56 | return view;
57 | }
58 |
59 | @Override
60 | protected String getScreenTitle() {
61 | return "";
62 | }
63 |
64 | @Override
65 | public void onStart() {
66 | super.onStart();
67 | mProducerConsumerBenchmarkUseCase.registerListener(this);
68 | }
69 |
70 | @Override
71 | public void onStop() {
72 | super.onStop();
73 | mProducerConsumerBenchmarkUseCase.unregisterListener(this);
74 | }
75 |
76 | @Override
77 | public void onBenchmarkCompleted(ProducerConsumerBenchmarkUseCase.Result result) {
78 | mProgressBar.setVisibility(View.INVISIBLE);
79 | mBtnStart.setEnabled(true);
80 | mTxtReceivedMessagesCount.setText("Received messages: " + result.getNumOfReceivedMessages());
81 | mTxtExecutionTime.setText("Execution time: " + result.getExecutionTime() + "ms");
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/multithreading/demonstrations/designasynctask/MyBlockingQueue.java:
--------------------------------------------------------------------------------
1 | package com.techyourchance.multithreading.demonstrations.designasynctask;
2 |
3 | import java.util.LinkedList;
4 | import java.util.Queue;
5 |
6 | /**
7 | * Simplified implementation of blocking queue.
8 | */
9 | class MyBlockingQueue {
10 |
11 | private final Object QUEUE_LOCK = new Object();
12 |
13 | private final int mCapacity;
14 | private final Queue mQueue = new LinkedList<>();
15 |
16 | private int mCurrentSize = 0;
17 |
18 | MyBlockingQueue(int capacity) {
19 | mCapacity = capacity;
20 | }
21 |
22 | /**
23 | * Inserts the specified element into this queue, waiting if necessary
24 | * for space to become available.
25 | *
26 | * @param number the element to add
27 | */
28 | public void put(int number) {
29 | synchronized (QUEUE_LOCK) {
30 | while (mCurrentSize >= mCapacity) {
31 | try {
32 | QUEUE_LOCK.wait();
33 | } catch (InterruptedException e) {
34 | return;
35 | }
36 | }
37 |
38 | mQueue.offer(number);
39 | mCurrentSize++;
40 | QUEUE_LOCK.notifyAll();
41 | }
42 | }
43 |
44 | /**
45 | * Retrieves and removes the head of this queue, waiting if necessary
46 | * until an element becomes available.
47 | *
48 | * @return the head of this queue
49 | */
50 | public int take() {
51 | synchronized (QUEUE_LOCK) {
52 | while (mCurrentSize <= 0) {
53 | try {
54 | QUEUE_LOCK.wait();
55 | } catch (InterruptedException e) {
56 | return 0;
57 | }
58 | }
59 | mCurrentSize--;
60 | QUEUE_LOCK.notifyAll();
61 | return mQueue.poll();
62 | }
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/multithreading/demonstrations/designasynctask/ProducerConsumerBenchmarkUseCase.java:
--------------------------------------------------------------------------------
1 | package com.techyourchance.multithreading.demonstrations.designasynctask;
2 |
3 | import android.os.AsyncTask;
4 | import android.os.Handler;
5 | import android.os.Looper;
6 |
7 | import com.techyourchance.multithreading.DefaultConfiguration;
8 | import com.techyourchance.multithreading.common.BaseObservable;
9 |
10 | import androidx.annotation.UiThread;
11 |
12 | public class ProducerConsumerBenchmarkUseCase extends BaseObservable {
13 |
14 | public static interface Listener {
15 | void onBenchmarkCompleted(Result result);
16 | }
17 |
18 | public static class Result {
19 | private final long mExecutionTime;
20 | private final int mNumOfReceivedMessages;
21 |
22 | public Result(long executionTime, int numOfReceivedMessages) {
23 | mExecutionTime = executionTime;
24 | mNumOfReceivedMessages = numOfReceivedMessages;
25 | }
26 |
27 | public long getExecutionTime() {
28 | return mExecutionTime;
29 | }
30 |
31 | public int getNumOfReceivedMessages() {
32 | return mNumOfReceivedMessages;
33 | }
34 | }
35 |
36 | private static final int NUM_OF_MESSAGES = DefaultConfiguration.DEFAULT_NUM_OF_MESSAGES;
37 | private static final int BLOCKING_QUEUE_CAPACITY = DefaultConfiguration.DEFAULT_BLOCKING_QUEUE_SIZE;
38 |
39 | private final Object LOCK = new Object();
40 |
41 | private final Handler mUiHandler = new Handler(Looper.getMainLooper());
42 |
43 | private final MyBlockingQueue mBlockingQueue = new MyBlockingQueue(BLOCKING_QUEUE_CAPACITY);
44 |
45 | private int mNumOfFinishedConsumers;
46 |
47 | private int mNumOfReceivedMessages;
48 |
49 | private long mStartTimestamp;
50 |
51 |
52 | public void startBenchmarkAndNotify() {
53 |
54 | synchronized (LOCK) {
55 | mNumOfReceivedMessages = 0;
56 | mNumOfFinishedConsumers = 0;
57 | mStartTimestamp = System.currentTimeMillis();
58 | }
59 |
60 | // watcher-reporter thread
61 | new AsyncTask() {
62 |
63 | @Override
64 | protected void onPreExecute() {
65 | super.onPreExecute();
66 | }
67 |
68 | @Override
69 | protected Void doInBackground(Void... voids) {
70 | synchronized (LOCK) {
71 | while (mNumOfFinishedConsumers < NUM_OF_MESSAGES) {
72 | try {
73 | LOCK.wait();
74 | } catch (InterruptedException e) {
75 | return null;
76 | }
77 | }
78 | }
79 | return null;
80 | }
81 |
82 | @Override
83 | protected void onPostExecute(Void aVoid) {
84 | notifySuccess();
85 | }
86 | }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
87 |
88 | // producers init thread
89 | new Thread(() -> {
90 | for (int i = 0; i < NUM_OF_MESSAGES; i++) {
91 | startNewProducer(i);
92 | }
93 | }).start();
94 |
95 | // consumers init thread
96 | new Thread(() -> {
97 | for (int i = 0; i < NUM_OF_MESSAGES; i++) {
98 | startNewConsumer();
99 | }
100 | }).start();
101 | }
102 |
103 |
104 | private void startNewProducer(final int index) {
105 | new Thread(() -> {
106 | try {
107 | Thread.sleep(DefaultConfiguration.DEFAULT_PRODUCER_DELAY_MS);
108 | } catch (InterruptedException e) {
109 | return;
110 | }
111 | mBlockingQueue.put(index);
112 | }).start();
113 | }
114 |
115 | private void startNewConsumer() {
116 | new Thread(() -> {
117 | int message = mBlockingQueue.take();
118 | synchronized (LOCK) {
119 | if (message != -1) {
120 | mNumOfReceivedMessages++;
121 | }
122 | mNumOfFinishedConsumers++;
123 | LOCK.notifyAll();
124 | }
125 | }).start();
126 | }
127 |
128 | @UiThread
129 | private void notifySuccess() {
130 | Result result;
131 | synchronized (LOCK) {
132 | result =
133 | new Result(
134 | System.currentTimeMillis() - mStartTimestamp,
135 | mNumOfReceivedMessages
136 | );
137 | }
138 | for (Listener listener : getListeners()) {
139 | listener.onBenchmarkCompleted(result);
140 | }
141 | }
142 |
143 |
144 | }
145 |
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/multithreading/demonstrations/designcoroutines/DesignWithCoroutinesDemonstrationFragment.kt:
--------------------------------------------------------------------------------
1 | package com.techyourchance.multithreading.demonstrations.designcoroutines
2 |
3 | import android.os.Bundle
4 | import android.os.Handler
5 | import android.os.Looper
6 | import android.util.Log
7 | import android.view.LayoutInflater
8 | import android.view.View
9 | import android.view.View.*
10 | import android.view.ViewGroup
11 | import android.widget.Button
12 | import android.widget.ProgressBar
13 | import android.widget.TextView
14 | import androidx.fragment.app.Fragment
15 | import com.techyourchance.multithreading.R
16 | import com.techyourchance.multithreading.common.BaseFragment
17 | import kotlinx.coroutines.CoroutineScope
18 | import kotlinx.coroutines.Dispatchers
19 | import kotlinx.coroutines.Job
20 | import kotlinx.coroutines.launch
21 |
22 | class DesignWithCoroutinesDemonstrationFragment : BaseFragment() {
23 |
24 | private lateinit var btnStart: Button
25 | private lateinit var progressBar: ProgressBar
26 | private lateinit var txtReceivedMessagesCount: TextView
27 | private lateinit var txtExecutionTime: TextView
28 | private lateinit var viewUiNonBlockedIndicator : View
29 |
30 | private lateinit var producerConsumerBenchmarkUseCase: ProducerConsumerBenchmarkUseCase
31 |
32 | private var showUiNonBlockedIndication : Boolean = false
33 |
34 | private var job : Job? = null
35 |
36 | override fun onCreate(savedInstanceState: Bundle?) {
37 | super.onCreate(savedInstanceState)
38 | producerConsumerBenchmarkUseCase = ProducerConsumerBenchmarkUseCase()
39 | }
40 |
41 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
42 | val view = inflater.inflate(R.layout.fragment_design_with_coroutines_demonstration, container, false)
43 |
44 | view.apply {
45 | btnStart = findViewById(R.id.btn_start)
46 | progressBar = findViewById(R.id.progress)
47 | txtReceivedMessagesCount = findViewById(R.id.txt_received_messages_count)
48 | txtExecutionTime = findViewById(R.id.txt_execution_time)
49 | viewUiNonBlockedIndicator = findViewById(R.id.view_ui_non_blocked_indicator)
50 | }
51 |
52 | btnStart.setOnClickListener { _ ->
53 | btnStart.isEnabled = false
54 | txtReceivedMessagesCount.text = ""
55 | txtExecutionTime.text = ""
56 | progressBar.visibility = VISIBLE
57 |
58 | job = CoroutineScope(Dispatchers.Main).launch {
59 | val result = producerConsumerBenchmarkUseCase.startBenchmark()
60 | onBenchmarkCompleted(result)
61 | }
62 | }
63 |
64 | return view
65 | }
66 |
67 | override fun getScreenTitle(): String {
68 | return ""
69 | }
70 |
71 | override fun onStart() {
72 | super.onStart()
73 | showUiNonBlockedIndication = true
74 | postUiNonBlockedIndication()
75 | }
76 | override fun onStop() {
77 | Log.d("FragmentCoroutinesDemo", "onStop() called")
78 | super.onStop()
79 | showUiNonBlockedIndication = false
80 | job?.apply { cancel() }
81 | }
82 |
83 | private fun postUiNonBlockedIndication() {
84 | Handler(Looper.getMainLooper()).postDelayed(
85 | {
86 | if (showUiNonBlockedIndication) {
87 | val indicatorVisible = viewUiNonBlockedIndicator.visibility == VISIBLE
88 | viewUiNonBlockedIndicator.visibility = if (indicatorVisible) INVISIBLE else VISIBLE
89 | postUiNonBlockedIndication()
90 | }
91 | },
92 | 500
93 | )
94 | }
95 |
96 |
97 | fun onBenchmarkCompleted(result: ProducerConsumerBenchmarkUseCase.Result) {
98 | Log.d("FragmentCoroutinesDemo", "onBenchmarkCompleted() called")
99 | progressBar.visibility = INVISIBLE
100 | btnStart.isEnabled = true
101 | txtReceivedMessagesCount.text = "Received messages: ${result.numOfReceivedMessages}"
102 | txtExecutionTime.text = "Execution time: ${result.executionTime} ms"
103 | }
104 |
105 | companion object {
106 | fun newInstance(): Fragment {
107 | return DesignWithCoroutinesDemonstrationFragment()
108 | }
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/multithreading/demonstrations/designcoroutines/MyBlockingQueue.kt:
--------------------------------------------------------------------------------
1 | package com.techyourchance.multithreading.demonstrations.designcoroutines
2 |
3 | import java.util.LinkedList
4 | import java.util.concurrent.locks.ReentrantLock
5 | import kotlin.concurrent.withLock
6 |
7 | /**
8 | * Simplified implementation of blocking queue.
9 | */
10 | internal class MyBlockingQueue(private val capacity: Int) {
11 |
12 | private val reentrantLock = ReentrantLock()
13 | private val lockCondition = reentrantLock.newCondition()
14 |
15 | private val queue = LinkedList()
16 |
17 | private var currentSize = 0
18 |
19 | /**
20 | * Inserts the specified element into this queue, waiting if necessary
21 | * for space to become available.
22 | *
23 | * @param number the element to add
24 | */
25 | fun put(number: Int) {
26 | reentrantLock.withLock {
27 | while (currentSize >= capacity) {
28 | try {
29 | lockCondition.await()
30 | } catch (e: InterruptedException) {
31 | return
32 | }
33 | }
34 | queue.offer(number)
35 | currentSize++
36 | lockCondition.signalAll()
37 | }
38 | }
39 |
40 | /**
41 | * Retrieves and removes the head of this queue, waiting if necessary
42 | * until an element becomes available.
43 | *
44 | * @return the head of this queue
45 | */
46 | fun take(): Int {
47 | reentrantLock.withLock {
48 | while (currentSize <= 0) {
49 | try {
50 | lockCondition.await()
51 | } catch (e: InterruptedException) {
52 | return 0
53 | }
54 | }
55 | currentSize--
56 | lockCondition.signalAll()
57 | return queue.poll()
58 | }
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/multithreading/demonstrations/designcoroutines/ProducerConsumerBenchmarkUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.techyourchance.multithreading.demonstrations.designcoroutines
2 |
3 | import android.os.Handler
4 | import android.os.Looper
5 | import android.util.Log
6 | import com.techyourchance.multithreading.DefaultConfiguration
7 | import kotlinx.coroutines.*
8 | import java.util.concurrent.atomic.AtomicInteger
9 |
10 | class ProducerConsumerBenchmarkUseCase {
11 |
12 | class Result(val executionTime: Long, val numOfReceivedMessages: Int)
13 |
14 | private val blockingQueue = MyBlockingQueue(BLOCKING_QUEUE_CAPACITY)
15 |
16 | private val numOfReceivedMessages: AtomicInteger = AtomicInteger(0)
17 | private val numOfProducers: AtomicInteger = AtomicInteger(0)
18 | private val numOfConsumers: AtomicInteger = AtomicInteger(0)
19 |
20 | suspend fun startBenchmark() : Result {
21 |
22 | return withContext(Dispatchers.IO) {
23 |
24 | numOfReceivedMessages.set(0)
25 | numOfProducers.set(0)
26 | numOfConsumers.set(0)
27 |
28 | val startTimestamp = System.currentTimeMillis()
29 |
30 | // producers init coroutine
31 | val deferredProducers = async(Dispatchers.IO + NonCancellable) {
32 | for (i in 0 until NUM_OF_MESSAGES) {
33 | startNewProducer(i)
34 | }
35 | }
36 |
37 | // consumers init coroutine
38 | val deferredConsumers = async(Dispatchers.IO + NonCancellable) {
39 | for (i in 0 until NUM_OF_MESSAGES) {
40 | startNewConsumer()
41 | }
42 | }
43 |
44 | awaitAll(deferredConsumers, deferredProducers)
45 |
46 | Result(
47 | System.currentTimeMillis() - startTimestamp,
48 | numOfReceivedMessages.get()
49 | )
50 | }
51 |
52 | }
53 |
54 | private fun CoroutineScope.startNewProducer(index: Int) = launch(Dispatchers.IO) {
55 | Log.d("Producer", "producer ${numOfProducers.incrementAndGet()} started; " +
56 | "on thread ${Thread.currentThread().name}");
57 | Thread.sleep(DefaultConfiguration.DEFAULT_PRODUCER_DELAY_MS.toLong())
58 | blockingQueue.put(index)
59 | }
60 |
61 | private fun CoroutineScope.startNewConsumer() = launch(Dispatchers.IO) {
62 | Log.d("Consumer", "consumer ${numOfConsumers.incrementAndGet()} started; " +
63 | "on thread ${Thread.currentThread().name}");
64 | val message = blockingQueue.take()
65 | if (message != -1) {
66 | numOfReceivedMessages.incrementAndGet()
67 | }
68 | }
69 |
70 | companion object {
71 | private const val NUM_OF_MESSAGES = DefaultConfiguration.DEFAULT_NUM_OF_MESSAGES
72 | private const val BLOCKING_QUEUE_CAPACITY = DefaultConfiguration.DEFAULT_BLOCKING_QUEUE_SIZE
73 | }
74 |
75 | }
76 |
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/multithreading/demonstrations/designrxjava/DesignWithRxJavaDemonstrationFragment.java:
--------------------------------------------------------------------------------
1 | package com.techyourchance.multithreading.demonstrations.designrxjava;
2 |
3 | import android.os.Bundle;
4 | import android.view.LayoutInflater;
5 | import android.view.View;
6 | import android.view.ViewGroup;
7 | import android.widget.Button;
8 | import android.widget.ProgressBar;
9 | import android.widget.TextView;
10 |
11 | import com.techyourchance.multithreading.R;
12 | import com.techyourchance.multithreading.common.BaseFragment;
13 |
14 | import androidx.annotation.NonNull;
15 | import androidx.annotation.Nullable;
16 | import androidx.fragment.app.Fragment;
17 | import io.reactivex.android.schedulers.AndroidSchedulers;
18 | import io.reactivex.disposables.Disposable;
19 | import io.reactivex.schedulers.Schedulers;
20 |
21 | public class DesignWithRxJavaDemonstrationFragment extends BaseFragment {
22 |
23 | public static Fragment newInstance() {
24 | return new DesignWithRxJavaDemonstrationFragment();
25 | }
26 |
27 | private Button mBtnStart;
28 | private ProgressBar mProgressBar;
29 | private TextView mTxtReceivedMessagesCount;
30 | private TextView mTxtExecutionTime;
31 |
32 | private ProducerConsumerBenchmarkUseCase mProducerConsumerBenchmarkUseCase;
33 |
34 | private @Nullable Disposable mDisposable;
35 |
36 | @Override
37 | public void onCreate(@Nullable Bundle savedInstanceState) {
38 | super.onCreate(savedInstanceState);
39 | mProducerConsumerBenchmarkUseCase = new ProducerConsumerBenchmarkUseCase();
40 | }
41 |
42 | @Nullable
43 | @Override
44 | public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
45 | View view = inflater.inflate(R.layout.fragment_design_with_thread_demonstration, container, false);
46 |
47 | mBtnStart = view.findViewById(R.id.btn_start);
48 | mProgressBar = view.findViewById(R.id.progress);
49 | mTxtReceivedMessagesCount = view.findViewById(R.id.txt_received_messages_count);
50 | mTxtExecutionTime = view.findViewById(R.id.txt_execution_time);
51 |
52 | mBtnStart.setOnClickListener(v -> {
53 | mBtnStart.setEnabled(false);
54 | mTxtReceivedMessagesCount.setText("");
55 | mTxtExecutionTime.setText("");
56 | mProgressBar.setVisibility(View.VISIBLE);
57 |
58 | mDisposable = mProducerConsumerBenchmarkUseCase.startBenchmark()
59 | .subscribeOn(Schedulers.io())
60 | .observeOn(AndroidSchedulers.mainThread())
61 | .subscribe(this::onBenchmarkCompleted);
62 | });
63 |
64 | return view;
65 | }
66 |
67 | @Override
68 | protected String getScreenTitle() {
69 | return "";
70 | }
71 |
72 | @Override
73 | public void onStop() {
74 | super.onStop();
75 | if (mDisposable != null) {
76 | mDisposable.dispose();
77 | }
78 | }
79 |
80 | public void onBenchmarkCompleted(ProducerConsumerBenchmarkUseCase.Result result) {
81 | mProgressBar.setVisibility(View.INVISIBLE);
82 | mBtnStart.setEnabled(true);
83 | mTxtReceivedMessagesCount.setText("Received messages: " + result.getNumOfReceivedMessages());
84 | mTxtExecutionTime.setText("Execution time: " + result.getExecutionTime() + "ms");
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/multithreading/demonstrations/designrxjava/MyBlockingQueue.java:
--------------------------------------------------------------------------------
1 | package com.techyourchance.multithreading.demonstrations.designrxjava;
2 |
3 | import java.util.LinkedList;
4 | import java.util.Queue;
5 |
6 | /**
7 | * Simplified implementation of blocking queue.
8 | */
9 | class MyBlockingQueue {
10 |
11 | private final Object QUEUE_LOCK = new Object();
12 |
13 | private final int mCapacity;
14 | private final Queue mQueue = new LinkedList<>();
15 |
16 | private int mCurrentSize = 0;
17 |
18 | MyBlockingQueue(int capacity) {
19 | mCapacity = capacity;
20 | }
21 |
22 | /**
23 | * Inserts the specified element into this queue, waiting if necessary
24 | * for space to become available.
25 | *
26 | * @param number the element to add
27 | */
28 | public void put(int number) {
29 | synchronized (QUEUE_LOCK) {
30 | while (mCurrentSize >= mCapacity) {
31 | try {
32 | QUEUE_LOCK.wait();
33 | } catch (InterruptedException e) {
34 | return;
35 | }
36 | }
37 |
38 | mQueue.offer(number);
39 | mCurrentSize++;
40 | QUEUE_LOCK.notifyAll();
41 | }
42 | }
43 |
44 | /**
45 | * Retrieves and removes the head of this queue, waiting if necessary
46 | * until an element becomes available.
47 | *
48 | * @return the head of this queue
49 | */
50 | public int take() {
51 | synchronized (QUEUE_LOCK) {
52 | while (mCurrentSize <= 0) {
53 | try {
54 | QUEUE_LOCK.wait();
55 | } catch (InterruptedException e) {
56 | return 0;
57 | }
58 | }
59 | mCurrentSize--;
60 | QUEUE_LOCK.notifyAll();
61 | return mQueue.poll();
62 | }
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/multithreading/demonstrations/designrxjava/ProducerConsumerBenchmarkUseCase.java:
--------------------------------------------------------------------------------
1 | package com.techyourchance.multithreading.demonstrations.designrxjava;
2 |
3 | import android.util.Log;
4 |
5 | import com.techyourchance.multithreading.DefaultConfiguration;
6 |
7 | import java.util.concurrent.Callable;
8 |
9 | import io.reactivex.Flowable;
10 | import io.reactivex.Observable;
11 | import io.reactivex.schedulers.Schedulers;
12 |
13 | public class ProducerConsumerBenchmarkUseCase {
14 |
15 | public static class Result {
16 | private final long mExecutionTime;
17 | private final int mNumOfReceivedMessages;
18 |
19 | public Result(long executionTime, int numOfReceivedMessages) {
20 | mExecutionTime = executionTime;
21 | mNumOfReceivedMessages = numOfReceivedMessages;
22 | }
23 |
24 | public long getExecutionTime() {
25 | return mExecutionTime;
26 | }
27 |
28 | public int getNumOfReceivedMessages() {
29 | return mNumOfReceivedMessages;
30 | }
31 | }
32 |
33 | private static final int NUM_OF_MESSAGES = DefaultConfiguration.DEFAULT_NUM_OF_MESSAGES;
34 | private static final int BLOCKING_QUEUE_CAPACITY = DefaultConfiguration.DEFAULT_BLOCKING_QUEUE_SIZE;
35 |
36 | private final MyBlockingQueue mBlockingQueue = new MyBlockingQueue(BLOCKING_QUEUE_CAPACITY);
37 |
38 | private long mStartTimestamp;
39 |
40 |
41 | public Observable startBenchmark() {
42 | return Flowable.range(0, NUM_OF_MESSAGES)
43 | .flatMap(id -> Flowable
44 | .fromCallable(() -> {
45 | try {
46 | Thread.sleep(DefaultConfiguration.DEFAULT_PRODUCER_DELAY_MS);
47 | } catch (InterruptedException e) {
48 | return id;
49 | }
50 | mBlockingQueue.put(id);
51 | return id;
52 | }) // <-- generate message
53 | .subscribeOn(Schedulers.io())
54 | )
55 | .parallel(NUM_OF_MESSAGES)
56 | .runOn(Schedulers.io())
57 | .doOnNext(msg -> { mBlockingQueue.take(); }) // <-- process message
58 | .sequential()
59 | .count()
60 | .doOnSubscribe(s -> { mStartTimestamp = System.currentTimeMillis(); })
61 | .map(cnt -> new Result(System.currentTimeMillis() - mStartTimestamp, cnt.intValue()))
62 | .toObservable();
63 | }
64 |
65 |
66 | }
67 |
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/multithreading/demonstrations/designthread/DesignWithThreadsDemonstrationFragment.java:
--------------------------------------------------------------------------------
1 | package com.techyourchance.multithreading.demonstrations.designthread;
2 |
3 | import android.os.Bundle;
4 | import android.view.LayoutInflater;
5 | import android.view.View;
6 | import android.view.ViewGroup;
7 | import android.widget.Button;
8 | import android.widget.ProgressBar;
9 | import android.widget.TextView;
10 |
11 | import com.techyourchance.multithreading.R;
12 | import com.techyourchance.multithreading.common.BaseFragment;
13 |
14 | import androidx.annotation.NonNull;
15 | import androidx.annotation.Nullable;
16 | import androidx.fragment.app.Fragment;
17 |
18 | public class DesignWithThreadsDemonstrationFragment extends BaseFragment implements ProducerConsumerBenchmarkUseCase.Listener {
19 |
20 | public static Fragment newInstance() {
21 | return new DesignWithThreadsDemonstrationFragment();
22 | }
23 |
24 | private Button mBtnStart;
25 | private ProgressBar mProgressBar;
26 | private TextView mTxtReceivedMessagesCount;
27 | private TextView mTxtExecutionTime;
28 |
29 | private ProducerConsumerBenchmarkUseCase mProducerConsumerBenchmarkUseCase;
30 |
31 | @Override
32 | public void onCreate(@Nullable Bundle savedInstanceState) {
33 | super.onCreate(savedInstanceState);
34 | mProducerConsumerBenchmarkUseCase = new ProducerConsumerBenchmarkUseCase();
35 | }
36 |
37 | @Nullable
38 | @Override
39 | public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
40 | View view = inflater.inflate(R.layout.fragment_design_with_thread_demonstration, container, false);
41 |
42 | mBtnStart = view.findViewById(R.id.btn_start);
43 | mProgressBar = view.findViewById(R.id.progress);
44 | mTxtReceivedMessagesCount = view.findViewById(R.id.txt_received_messages_count);
45 | mTxtExecutionTime = view.findViewById(R.id.txt_execution_time);
46 |
47 | mBtnStart.setOnClickListener(v -> {
48 | mBtnStart.setEnabled(false);
49 | mTxtReceivedMessagesCount.setText("");
50 | mTxtExecutionTime.setText("");
51 | mProgressBar.setVisibility(View.VISIBLE);
52 |
53 | mProducerConsumerBenchmarkUseCase.startBenchmarkAndNotify();
54 | });
55 |
56 | return view;
57 | }
58 |
59 | @Override
60 | protected String getScreenTitle() {
61 | return "";
62 | }
63 |
64 | @Override
65 | public void onStart() {
66 | super.onStart();
67 | mProducerConsumerBenchmarkUseCase.registerListener(this);
68 | }
69 |
70 | @Override
71 | public void onStop() {
72 | super.onStop();
73 | mProducerConsumerBenchmarkUseCase.unregisterListener(this);
74 | }
75 |
76 | @Override
77 | public void onBenchmarkCompleted(ProducerConsumerBenchmarkUseCase.Result result) {
78 | mProgressBar.setVisibility(View.INVISIBLE);
79 | mBtnStart.setEnabled(true);
80 | mTxtReceivedMessagesCount.setText("Received messages: " + result.getNumOfReceivedMessages());
81 | mTxtExecutionTime.setText("Execution time: " + result.getExecutionTime() + "ms");
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/multithreading/demonstrations/designthread/MyBlockingQueue.java:
--------------------------------------------------------------------------------
1 | package com.techyourchance.multithreading.demonstrations.designthread;
2 |
3 | import java.util.LinkedList;
4 | import java.util.Queue;
5 |
6 | /**
7 | * Simplified implementation of blocking queue.
8 | */
9 | class MyBlockingQueue {
10 |
11 | private final Object QUEUE_LOCK = new Object();
12 |
13 | private final int mCapacity;
14 | private final Queue mQueue = new LinkedList<>();
15 |
16 | private int mCurrentSize = 0;
17 |
18 | MyBlockingQueue(int capacity) {
19 | mCapacity = capacity;
20 | }
21 |
22 | /**
23 | * Inserts the specified element into this queue, waiting if necessary
24 | * for space to become available.
25 | *
26 | * @param number the element to add
27 | */
28 | public void put(int number) {
29 | synchronized (QUEUE_LOCK) {
30 | while (mCurrentSize >= mCapacity) {
31 | try {
32 | QUEUE_LOCK.wait();
33 | } catch (InterruptedException e) {
34 | return;
35 | }
36 | }
37 |
38 | mQueue.offer(number);
39 | mCurrentSize++;
40 | QUEUE_LOCK.notifyAll();
41 | }
42 | }
43 |
44 | /**
45 | * Retrieves and removes the head of this queue, waiting if necessary
46 | * until an element becomes available.
47 | *
48 | * @return the head of this queue
49 | */
50 | public int take() {
51 | synchronized (QUEUE_LOCK) {
52 | while (mCurrentSize <= 0) {
53 | try {
54 | QUEUE_LOCK.wait();
55 | } catch (InterruptedException e) {
56 | return 0;
57 | }
58 | }
59 | mCurrentSize--;
60 | QUEUE_LOCK.notifyAll();
61 | return mQueue.poll();
62 | }
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/multithreading/demonstrations/designthread/ProducerConsumerBenchmarkUseCase.java:
--------------------------------------------------------------------------------
1 | package com.techyourchance.multithreading.demonstrations.designthread;
2 |
3 | import android.os.Handler;
4 | import android.os.Looper;
5 |
6 | import com.techyourchance.multithreading.DefaultConfiguration;
7 | import com.techyourchance.multithreading.common.BaseObservable;
8 |
9 | public class ProducerConsumerBenchmarkUseCase extends BaseObservable {
10 |
11 | public static interface Listener {
12 | void onBenchmarkCompleted(Result result);
13 | }
14 |
15 | public static class Result {
16 | private final long mExecutionTime;
17 | private final int mNumOfReceivedMessages;
18 |
19 | public Result(long executionTime, int numOfReceivedMessages) {
20 | mExecutionTime = executionTime;
21 | mNumOfReceivedMessages = numOfReceivedMessages;
22 | }
23 |
24 | public long getExecutionTime() {
25 | return mExecutionTime;
26 | }
27 |
28 | public int getNumOfReceivedMessages() {
29 | return mNumOfReceivedMessages;
30 | }
31 | }
32 |
33 |
34 | private static final int NUM_OF_MESSAGES = DefaultConfiguration.DEFAULT_NUM_OF_MESSAGES;
35 | private static final int BLOCKING_QUEUE_CAPACITY = DefaultConfiguration.DEFAULT_BLOCKING_QUEUE_SIZE;
36 |
37 | private final Object LOCK = new Object();
38 |
39 | private final Handler mUiHandler = new Handler(Looper.getMainLooper());
40 |
41 | private final MyBlockingQueue mBlockingQueue = new MyBlockingQueue(BLOCKING_QUEUE_CAPACITY);
42 |
43 | private int mNumOfFinishedConsumers;
44 |
45 | private int mNumOfReceivedMessages;
46 |
47 | private long mStartTimestamp;
48 |
49 |
50 | public void startBenchmarkAndNotify() {
51 |
52 | synchronized (LOCK) {
53 | mNumOfReceivedMessages = 0;
54 | mNumOfFinishedConsumers = 0;
55 | mStartTimestamp = System.currentTimeMillis();
56 | }
57 |
58 | // watcher-reporter thread
59 | new Thread(() -> {
60 | synchronized (LOCK) {
61 | while (mNumOfFinishedConsumers < NUM_OF_MESSAGES) {
62 | try {
63 | LOCK.wait();
64 | } catch (InterruptedException e) {
65 | return;
66 | }
67 | }
68 | }
69 | notifySuccess();
70 | }).start();
71 |
72 | // producers init thread
73 | new Thread(() -> {
74 | for (int i = 0; i < NUM_OF_MESSAGES; i++) {
75 | startNewProducer(i);
76 | }
77 | }).start();
78 |
79 | // consumers init thread
80 | new Thread(() -> {
81 | for (int i = 0; i < NUM_OF_MESSAGES; i++) {
82 | startNewConsumer();
83 | }
84 | }).start();
85 | }
86 |
87 |
88 | private void startNewProducer(final int index) {
89 | new Thread(() -> {
90 | try {
91 | Thread.sleep(DefaultConfiguration.DEFAULT_PRODUCER_DELAY_MS);
92 | } catch (InterruptedException e) {
93 | return;
94 | }
95 | mBlockingQueue.put(index);
96 | }).start();
97 | }
98 |
99 | private void startNewConsumer() {
100 | new Thread(() -> {
101 | int message = mBlockingQueue.take();
102 | synchronized (LOCK) {
103 | if (message != -1) {
104 | mNumOfReceivedMessages++;
105 | }
106 | mNumOfFinishedConsumers++;
107 | LOCK.notifyAll();
108 | }
109 | }).start();
110 | }
111 |
112 | private void notifySuccess() {
113 | mUiHandler.post(() -> {
114 | Result result;
115 | synchronized (LOCK) {
116 | result =
117 | new Result(
118 | System.currentTimeMillis() - mStartTimestamp,
119 | mNumOfReceivedMessages
120 | );
121 | }
122 | for (Listener listener : getListeners()) {
123 | listener.onBenchmarkCompleted(result);
124 | }
125 | });
126 | }
127 |
128 |
129 | }
130 |
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/multithreading/demonstrations/designthreadpool/DesignWithThreadPoolDemonstrationFragment.java:
--------------------------------------------------------------------------------
1 | package com.techyourchance.multithreading.demonstrations.designthreadpool;
2 |
3 | import android.os.Bundle;
4 | import android.view.LayoutInflater;
5 | import android.view.View;
6 | import android.view.ViewGroup;
7 | import android.widget.Button;
8 | import android.widget.ProgressBar;
9 | import android.widget.TextView;
10 |
11 | import com.techyourchance.multithreading.R;
12 | import com.techyourchance.multithreading.common.BaseFragment;
13 |
14 | import androidx.annotation.NonNull;
15 | import androidx.annotation.Nullable;
16 | import androidx.fragment.app.Fragment;
17 |
18 | public class DesignWithThreadPoolDemonstrationFragment extends BaseFragment implements ProducerConsumerBenchmarkUseCase.Listener {
19 |
20 | public static Fragment newInstance() {
21 | return new DesignWithThreadPoolDemonstrationFragment();
22 | }
23 |
24 | private Button mBtnStart;
25 | private ProgressBar mProgressBar;
26 | private TextView mTxtReceivedMessagesCount;
27 | private TextView mTxtExecutionTime;
28 |
29 | private ProducerConsumerBenchmarkUseCase mProducerConsumerBenchmarkUseCase;
30 |
31 | @Override
32 | public void onCreate(@Nullable Bundle savedInstanceState) {
33 | super.onCreate(savedInstanceState);
34 | mProducerConsumerBenchmarkUseCase = new ProducerConsumerBenchmarkUseCase(
35 | getCompositionRoot().getUiHandler(),
36 | getCompositionRoot().getThreadPool()
37 | );
38 | }
39 |
40 | @Nullable
41 | @Override
42 | public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
43 | View view = inflater.inflate(R.layout.fragment_design_with_thread_pool_demonstration, container, false);
44 |
45 | mBtnStart = view.findViewById(R.id.btn_start);
46 | mProgressBar = view.findViewById(R.id.progress);
47 | mTxtReceivedMessagesCount = view.findViewById(R.id.txt_received_messages_count);
48 | mTxtExecutionTime = view.findViewById(R.id.txt_execution_time);
49 |
50 | mBtnStart.setOnClickListener(v -> {
51 | mBtnStart.setEnabled(false);
52 | mTxtReceivedMessagesCount.setText("");
53 | mTxtExecutionTime.setText("");
54 | mProgressBar.setVisibility(View.VISIBLE);
55 |
56 | mProducerConsumerBenchmarkUseCase.startBenchmarkAndNotify();
57 | });
58 |
59 | return view;
60 | }
61 |
62 | @Override
63 | protected String getScreenTitle() {
64 | return "";
65 | }
66 |
67 | @Override
68 | public void onStart() {
69 | super.onStart();
70 | mProducerConsumerBenchmarkUseCase.registerListener(this);
71 | }
72 |
73 | @Override
74 | public void onStop() {
75 | super.onStop();
76 | mProducerConsumerBenchmarkUseCase.unregisterListener(this);
77 | }
78 |
79 | @Override
80 | public void onBenchmarkCompleted(ProducerConsumerBenchmarkUseCase.Result result) {
81 | mProgressBar.setVisibility(View.INVISIBLE);
82 | mBtnStart.setEnabled(true);
83 | mTxtReceivedMessagesCount.setText("Received messages: " + result.getNumOfReceivedMessages());
84 | mTxtExecutionTime.setText("Execution time: " + result.getExecutionTime() + "ms");
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/multithreading/demonstrations/designthreadpool/MyBlockingQueue.java:
--------------------------------------------------------------------------------
1 | package com.techyourchance.multithreading.demonstrations.designthreadpool;
2 |
3 | import java.util.LinkedList;
4 | import java.util.Queue;
5 |
6 | /**
7 | * Simplified implementation of blocking queue.
8 | */
9 | class MyBlockingQueue {
10 |
11 | private final Object QUEUE_LOCK = new Object();
12 |
13 | private final int mCapacity;
14 | private final Queue mQueue = new LinkedList<>();
15 |
16 | private int mCurrentSize = 0;
17 |
18 | MyBlockingQueue(int capacity) {
19 | mCapacity = capacity;
20 | }
21 |
22 | /**
23 | * Inserts the specified element into this queue, waiting if necessary
24 | * for space to become available.
25 | *
26 | * @param number the element to add
27 | */
28 | public void put(int number) {
29 | synchronized (QUEUE_LOCK) {
30 | while (mCurrentSize >= mCapacity) {
31 | try {
32 | QUEUE_LOCK.wait();
33 | } catch (InterruptedException e) {
34 | return;
35 | }
36 | }
37 |
38 | mQueue.offer(number);
39 | mCurrentSize++;
40 | QUEUE_LOCK.notifyAll();
41 | }
42 | }
43 |
44 | /**
45 | * Retrieves and removes the head of this queue, waiting if necessary
46 | * until an element becomes available.
47 | *
48 | * @return the head of this queue
49 | */
50 | public int take() {
51 | synchronized (QUEUE_LOCK) {
52 | while (mCurrentSize <= 0) {
53 | try {
54 | QUEUE_LOCK.wait();
55 | } catch (InterruptedException e) {
56 | return 0;
57 | }
58 | }
59 | mCurrentSize--;
60 | QUEUE_LOCK.notifyAll();
61 | return mQueue.poll();
62 | }
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/multithreading/demonstrations/designthreadpool/ProducerConsumerBenchmarkUseCase.java:
--------------------------------------------------------------------------------
1 | package com.techyourchance.multithreading.demonstrations.designthreadpool;
2 |
3 | import android.os.Handler;
4 |
5 | import com.techyourchance.multithreading.DefaultConfiguration;
6 | import com.techyourchance.multithreading.common.BaseObservable;
7 |
8 | import java.util.concurrent.ThreadPoolExecutor;
9 | import java.util.concurrent.atomic.AtomicInteger;
10 |
11 | public class ProducerConsumerBenchmarkUseCase extends BaseObservable {
12 |
13 | public static interface Listener {
14 | void onBenchmarkCompleted(Result result);
15 | }
16 |
17 | public static class Result {
18 | private final long mExecutionTime;
19 | private final int mNumOfReceivedMessages;
20 |
21 | public Result(long executionTime, int numOfReceivedMessages) {
22 | mExecutionTime = executionTime;
23 | mNumOfReceivedMessages = numOfReceivedMessages;
24 | }
25 |
26 | public long getExecutionTime() {
27 | return mExecutionTime;
28 | }
29 |
30 | public int getNumOfReceivedMessages() {
31 | return mNumOfReceivedMessages;
32 | }
33 | }
34 |
35 | private static final int NUM_OF_MESSAGES = DefaultConfiguration.DEFAULT_NUM_OF_MESSAGES;
36 | private static final int BLOCKING_QUEUE_CAPACITY = DefaultConfiguration.DEFAULT_BLOCKING_QUEUE_SIZE;
37 |
38 | private final Object LOCK = new Object();
39 |
40 | private final Handler mUiHandler;
41 |
42 | private final AtomicInteger mNumOfThreads = new AtomicInteger(0);
43 |
44 | private final MyBlockingQueue mBlockingQueue = new MyBlockingQueue(BLOCKING_QUEUE_CAPACITY);
45 |
46 | private final ThreadPoolExecutor mThreadPool;
47 |
48 | private int mNumOfFinishedConsumers;
49 |
50 | private int mNumOfReceivedMessages;
51 |
52 | private long mStartTimestamp;
53 |
54 | public ProducerConsumerBenchmarkUseCase(Handler uiHandler, ThreadPoolExecutor threadPool) {
55 | mUiHandler = uiHandler;
56 | mThreadPool = threadPool;
57 | }
58 |
59 | public void startBenchmarkAndNotify() {
60 |
61 | synchronized (LOCK) {
62 | mNumOfReceivedMessages = 0;
63 | mNumOfFinishedConsumers = 0;
64 | mStartTimestamp = System.currentTimeMillis();
65 | mNumOfThreads.set(0);
66 | }
67 |
68 | // watcher-reporter thread
69 | mThreadPool.execute(() -> {
70 | synchronized (LOCK) {
71 | while (mNumOfFinishedConsumers < NUM_OF_MESSAGES) {
72 | try {
73 | LOCK.wait();
74 | } catch (InterruptedException e) {
75 | return;
76 | }
77 | }
78 | }
79 | notifySuccess();
80 | });
81 |
82 | // producers init thread
83 | mThreadPool.execute(() -> {
84 | for (int i = 0; i < NUM_OF_MESSAGES; i++) {
85 | startNewProducer(i);
86 | }
87 | });
88 |
89 | // consumers init thread
90 | mThreadPool.execute(() -> {
91 | for (int i = 0; i < NUM_OF_MESSAGES; i++) {
92 | startNewConsumer();
93 | }
94 | });
95 | }
96 |
97 |
98 | private void startNewProducer(final int index) {
99 | mThreadPool.execute(() -> {
100 | try {
101 | Thread.sleep(DefaultConfiguration.DEFAULT_PRODUCER_DELAY_MS);
102 | } catch (InterruptedException e) {
103 | return;
104 | }
105 | mBlockingQueue.put(index);
106 | });
107 | }
108 |
109 | private void startNewConsumer() {
110 | mThreadPool.execute(() -> {
111 | int message = mBlockingQueue.take();
112 | synchronized (LOCK) {
113 | if (message != -1) {
114 | mNumOfReceivedMessages++;
115 | }
116 | mNumOfFinishedConsumers++;
117 | LOCK.notifyAll();
118 | }
119 | });
120 | }
121 |
122 | private void notifySuccess() {
123 | mUiHandler.post(() -> {
124 | Result result;
125 | synchronized (LOCK) {
126 | result =
127 | new Result(
128 | System.currentTimeMillis() - mStartTimestamp,
129 | mNumOfReceivedMessages
130 | );
131 | }
132 | for (Listener listener : getListeners()) {
133 | listener.onBenchmarkCompleted(result);
134 | }
135 | });
136 | }
137 |
138 |
139 | }
140 |
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/multithreading/demonstrations/designthreadposter/DesignWithThreadPosterDemonstrationFragment.java:
--------------------------------------------------------------------------------
1 | package com.techyourchance.multithreading.demonstrations.designthreadposter;
2 |
3 | import android.os.Bundle;
4 | import android.view.LayoutInflater;
5 | import android.view.View;
6 | import android.view.ViewGroup;
7 | import android.widget.Button;
8 | import android.widget.ProgressBar;
9 | import android.widget.TextView;
10 |
11 | import com.techyourchance.multithreading.R;
12 | import com.techyourchance.multithreading.common.BaseFragment;
13 |
14 | import androidx.annotation.NonNull;
15 | import androidx.annotation.Nullable;
16 | import androidx.fragment.app.Fragment;
17 |
18 | public class DesignWithThreadPosterDemonstrationFragment extends BaseFragment implements ProducerConsumerBenchmarkUseCase.Listener {
19 |
20 | public static Fragment newInstance() {
21 | return new DesignWithThreadPosterDemonstrationFragment();
22 | }
23 |
24 | private Button mBtnStart;
25 | private ProgressBar mProgressBar;
26 | private TextView mTxtReceivedMessagesCount;
27 | private TextView mTxtExecutionTime;
28 |
29 | private ProducerConsumerBenchmarkUseCase mProducerConsumerBenchmarkUseCase;
30 |
31 | @Override
32 | public void onCreate(@Nullable Bundle savedInstanceState) {
33 | super.onCreate(savedInstanceState);
34 | mProducerConsumerBenchmarkUseCase = new ProducerConsumerBenchmarkUseCase();
35 | }
36 |
37 | @Nullable
38 | @Override
39 | public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
40 | View view = inflater.inflate(R.layout.fragment_design_with_thread_demonstration, container, false);
41 |
42 | mBtnStart = view.findViewById(R.id.btn_start);
43 | mProgressBar = view.findViewById(R.id.progress);
44 | mTxtReceivedMessagesCount = view.findViewById(R.id.txt_received_messages_count);
45 | mTxtExecutionTime = view.findViewById(R.id.txt_execution_time);
46 |
47 | mBtnStart.setOnClickListener(v -> {
48 | mBtnStart.setEnabled(false);
49 | mTxtReceivedMessagesCount.setText("");
50 | mTxtExecutionTime.setText("");
51 | mProgressBar.setVisibility(View.VISIBLE);
52 |
53 | mProducerConsumerBenchmarkUseCase.startBenchmarkAndNotify();
54 | });
55 |
56 | return view;
57 | }
58 |
59 | @Override
60 | protected String getScreenTitle() {
61 | return "";
62 | }
63 |
64 | @Override
65 | public void onStart() {
66 | super.onStart();
67 | mProducerConsumerBenchmarkUseCase.registerListener(this);
68 | }
69 |
70 | @Override
71 | public void onStop() {
72 | super.onStop();
73 | mProducerConsumerBenchmarkUseCase.unregisterListener(this);
74 | }
75 |
76 | @Override
77 | public void onBenchmarkCompleted(ProducerConsumerBenchmarkUseCase.Result result) {
78 | mProgressBar.setVisibility(View.INVISIBLE);
79 | mBtnStart.setEnabled(true);
80 | mTxtReceivedMessagesCount.setText("Received messages: " + result.getNumOfReceivedMessages());
81 | mTxtExecutionTime.setText("Execution time: " + result.getExecutionTime() + "ms");
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/multithreading/demonstrations/designthreadposter/MyBlockingQueue.java:
--------------------------------------------------------------------------------
1 | package com.techyourchance.multithreading.demonstrations.designthreadposter;
2 |
3 | import java.util.LinkedList;
4 | import java.util.Queue;
5 |
6 | /**
7 | * Simplified implementation of blocking queue.
8 | */
9 | class MyBlockingQueue {
10 |
11 | private final Object QUEUE_LOCK = new Object();
12 |
13 | private final int mCapacity;
14 | private final Queue mQueue = new LinkedList<>();
15 |
16 | private int mCurrentSize = 0;
17 |
18 | MyBlockingQueue(int capacity) {
19 | mCapacity = capacity;
20 | }
21 |
22 | /**
23 | * Inserts the specified element into this queue, waiting if necessary
24 | * for space to become available.
25 | *
26 | * @param number the element to add
27 | */
28 | public void put(int number) {
29 | synchronized (QUEUE_LOCK) {
30 | while (mCurrentSize >= mCapacity) {
31 | try {
32 | QUEUE_LOCK.wait();
33 | } catch (InterruptedException e) {
34 | return;
35 | }
36 | }
37 |
38 | mQueue.offer(number);
39 | mCurrentSize++;
40 | QUEUE_LOCK.notifyAll();
41 | }
42 | }
43 |
44 | /**
45 | * Retrieves and removes the head of this queue, waiting if necessary
46 | * until an element becomes available.
47 | *
48 | * @return the head of this queue
49 | */
50 | public int take() {
51 | synchronized (QUEUE_LOCK) {
52 | while (mCurrentSize <= 0) {
53 | try {
54 | QUEUE_LOCK.wait();
55 | } catch (InterruptedException e) {
56 | return 0;
57 | }
58 | }
59 | mCurrentSize--;
60 | QUEUE_LOCK.notifyAll();
61 | return mQueue.poll();
62 | }
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/multithreading/demonstrations/designthreadposter/ProducerConsumerBenchmarkUseCase.java:
--------------------------------------------------------------------------------
1 | package com.techyourchance.multithreading.demonstrations.designthreadposter;
2 |
3 | import com.techyourchance.multithreading.DefaultConfiguration;
4 | import com.techyourchance.multithreading.common.BaseObservable;
5 | import com.techyourchance.threadposter.BackgroundThreadPoster;
6 | import com.techyourchance.threadposter.UiThreadPoster;
7 |
8 | public class ProducerConsumerBenchmarkUseCase extends BaseObservable {
9 |
10 | public static interface Listener {
11 | void onBenchmarkCompleted(Result result);
12 | }
13 |
14 | public static class Result {
15 | private final long mExecutionTime;
16 | private final int mNumOfReceivedMessages;
17 |
18 | public Result(long executionTime, int numOfReceivedMessages) {
19 | mExecutionTime = executionTime;
20 | mNumOfReceivedMessages = numOfReceivedMessages;
21 | }
22 |
23 | public long getExecutionTime() {
24 | return mExecutionTime;
25 | }
26 |
27 | public int getNumOfReceivedMessages() {
28 | return mNumOfReceivedMessages;
29 | }
30 | }
31 |
32 | private static final int NUM_OF_MESSAGES = DefaultConfiguration.DEFAULT_NUM_OF_MESSAGES;
33 | private static final int BLOCKING_QUEUE_CAPACITY = DefaultConfiguration.DEFAULT_BLOCKING_QUEUE_SIZE;
34 |
35 | private final Object LOCK = new Object();
36 |
37 | private final UiThreadPoster mUiThreadPoster = new UiThreadPoster();
38 | private final BackgroundThreadPoster mBackgroundThreadPoster = new BackgroundThreadPoster();
39 |
40 | private final MyBlockingQueue mBlockingQueue = new MyBlockingQueue(BLOCKING_QUEUE_CAPACITY);
41 |
42 | private int mNumOfFinishedConsumers;
43 |
44 | private int mNumOfReceivedMessages;
45 |
46 | private long mStartTimestamp;
47 |
48 |
49 | public void startBenchmarkAndNotify() {
50 |
51 | synchronized (LOCK) {
52 | mNumOfReceivedMessages = 0;
53 | mNumOfFinishedConsumers = 0;
54 | mStartTimestamp = System.currentTimeMillis();
55 | }
56 |
57 | // watcher-reporter thread
58 | mBackgroundThreadPoster.post(() -> {
59 | synchronized (LOCK) {
60 | while (mNumOfFinishedConsumers < NUM_OF_MESSAGES) {
61 | try {
62 | LOCK.wait();
63 | } catch (InterruptedException e) {
64 | return;
65 | }
66 | }
67 | }
68 | notifySuccess();
69 | });
70 |
71 | // producers init thread
72 | mBackgroundThreadPoster.post(() -> {
73 | for (int i = 0; i < NUM_OF_MESSAGES; i++) {
74 | startNewProducer(i);
75 | }
76 | });
77 |
78 | // consumers init thread
79 | mBackgroundThreadPoster.post(() -> {
80 | for (int i = 0; i < NUM_OF_MESSAGES; i++) {
81 | startNewConsumer();
82 | }
83 | });
84 | }
85 |
86 |
87 | private void startNewProducer(final int index) {
88 | mBackgroundThreadPoster.post(() -> {
89 | try {
90 | Thread.sleep(DefaultConfiguration.DEFAULT_PRODUCER_DELAY_MS);
91 | } catch (InterruptedException e) {
92 | return;
93 | }
94 | mBlockingQueue.put(index);
95 | });
96 | }
97 |
98 | private void startNewConsumer() {
99 | mBackgroundThreadPoster.post(() -> {
100 | int message = mBlockingQueue.take();
101 | synchronized (LOCK) {
102 | if (message != -1) {
103 | mNumOfReceivedMessages++;
104 | }
105 | mNumOfFinishedConsumers++;
106 | LOCK.notifyAll();
107 | }
108 | });
109 | }
110 |
111 | private void notifySuccess() {
112 | mUiThreadPoster.post(() -> {
113 | Result result;
114 | synchronized (LOCK) {
115 | result =
116 | new Result(
117 | System.currentTimeMillis() - mStartTimestamp,
118 | mNumOfReceivedMessages
119 | );
120 | }
121 | for (Listener listener : getListeners()) {
122 | listener.onBenchmarkCompleted(result);
123 | }
124 | });
125 | }
126 |
127 |
128 | }
129 |
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/multithreading/demonstrations/synchronization/SynchronizationDemonstration.java:
--------------------------------------------------------------------------------
1 | package com.techyourchance.multithreading.demonstrations.synchronization;
2 |
3 | public class SynchronizationDemonstration {
4 |
5 | private static final Object LOCK = new Object();
6 |
7 | private static int sCount = 0;
8 |
9 | public static void main(String[] args) {
10 | new Consumer().start();
11 | try {
12 | Thread.sleep(100);
13 | } catch (InterruptedException e) {
14 | return;
15 | }
16 | new Producer().start();
17 | }
18 |
19 | static class Consumer extends Thread {
20 | @Override
21 | public void run() {
22 | int localValue = -1;
23 | while (true) {
24 | synchronized (LOCK) {
25 | if (localValue != sCount) {
26 | System.out.println("Consumer: detected count change " + sCount);
27 | localValue = sCount;
28 | }
29 | if (sCount >= 5) {
30 | break;
31 | }
32 | }
33 | }
34 | System.out.println("Consumer: terminating");
35 | }
36 | }
37 |
38 | static class Producer extends Thread {
39 | @Override
40 | public void run() {
41 | while (true) {
42 | synchronized (LOCK) {
43 | if (sCount >= 5) {
44 | break;
45 | }
46 | int localValue = sCount;
47 | localValue++;
48 | System.out.println("Producer: incrementing count to " + localValue);
49 | sCount = localValue;
50 | }
51 | }
52 | System.out.println("Producer: terminating");
53 | }
54 | }
55 |
56 | }
57 |
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/multithreading/demonstrations/uihandler/UiHandlerDemonstrationFragment.java:
--------------------------------------------------------------------------------
1 | package com.techyourchance.multithreading.demonstrations.uihandler;
2 |
3 | import android.annotation.SuppressLint;
4 | import android.os.Bundle;
5 | import android.os.Handler;
6 | import android.os.Looper;
7 | import android.util.Log;
8 | import android.view.LayoutInflater;
9 | import android.view.View;
10 | import android.view.ViewGroup;
11 | import android.widget.Button;
12 |
13 | import com.techyourchance.multithreading.R;
14 | import com.techyourchance.multithreading.common.BaseFragment;
15 |
16 | import androidx.annotation.NonNull;
17 | import androidx.annotation.Nullable;
18 | import androidx.fragment.app.Fragment;
19 |
20 |
21 | @SuppressLint("SetTextI18n")
22 | public class UiHandlerDemonstrationFragment extends BaseFragment {
23 |
24 | private static final int ITERATIONS_COUNTER_DURATION_SEC = 10;
25 |
26 | public static Fragment newInstance() {
27 | return new UiHandlerDemonstrationFragment();
28 | }
29 |
30 | private Button mBtnCountIterations;
31 |
32 | private final Handler mUiHandler = new Handler(Looper.getMainLooper());
33 |
34 | @Nullable
35 | @Override
36 | public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
37 | View view = inflater.inflate(R.layout.fragment_ui_handler_demonstration, container, false);
38 |
39 | mBtnCountIterations = view.findViewById(R.id.btn_count_iterations);
40 | mBtnCountIterations.setOnClickListener(new View.OnClickListener() {
41 | @Override
42 | public void onClick(View v) {
43 | countIterations();
44 | }
45 | });
46 |
47 | return view;
48 | }
49 |
50 | @Override
51 | protected String getScreenTitle() {
52 | return "";
53 | }
54 |
55 | private void countIterations() {
56 | new Thread(new Runnable() {
57 | @Override
58 | public void run() {
59 | long startTimestamp = System.currentTimeMillis();
60 | long endTimestamp = startTimestamp + ITERATIONS_COUNTER_DURATION_SEC * 1000;
61 |
62 | int iterationsCount = 0;
63 | while (System.currentTimeMillis() <= endTimestamp) {
64 | iterationsCount++;
65 | }
66 |
67 | final int iterationsCountFinal = iterationsCount;
68 |
69 | mUiHandler.post(new Runnable() {
70 | @Override
71 | public void run() {
72 | Log.d("UiHandler", "Current thread: " + Thread.currentThread().getName());
73 | mBtnCountIterations.setText("Iterations: " + iterationsCountFinal);
74 | }
75 | });
76 | }
77 | }).start();
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/multithreading/demonstrations/uithread/UiThreadDemonstrationFragment.java:
--------------------------------------------------------------------------------
1 | package com.techyourchance.multithreading.demonstrations.uithread;
2 |
3 | import android.os.Bundle;
4 | import android.util.Log;
5 | import android.view.LayoutInflater;
6 | import android.view.View;
7 | import android.view.ViewGroup;
8 | import android.widget.Button;
9 |
10 | import com.techyourchance.multithreading.R;
11 | import com.techyourchance.multithreading.common.BaseFragment;
12 |
13 | import androidx.annotation.NonNull;
14 | import androidx.annotation.Nullable;
15 | import androidx.fragment.app.Fragment;
16 |
17 | public class UiThreadDemonstrationFragment extends BaseFragment {
18 |
19 | private static final String TAG = "UiThreadDemonstration";
20 |
21 | public static Fragment newInstance() {
22 | return new UiThreadDemonstrationFragment();
23 | }
24 |
25 | @Override
26 | public void onCreate(@Nullable Bundle savedInstanceState) {
27 | super.onCreate(savedInstanceState);
28 | logThreadInfo("onCreate()");
29 | }
30 |
31 | @Nullable
32 | @Override
33 | public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
34 | View view = inflater.inflate(R.layout.fragment_ui_thread_demonstration, container, false);
35 |
36 | Button mBtnCallbackCheck = view.findViewById(R.id.btn_callback_check);
37 | mBtnCallbackCheck.setOnClickListener(new View.OnClickListener() {
38 | @Override
39 | public void onClick(View v) {
40 | logThreadInfo("button callback");
41 | }
42 | });
43 |
44 | return view;
45 | }
46 |
47 | @Override
48 | public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
49 | super.onViewCreated(view, savedInstanceState);
50 | logThreadInfo("onViewCreated()");
51 | }
52 |
53 | @Override
54 | public void onStart() {
55 | super.onStart();
56 | logThreadInfo("onStart()");
57 | }
58 |
59 | @Override
60 | public void onResume() {
61 | super.onResume();
62 | logThreadInfo("onResume()");
63 | }
64 |
65 | @Override
66 | public void onPause() {
67 | super.onPause();
68 | logThreadInfo("onPause()");
69 | }
70 |
71 | @Override
72 | public void onStop() {
73 | super.onStop();
74 | logThreadInfo("onStop()");
75 | }
76 |
77 | @Override
78 | public void onDestroyView() {
79 | super.onDestroyView();
80 | logThreadInfo("onDestroyView()");
81 | }
82 |
83 | @Override
84 | public void onDestroy() {
85 | super.onDestroy();
86 | logThreadInfo("onDestroy()");
87 | }
88 |
89 | @Override
90 | protected String getScreenTitle() {
91 | return "";
92 | }
93 |
94 | private void logThreadInfo(String eventName) {
95 | Log.d(TAG, "event\n"
96 | + eventName
97 | + "; thread name: " + Thread.currentThread().getName()
98 | + "; thread ID: " + Thread.currentThread().getId());
99 |
100 | }
101 |
102 | }
103 |
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/multithreading/demonstrations/visibility/VisibilityDemonstration.java:
--------------------------------------------------------------------------------
1 | package com.techyourchance.multithreading.demonstrations.visibility;
2 |
3 | public class VisibilityDemonstration {
4 |
5 | private static int sCount = 0;
6 |
7 | public static void main(String[] args) {
8 | new Consumer().start();
9 | try {
10 | Thread.sleep(100);
11 | } catch (InterruptedException e) {
12 | return;
13 | }
14 | new Producer().start();
15 | }
16 |
17 | static class Consumer extends Thread {
18 | @Override
19 | public void run() {
20 | int localValue = -1;
21 | while (true) {
22 | if (localValue != sCount) {
23 | System.out.println("Consumer: detected count change " + sCount);
24 | localValue = sCount;
25 | }
26 | if (sCount >= 5) {
27 | break;
28 | }
29 | }
30 | System.out.println("Consumer: terminating");
31 | }
32 | }
33 |
34 | static class Producer extends Thread {
35 | @Override
36 | public void run() {
37 | while (sCount < 5) {
38 | int localValue = sCount;
39 | localValue++;
40 | System.out.println("Producer: incrementing count to " + localValue);
41 | sCount = localValue;
42 | try {
43 | Thread.sleep(1000);
44 | } catch (InterruptedException e) {
45 | return;
46 | }
47 | }
48 | System.out.println("Producer: terminating");
49 | }
50 | }
51 |
52 | }
53 |
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/multithreading/exercises/exercise1/Exercise1Fragment.java:
--------------------------------------------------------------------------------
1 | package com.techyourchance.multithreading.exercises.exercise1;
2 |
3 | import android.os.Bundle;
4 | import android.util.Log;
5 | import android.view.LayoutInflater;
6 | import android.view.View;
7 | import android.view.ViewGroup;
8 | import android.widget.Button;
9 | import android.widget.ListView;
10 | import android.widget.TextView;
11 | import android.widget.Toast;
12 |
13 | import com.techyourchance.multithreading.R;
14 | import com.techyourchance.multithreading.common.BaseFragment;
15 | import com.techyourchance.multithreading.common.ScreensNavigator;
16 | import com.techyourchance.multithreading.home.HomeArrayAdapter;
17 | import com.techyourchance.multithreading.home.ScreenReachableFromHome;
18 |
19 | import androidx.annotation.NonNull;
20 | import androidx.annotation.Nullable;
21 | import androidx.fragment.app.Fragment;
22 |
23 | public class Exercise1Fragment extends BaseFragment {
24 |
25 | private static final int ITERATIONS_COUNTER_DURATION_SEC = 10;
26 |
27 | public static Fragment newInstance() {
28 | return new Exercise1Fragment();
29 | }
30 |
31 | private Button mBtnCountIterations;
32 |
33 | @Nullable
34 | @Override
35 | public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
36 | View view = inflater.inflate(R.layout.fragment_exercise_1, container, false);
37 |
38 | mBtnCountIterations = view.findViewById(R.id.btn_count_iterations);
39 | mBtnCountIterations.setOnClickListener(new View.OnClickListener() {
40 | @Override
41 | public void onClick(View v) {
42 | countIterations();
43 | }
44 | });
45 |
46 | return view;
47 | }
48 |
49 | @Override
50 | protected String getScreenTitle() {
51 | return "Exercise 1";
52 | }
53 |
54 | private void countIterations() {
55 | long startTimestamp = System.currentTimeMillis();
56 | long endTimestamp = startTimestamp + ITERATIONS_COUNTER_DURATION_SEC * 1000;
57 |
58 | int iterationsCount = 0;
59 | while (System.currentTimeMillis() <= endTimestamp) {
60 | iterationsCount++;
61 | }
62 |
63 | Log.d(
64 | "Exercise1",
65 | "iterations in " + ITERATIONS_COUNTER_DURATION_SEC + "seconds: " + iterationsCount
66 | );
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/multithreading/exercises/exercise10/Exercise10Fragment.kt:
--------------------------------------------------------------------------------
1 | package com.techyourchance.multithreading.exercises.exercise10
2 |
3 | import android.app.Activity
4 | import android.os.Bundle
5 | import android.view.LayoutInflater
6 | import android.view.View
7 | import android.view.ViewGroup
8 | import android.view.inputmethod.InputMethodManager
9 | import android.widget.Button
10 | import android.widget.EditText
11 | import android.widget.TextView
12 |
13 | import com.techyourchance.multithreading.R
14 | import com.techyourchance.multithreading.common.BaseFragment
15 |
16 | import java.math.BigInteger
17 | import androidx.fragment.app.Fragment
18 | import com.techyourchance.multithreading.DefaultConfiguration
19 |
20 | class Exercise10Fragment : BaseFragment(), ComputeFactorialUseCase.Listener {
21 |
22 | private lateinit var edtArgument: EditText
23 | private lateinit var edtTimeout: EditText
24 | private lateinit var btnStartWork: Button
25 | private lateinit var txtResult: TextView
26 |
27 | private lateinit var computeFactorialUseCase: ComputeFactorialUseCase
28 |
29 | override fun onCreate(savedInstanceState: Bundle?) {
30 | super.onCreate(savedInstanceState)
31 | computeFactorialUseCase = ComputeFactorialUseCase()
32 | }
33 |
34 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
35 | val view = inflater.inflate(R.layout.fragment_exercise_10, container, false)
36 |
37 | view.apply {
38 | edtArgument = findViewById(R.id.edt_argument)
39 | edtTimeout = findViewById(R.id.edt_timeout)
40 | btnStartWork = findViewById(R.id.btn_compute)
41 | txtResult = findViewById(R.id.txt_result)
42 | }
43 |
44 | btnStartWork.setOnClickListener { _ ->
45 | if (edtArgument.text.toString().isEmpty()) {
46 | return@setOnClickListener
47 | }
48 |
49 | txtResult.text = ""
50 | btnStartWork.isEnabled = false
51 |
52 |
53 | val imm = requireContext().getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager
54 | imm.hideSoftInputFromWindow(btnStartWork.windowToken, 0)
55 |
56 | val argument = Integer.valueOf(edtArgument.text.toString())
57 |
58 | computeFactorialUseCase.computeFactorialAndNotify(argument, getTimeout())
59 | }
60 |
61 | return view
62 | }
63 |
64 | override fun onStart() {
65 | super.onStart()
66 | computeFactorialUseCase.registerListener(this)
67 | }
68 |
69 | override fun onStop() {
70 | super.onStop()
71 | computeFactorialUseCase.unregisterListener(this)
72 |
73 | }
74 |
75 | override fun getScreenTitle(): String {
76 | return "Exercise 10"
77 | }
78 |
79 | override fun onFactorialComputed(result: BigInteger) {
80 | txtResult.text = result.toString()
81 | btnStartWork.isEnabled = true
82 | }
83 |
84 | override fun onFactorialComputationTimedOut() {
85 | txtResult.text = "Computation timed out"
86 | btnStartWork.isEnabled = true
87 | }
88 |
89 | override fun onFactorialComputationAborted() {
90 | txtResult.text = "Computation aborted"
91 | btnStartWork.isEnabled = true
92 | }
93 |
94 | private fun getTimeout() : Int {
95 | var timeout: Int
96 | if (edtTimeout.text.toString().isEmpty()) {
97 | timeout = MAX_TIMEOUT_MS
98 | } else {
99 | timeout = Integer.valueOf(edtTimeout.text.toString())
100 | if (timeout > MAX_TIMEOUT_MS) {
101 | timeout = MAX_TIMEOUT_MS
102 | }
103 | }
104 | return timeout
105 | }
106 |
107 | companion object {
108 | fun newInstance(): Fragment {
109 | return Exercise10Fragment()
110 | }
111 | private const val MAX_TIMEOUT_MS = DefaultConfiguration.DEFAULT_FACTORIAL_TIMEOUT_MS
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/multithreading/exercises/exercise10/tips.txt:
--------------------------------------------------------------------------------
1 | Approach exercise 10 in three steps:
2 |
3 | Step 1: remove custom implementation of Observer pattern and wrap the current implementation of factorial computation algorithm in coroutine
4 | Step 2: refactor the internal implementation of the use case to coroutines, but keep the timeout detection logic intact
5 | Step 3: replace custom timeout detection logic with a call to "withTimeout" suspending function
6 |
7 | Hints for the solution:
8 | 1. The idea behind structured concurrency is to make your multithreaded code look sequential. Therefore, "clean" solution with coroutines shouldn't have any class-level properties (no shared mutable state).
9 | 2. Without mutable shared state, the solution should look similar to this (in principle, not in details):
10 |
11 | val intermediateResult1 = intermediateResult1()
12 | val intermediateResult2 = intermediateResult2(intermediateResult1)
13 | val result = computeFinalResult(intermediateResult2)
14 |
15 | 3. You might want to implement function with this signature in your solution:
16 |
17 | private fun CoroutineScope.computeProductForRangeAsync(computationRange: ComputationRange) : Deferred = async(Dispatchers.IO) {
18 | }
19 |
20 | 4. Note that top level suspending function will need to return either success or timeout to Fragment. You can use sealed class for the return type.
21 |
22 |
23 |
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/multithreading/exercises/exercise2/Exercise2Fragment.java:
--------------------------------------------------------------------------------
1 | package com.techyourchance.multithreading.exercises.exercise2;
2 |
3 | import android.os.Bundle;
4 | import android.util.Log;
5 | import android.view.LayoutInflater;
6 | import android.view.View;
7 | import android.view.ViewGroup;
8 | import android.widget.ImageView;
9 |
10 | import com.techyourchance.multithreading.R;
11 | import com.techyourchance.multithreading.common.BaseFragment;
12 |
13 | import androidx.annotation.NonNull;
14 | import androidx.annotation.Nullable;
15 | import androidx.core.content.ContextCompat;
16 | import androidx.fragment.app.Fragment;
17 |
18 | public class Exercise2Fragment extends BaseFragment {
19 |
20 | public static Fragment newInstance() {
21 | return new Exercise2Fragment();
22 | }
23 |
24 | private byte[] mDummyData;
25 |
26 | @Nullable
27 | @Override
28 | public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
29 | mDummyData = new byte[50 * 1000 * 1000];
30 | return inflater.inflate(R.layout.fragment_exercise_2, container, false);
31 | }
32 |
33 | @Override
34 | public void onStart() {
35 | super.onStart();
36 | countScreenTime();
37 | }
38 |
39 | @Override
40 | public void onStop() {
41 | super.onStop();
42 | }
43 |
44 | @Override
45 | protected String getScreenTitle() {
46 | return "Exercise 2";
47 | }
48 |
49 | private void countScreenTime() {
50 | new Thread(new Runnable() {
51 | @Override
52 | public void run() {
53 | int screenTimeSeconds = 0;
54 | while (true) {
55 | try {
56 | Thread.sleep(1000);
57 | } catch (InterruptedException e) {
58 | return;
59 | }
60 | screenTimeSeconds++;
61 | Log.d("Exercise 2", "screen time: " + screenTimeSeconds + "s");
62 | }
63 | }
64 | }).start();
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/multithreading/exercises/exercise3/Exercise3Fragment.java:
--------------------------------------------------------------------------------
1 | package com.techyourchance.multithreading.exercises.exercise3;
2 |
3 | import android.os.Bundle;
4 | import android.view.LayoutInflater;
5 | import android.view.View;
6 | import android.view.ViewGroup;
7 | import android.widget.Button;
8 | import android.widget.TextView;
9 |
10 | import com.techyourchance.multithreading.R;
11 | import com.techyourchance.multithreading.common.BaseFragment;
12 |
13 | import androidx.annotation.NonNull;
14 | import androidx.annotation.Nullable;
15 | import androidx.fragment.app.Fragment;
16 |
17 | public class Exercise3Fragment extends BaseFragment {
18 |
19 | private static final int SECONDS_TO_COUNT = 3;
20 |
21 | public static Fragment newInstance() {
22 | return new Exercise3Fragment();
23 | }
24 |
25 | private Button mBtnCountSeconds;
26 | private TextView mTxtCount;
27 |
28 | @Nullable
29 | @Override
30 | public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
31 | View view = inflater.inflate(R.layout.fragment_exercise_3, container, false);
32 |
33 | mBtnCountSeconds = view.findViewById(R.id.btn_count_seconds);
34 | mTxtCount = view.findViewById(R.id.txt_count);
35 |
36 | mBtnCountSeconds.setOnClickListener(new View.OnClickListener() {
37 | @Override
38 | public void onClick(View v) {
39 | countIterations();
40 | }
41 | });
42 |
43 | return view;
44 | }
45 |
46 | @Override
47 | protected String getScreenTitle() {
48 | return "Exercise 3";
49 | }
50 |
51 | private void countIterations() {
52 | /*
53 | 1. Disable button to prevent multiple clicks
54 | 2. Start counting on background thread using loop and Thread.sleep()
55 | 3. Show count in TextView
56 | 4. When count completes, show "done" in TextView and enable the button
57 | */
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/multithreading/exercises/exercise7/Exercise7Fragment.java:
--------------------------------------------------------------------------------
1 | package com.techyourchance.multithreading.exercises.exercise7;
2 |
3 | import android.app.Activity;
4 | import android.os.Bundle;
5 | import android.view.LayoutInflater;
6 | import android.view.View;
7 | import android.view.ViewGroup;
8 | import android.view.inputmethod.InputMethodManager;
9 | import android.widget.Button;
10 | import android.widget.EditText;
11 | import android.widget.TextView;
12 |
13 | import com.techyourchance.multithreading.DefaultConfiguration;
14 | import com.techyourchance.multithreading.R;
15 | import com.techyourchance.multithreading.common.BaseFragment;
16 |
17 | import java.math.BigInteger;
18 |
19 | import androidx.annotation.NonNull;
20 | import androidx.annotation.Nullable;
21 | import androidx.fragment.app.Fragment;
22 |
23 | public class Exercise7Fragment extends BaseFragment implements ComputeFactorialUseCase.Listener {
24 |
25 | public static Fragment newInstance() {
26 | return new Exercise7Fragment();
27 | }
28 |
29 | private static int MAX_TIMEOUT_MS = DefaultConfiguration.DEFAULT_FACTORIAL_TIMEOUT_MS;
30 |
31 | private EditText mEdtArgument;
32 | private EditText mEdtTimeout;
33 | private Button mBtnStartWork;
34 | private TextView mTxtResult;
35 |
36 | private ComputeFactorialUseCase mComputeFactorialUseCase;
37 |
38 | @Override
39 | public void onCreate(@Nullable Bundle savedInstanceState) {
40 | super.onCreate(savedInstanceState);
41 | mComputeFactorialUseCase = new ComputeFactorialUseCase();
42 | }
43 |
44 | @Nullable
45 | @Override
46 | public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
47 | View view = inflater.inflate(R.layout.fragment_exercise_7, container, false);
48 |
49 | mEdtArgument = view.findViewById(R.id.edt_argument);
50 | mEdtTimeout = view.findViewById(R.id.edt_timeout);
51 | mBtnStartWork = view.findViewById(R.id.btn_compute);
52 | mTxtResult = view.findViewById(R.id.txt_result);
53 |
54 | mBtnStartWork.setOnClickListener(v -> {
55 | if (mEdtArgument.getText().toString().isEmpty()) {
56 | return;
57 | }
58 |
59 | mTxtResult.setText("");
60 | mBtnStartWork.setEnabled(false);
61 |
62 |
63 | InputMethodManager imm =
64 | (InputMethodManager) requireContext().getSystemService(Activity.INPUT_METHOD_SERVICE);
65 | imm.hideSoftInputFromWindow(mBtnStartWork.getWindowToken(), 0);
66 |
67 | int argument = Integer.valueOf(mEdtArgument.getText().toString());
68 |
69 | mComputeFactorialUseCase.computeFactorialAndNotify(argument, getTimeout());
70 | });
71 |
72 | return view;
73 | }
74 |
75 | @Override
76 | public void onStart() {
77 | super.onStart();
78 | mComputeFactorialUseCase.registerListener(this);
79 | }
80 |
81 | @Override
82 | public void onStop() {
83 | super.onStop();
84 | mComputeFactorialUseCase.unregisterListener(this);
85 |
86 | }
87 |
88 | @Override
89 | protected String getScreenTitle() {
90 | return "Exercise 7";
91 | }
92 |
93 | private int getTimeout() {
94 | int timeout;
95 | if (mEdtTimeout.getText().toString().isEmpty()) {
96 | timeout = MAX_TIMEOUT_MS;
97 | } else {
98 | timeout = Integer.valueOf(mEdtTimeout.getText().toString());
99 | if (timeout > MAX_TIMEOUT_MS) {
100 | timeout = MAX_TIMEOUT_MS;
101 | }
102 | }
103 | return timeout;
104 | }
105 |
106 | @Override
107 | public void onFactorialComputed(BigInteger result) {
108 | mTxtResult.setText(result.toString());
109 | mBtnStartWork.setEnabled(true);
110 | }
111 |
112 | @Override
113 | public void onFactorialComputationTimedOut() {
114 | mTxtResult.setText("Computation timed out");
115 | mBtnStartWork.setEnabled(true);
116 | }
117 |
118 | @Override
119 | public void onFactorialComputationAborted() {
120 | mTxtResult.setText("Computation aborted");
121 | mBtnStartWork.setEnabled(true);
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/multithreading/exercises/exercise8/Exercise8Fragment.java:
--------------------------------------------------------------------------------
1 | package com.techyourchance.multithreading.exercises.exercise8;
2 |
3 | import android.app.Activity;
4 | import android.os.Bundle;
5 | import android.view.LayoutInflater;
6 | import android.view.View;
7 | import android.view.ViewGroup;
8 | import android.view.inputmethod.InputMethodManager;
9 | import android.widget.Button;
10 | import android.widget.EditText;
11 | import android.widget.TextView;
12 |
13 | import com.techyourchance.multithreading.DefaultConfiguration;
14 | import com.techyourchance.multithreading.R;
15 | import com.techyourchance.multithreading.common.BaseFragment;
16 |
17 | import java.math.BigInteger;
18 |
19 | import androidx.annotation.NonNull;
20 | import androidx.annotation.Nullable;
21 | import androidx.fragment.app.Fragment;
22 |
23 | public class Exercise8Fragment extends BaseFragment implements ComputeFactorialUseCase.Listener {
24 |
25 | public static Fragment newInstance() {
26 | return new Exercise8Fragment();
27 | }
28 |
29 | private static int MAX_TIMEOUT_MS = DefaultConfiguration.DEFAULT_FACTORIAL_TIMEOUT_MS;
30 |
31 | private EditText mEdtArgument;
32 | private EditText mEdtTimeout;
33 | private Button mBtnStartWork;
34 | private TextView mTxtResult;
35 |
36 | private ComputeFactorialUseCase mComputeFactorialUseCase;
37 |
38 | @Override
39 | public void onCreate(@Nullable Bundle savedInstanceState) {
40 | super.onCreate(savedInstanceState);
41 | mComputeFactorialUseCase = new ComputeFactorialUseCase();
42 | }
43 |
44 | @Nullable
45 | @Override
46 | public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
47 | View view = inflater.inflate(R.layout.fragment_exercise_8, container, false);
48 |
49 | mEdtArgument = view.findViewById(R.id.edt_argument);
50 | mEdtTimeout = view.findViewById(R.id.edt_timeout);
51 | mBtnStartWork = view.findViewById(R.id.btn_compute);
52 | mTxtResult = view.findViewById(R.id.txt_result);
53 |
54 | mBtnStartWork.setOnClickListener(v -> {
55 | if (mEdtArgument.getText().toString().isEmpty()) {
56 | return;
57 | }
58 |
59 | mTxtResult.setText("");
60 | mBtnStartWork.setEnabled(false);
61 |
62 |
63 | InputMethodManager imm =
64 | (InputMethodManager) requireContext().getSystemService(Activity.INPUT_METHOD_SERVICE);
65 | imm.hideSoftInputFromWindow(mBtnStartWork.getWindowToken(), 0);
66 |
67 | int argument = Integer.valueOf(mEdtArgument.getText().toString());
68 |
69 | mComputeFactorialUseCase.computeFactorialAndNotify(argument, getTimeout());
70 | });
71 |
72 | return view;
73 | }
74 |
75 | @Override
76 | public void onStart() {
77 | super.onStart();
78 | mComputeFactorialUseCase.registerListener(this);
79 | }
80 |
81 | @Override
82 | public void onStop() {
83 | super.onStop();
84 | mComputeFactorialUseCase.unregisterListener(this);
85 |
86 | }
87 |
88 | @Override
89 | protected String getScreenTitle() {
90 | return "Exercise 8";
91 | }
92 |
93 | private int getTimeout() {
94 | int timeout;
95 | if (mEdtTimeout.getText().toString().isEmpty()) {
96 | timeout = MAX_TIMEOUT_MS;
97 | } else {
98 | timeout = Integer.valueOf(mEdtTimeout.getText().toString());
99 | if (timeout > MAX_TIMEOUT_MS) {
100 | timeout = MAX_TIMEOUT_MS;
101 | }
102 | }
103 | return timeout;
104 | }
105 |
106 | @Override
107 | public void onFactorialComputed(BigInteger result) {
108 | mTxtResult.setText(result.toString());
109 | mBtnStartWork.setEnabled(true);
110 | }
111 |
112 | @Override
113 | public void onFactorialComputationTimedOut() {
114 | mTxtResult.setText("Computation timed out");
115 | mBtnStartWork.setEnabled(true);
116 | }
117 |
118 | @Override
119 | public void onFactorialComputationAborted() {
120 | mTxtResult.setText("Computation aborted");
121 | mBtnStartWork.setEnabled(true);
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/multithreading/exercises/exercise9/Exercise9Fragment.java:
--------------------------------------------------------------------------------
1 | package com.techyourchance.multithreading.exercises.exercise9;
2 |
3 | import android.app.Activity;
4 | import android.os.Bundle;
5 | import android.view.LayoutInflater;
6 | import android.view.View;
7 | import android.view.ViewGroup;
8 | import android.view.inputmethod.InputMethodManager;
9 | import android.widget.Button;
10 | import android.widget.EditText;
11 | import android.widget.TextView;
12 |
13 | import com.techyourchance.multithreading.DefaultConfiguration;
14 | import com.techyourchance.multithreading.R;
15 | import com.techyourchance.multithreading.common.BaseFragment;
16 |
17 | import java.math.BigInteger;
18 |
19 | import androidx.annotation.NonNull;
20 | import androidx.annotation.Nullable;
21 | import androidx.fragment.app.Fragment;
22 |
23 | public class Exercise9Fragment extends BaseFragment implements ComputeFactorialUseCase.Listener {
24 |
25 | public static Fragment newInstance() {
26 | return new Exercise9Fragment();
27 | }
28 |
29 | private static int MAX_TIMEOUT_MS = DefaultConfiguration.DEFAULT_FACTORIAL_TIMEOUT_MS;
30 |
31 | private EditText mEdtArgument;
32 | private EditText mEdtTimeout;
33 | private Button mBtnStartWork;
34 | private TextView mTxtResult;
35 |
36 | private ComputeFactorialUseCase mComputeFactorialUseCase;
37 |
38 | @Override
39 | public void onCreate(@Nullable Bundle savedInstanceState) {
40 | super.onCreate(savedInstanceState);
41 | mComputeFactorialUseCase = new ComputeFactorialUseCase();
42 | }
43 |
44 | @Nullable
45 | @Override
46 | public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
47 | View view = inflater.inflate(R.layout.fragment_exercise_9, container, false);
48 |
49 | mEdtArgument = view.findViewById(R.id.edt_argument);
50 | mEdtTimeout = view.findViewById(R.id.edt_timeout);
51 | mBtnStartWork = view.findViewById(R.id.btn_compute);
52 | mTxtResult = view.findViewById(R.id.txt_result);
53 |
54 | mBtnStartWork.setOnClickListener(v -> {
55 | if (mEdtArgument.getText().toString().isEmpty()) {
56 | return;
57 | }
58 |
59 | mTxtResult.setText("");
60 | mBtnStartWork.setEnabled(false);
61 |
62 |
63 | InputMethodManager imm =
64 | (InputMethodManager) requireContext().getSystemService(Activity.INPUT_METHOD_SERVICE);
65 | imm.hideSoftInputFromWindow(mBtnStartWork.getWindowToken(), 0);
66 |
67 | int argument = Integer.valueOf(mEdtArgument.getText().toString());
68 |
69 | mComputeFactorialUseCase.computeFactorialAndNotify(argument, getTimeout());
70 | });
71 |
72 | return view;
73 | }
74 |
75 | @Override
76 | public void onStart() {
77 | super.onStart();
78 | mComputeFactorialUseCase.registerListener(this);
79 | }
80 |
81 | @Override
82 | public void onStop() {
83 | super.onStop();
84 | mComputeFactorialUseCase.unregisterListener(this);
85 |
86 | }
87 |
88 | @Override
89 | protected String getScreenTitle() {
90 | return "Exercise 9";
91 | }
92 |
93 | private int getTimeout() {
94 | int timeout;
95 | if (mEdtTimeout.getText().toString().isEmpty()) {
96 | timeout = MAX_TIMEOUT_MS;
97 | } else {
98 | timeout = Integer.valueOf(mEdtTimeout.getText().toString());
99 | if (timeout > MAX_TIMEOUT_MS) {
100 | timeout = MAX_TIMEOUT_MS;
101 | }
102 | }
103 | return timeout;
104 | }
105 |
106 | @Override
107 | public void onFactorialComputed(BigInteger result) {
108 | mTxtResult.setText(result.toString());
109 | mBtnStartWork.setEnabled(true);
110 | }
111 |
112 | @Override
113 | public void onFactorialComputationTimedOut() {
114 | mTxtResult.setText("Computation timed out");
115 | mBtnStartWork.setEnabled(true);
116 | }
117 |
118 | @Override
119 | public void onFactorialComputationAborted() {
120 | mTxtResult.setText("Computation aborted");
121 | mBtnStartWork.setEnabled(true);
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/multithreading/home/HomeArrayAdapter.java:
--------------------------------------------------------------------------------
1 | package com.techyourchance.multithreading.home;
2 |
3 | import android.content.Context;
4 | import android.view.LayoutInflater;
5 | import android.view.View;
6 | import android.view.ViewGroup;
7 | import android.widget.ArrayAdapter;
8 | import android.widget.TextView;
9 |
10 | import com.techyourchance.multithreading.R;
11 |
12 | import androidx.annotation.NonNull;
13 |
14 | public class HomeArrayAdapter extends ArrayAdapter {
15 |
16 | public interface Listener {
17 | void onScreenClicked(ScreenReachableFromHome screenReachableFromHome);
18 | }
19 |
20 | private final Listener mListener;
21 |
22 | public HomeArrayAdapter(@NonNull Context context, Listener listener) {
23 | super(context, 0);
24 | mListener = listener;
25 | }
26 |
27 | @Override
28 | public View getView(int position, View convertView, ViewGroup parent) {
29 | if (convertView == null) {
30 | convertView = LayoutInflater.from(getContext())
31 | .inflate(R.layout.list_item_screen_reachable_from_home, parent, false);
32 | }
33 |
34 | final ScreenReachableFromHome screenReachableFromHome = getItem(position);
35 |
36 | // display screen name
37 | TextView txtName = convertView.findViewById(R.id.txt_screen_name);
38 | txtName.setText(screenReachableFromHome.getName());
39 |
40 | // set click listener on individual item view
41 | convertView.setOnClickListener(new View.OnClickListener() {
42 | @Override
43 | public void onClick(View v) {
44 | mListener.onScreenClicked(screenReachableFromHome);
45 | }
46 | });
47 |
48 | return convertView;
49 | }
50 |
51 | }
52 |
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/multithreading/home/HomeFragment.java:
--------------------------------------------------------------------------------
1 | package com.techyourchance.multithreading.home;
2 |
3 | import android.os.Bundle;
4 | import android.view.LayoutInflater;
5 | import android.view.View;
6 | import android.view.ViewGroup;
7 | import android.widget.ListView;
8 |
9 | import com.techyourchance.multithreading.common.BaseFragment;
10 | import com.techyourchance.multithreading.R;
11 | import com.techyourchance.multithreading.common.ScreensNavigator;
12 |
13 | import androidx.annotation.NonNull;
14 | import androidx.annotation.Nullable;
15 | import androidx.fragment.app.Fragment;
16 |
17 | public class HomeFragment extends BaseFragment implements HomeArrayAdapter.Listener {
18 |
19 | public static Fragment newInstance() {
20 | return new HomeFragment();
21 | }
22 |
23 | private ScreensNavigator mScreensNavigator;
24 |
25 | private ListView mListScreensReachableFromHome;
26 | private HomeArrayAdapter mAdapterScreensReachableFromHome;
27 |
28 | @Nullable
29 | @Override
30 | public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
31 | View view = inflater.inflate(R.layout.fragment_home, container, false);
32 |
33 | mScreensNavigator = getCompositionRoot().getScreensNavigator();
34 |
35 | mAdapterScreensReachableFromHome = new HomeArrayAdapter(requireContext(), this);
36 | mListScreensReachableFromHome = view.findViewById(R.id.list_screens);
37 | mListScreensReachableFromHome.setAdapter(mAdapterScreensReachableFromHome);
38 |
39 | mAdapterScreensReachableFromHome.addAll(ScreenReachableFromHome.values());
40 | mAdapterScreensReachableFromHome.notifyDataSetChanged();
41 |
42 | return view;
43 | }
44 |
45 | @Override
46 | protected String getScreenTitle() {
47 | return "Home Screen";
48 | }
49 |
50 | @Nullable
51 | @Override
52 | public Fragment getHierarchicalParentFragment() {
53 | return null;
54 | }
55 |
56 | @Override
57 | public void onScreenClicked(ScreenReachableFromHome screenReachableFromHome) {
58 | switch (screenReachableFromHome) {
59 | case EXERCISE_1:
60 | mScreensNavigator.toExercise1Screen();
61 | break;
62 | case EXERCISE_2:
63 | mScreensNavigator.toExercise2Screen();
64 | break;
65 | case UI_THREAD_DEMONSTRATION:
66 | mScreensNavigator.toUiThreadDemonstration();
67 | break;
68 | case UI_HANDLER_DEMONSTRATION:
69 | mScreensNavigator.toUiHandlerDemonstration();
70 | break;
71 | case CUSTOM_HANDLER_DEMONSTRATION:
72 | mScreensNavigator.toCustomHandlerDemonstration();
73 | break;
74 | case EXERCISE_3:
75 | mScreensNavigator.toExercise3Screen();
76 | break;
77 | case ATOMICITY_DEMONSTRATION:
78 | mScreensNavigator.toAtomicityDemonstration();
79 | break;
80 | case EXERCISE_4:
81 | mScreensNavigator.toExercise4Screen();
82 | break;
83 | case THREAD_WAIT_DEMONSTRATION:
84 | mScreensNavigator.toThreadWaitDemonstration();
85 | break;
86 | case EXERCISE_5:
87 | mScreensNavigator.toExercise5Screen();
88 | break;
89 | case DESIGN_WITH_THREADS_DEMONSTRATION:
90 | mScreensNavigator.toDesignWithThreadsDemonstration();
91 | break;
92 | case EXERCISE_6:
93 | mScreensNavigator.toExercise6Screen();
94 | break;
95 | case DESIGN_WITH_THREAD_POOL_DEMONSTRATION:
96 | mScreensNavigator.toDesignWithThreadPoolDemonstration();
97 | break;
98 | case EXERCISE_7:
99 | mScreensNavigator.toExercise7Screen();
100 | break;
101 | case DESIGN_WITH_ASYNCTASK_DEMONSTRATION:
102 | mScreensNavigator.toDesignWithAsyncTaskDemonstration();
103 | break;
104 | case DESIGN_WITH_THREAD_POSTER_DEMONSTRATION:
105 | mScreensNavigator.toThreadPosterDemonstration();
106 | break;
107 | case EXERCISE_8:
108 | mScreensNavigator.toExercise8Screen();
109 | break;
110 | case DESIGN_WITH_RX_JAVA_DEMONSTRATION:
111 | mScreensNavigator.toDesignWithRxJavaDemonstration();
112 | break;
113 | case EXERCISE_9:
114 | mScreensNavigator.toExercise9Screen();
115 | break;
116 | case DESIGN_WITH_COROUTINES_DEMONSTRATION:
117 | mScreensNavigator.toDesignWithCoroutinesDemonstration();
118 | break;
119 | case EXERCISE_10:
120 | mScreensNavigator.toExercise10Screen();
121 | break;
122 | }
123 | }
124 | }
125 |
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/multithreading/home/ScreenReachableFromHome.java:
--------------------------------------------------------------------------------
1 | package com.techyourchance.multithreading.home;
2 |
3 | public enum ScreenReachableFromHome {
4 | EXERCISE_1("Exercise 1"),
5 | EXERCISE_2("Exercise 2"),
6 | UI_THREAD_DEMONSTRATION("UI Thread Demo"),
7 | UI_HANDLER_DEMONSTRATION("UI Handler Demo"),
8 | CUSTOM_HANDLER_DEMONSTRATION("Custom Handler Demo"),
9 | EXERCISE_3("Exercise 3"),
10 | ATOMICITY_DEMONSTRATION("Atomicity Demo"),
11 | EXERCISE_4("Exercise 4"),
12 | THREAD_WAIT_DEMONSTRATION("Thread Wait Demo"),
13 | EXERCISE_5("Exercise 5"),
14 | DESIGN_WITH_THREADS_DEMONSTRATION("Design Demo: Threads"),
15 | EXERCISE_6("Exercise 6"),
16 | DESIGN_WITH_THREAD_POOL_DEMONSTRATION("Design Demo: Thread Pool"),
17 | EXERCISE_7("Exercise 7"),
18 | DESIGN_WITH_ASYNCTASK_DEMONSTRATION("Design Demo: AsyncTask"),
19 | DESIGN_WITH_THREAD_POSTER_DEMONSTRATION("Design Demo: ThreadPoster"),
20 | EXERCISE_8("Exercise 8"),
21 | DESIGN_WITH_RX_JAVA_DEMONSTRATION("Design Demo: RxJava"),
22 | EXERCISE_9("Exercise 9"),
23 | DESIGN_WITH_COROUTINES_DEMONSTRATION("Design Demo: Coroutines"),
24 | EXERCISE_10("Exercise 10"),
25 | ;
26 |
27 | private String mName;
28 |
29 | ScreenReachableFromHome(String name) {
30 | mName = name;
31 | }
32 |
33 | public String getName() {
34 | return mName;
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/multithreading/solutions/exercise1/SolutionExercise1Fragment.java:
--------------------------------------------------------------------------------
1 | package com.techyourchance.multithreading.solutions.exercise1;
2 |
3 | import android.os.Bundle;
4 | import android.util.Log;
5 | import android.view.LayoutInflater;
6 | import android.view.View;
7 | import android.view.ViewGroup;
8 | import android.widget.Button;
9 |
10 | import com.techyourchance.multithreading.R;
11 | import com.techyourchance.multithreading.common.BaseFragment;
12 |
13 | import androidx.annotation.NonNull;
14 | import androidx.annotation.Nullable;
15 | import androidx.fragment.app.Fragment;
16 |
17 | public class SolutionExercise1Fragment extends BaseFragment {
18 |
19 | private static final int ITERATIONS_COUNTER_DURATION_SEC = 10;
20 |
21 | public static Fragment newInstance() {
22 | return new SolutionExercise1Fragment();
23 | }
24 |
25 | private Button mBtnCountIterations;
26 |
27 | @Nullable
28 | @Override
29 | public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
30 | View view = inflater.inflate(R.layout.fragment_exercise_1, container, false);
31 |
32 | mBtnCountIterations = view.findViewById(R.id.btn_count_iterations);
33 | mBtnCountIterations.setOnClickListener(new View.OnClickListener() {
34 | @Override
35 | public void onClick(View v) {
36 | countIterations();
37 | }
38 | });
39 |
40 | return view;
41 | }
42 |
43 | @Override
44 | protected String getScreenTitle() {
45 | return "Exercise 1";
46 | }
47 |
48 | private void countIterations() {
49 | new Thread(new Runnable() {
50 | @Override
51 | public void run() {
52 | long startTimestamp = System.currentTimeMillis();
53 | long endTimestamp = startTimestamp + ITERATIONS_COUNTER_DURATION_SEC * 1000;
54 |
55 | int iterationsCount = 0;
56 | while (System.currentTimeMillis() <= endTimestamp) {
57 | iterationsCount++;
58 | }
59 |
60 | Log.d(
61 | "Exercise1",
62 | "iterations in " + ITERATIONS_COUNTER_DURATION_SEC + "seconds: " + iterationsCount
63 | );
64 | }
65 | }).start();
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/multithreading/solutions/exercise10/ComputeFactorialUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.techyourchance.multithreading.solutions.exercise10
2 |
3 | import java.math.BigInteger
4 |
5 | import androidx.annotation.WorkerThread
6 | import kotlinx.coroutines.*
7 | import java.util.concurrent.TimeUnit
8 |
9 | class ComputeFactorialUseCase {
10 |
11 | public sealed class Result {
12 | class Success(val result: BigInteger) : Result()
13 | object Timeout : Result()
14 | }
15 |
16 | suspend fun computeFactorial(argument: Int, timeout: Int) : Result {
17 |
18 | return withContext(Dispatchers.IO) {
19 |
20 | try {
21 | withTimeout(timeMillis = timeout.toLong()) {
22 |
23 | val computationRanges = getComputationRanges(argument)
24 |
25 | val partialProductsForRanges = computePartialProducts(computationRanges)
26 |
27 | val result = computeFinalResult(partialProductsForRanges)
28 |
29 | Result.Success(result)
30 | }
31 | } catch (e : TimeoutCancellationException) {
32 | Result.Timeout
33 | }
34 |
35 | }
36 | }
37 |
38 | private fun getComputationRanges(factorialArgument: Int) : Array {
39 | val numberOfThreads = getNumberOfThreads(factorialArgument)
40 |
41 | val threadsComputationRanges = Array(numberOfThreads) { ComputationRange(0, 0) }
42 |
43 | val computationRangeSize = factorialArgument / numberOfThreads
44 |
45 | var nextComputationRangeEnd = factorialArgument.toLong()
46 |
47 | for (i in numberOfThreads - 1 downTo 0) {
48 | threadsComputationRanges[i] = ComputationRange(
49 | nextComputationRangeEnd - computationRangeSize + 1,
50 | nextComputationRangeEnd
51 | )
52 | nextComputationRangeEnd = threadsComputationRanges[i].start - 1
53 | }
54 |
55 | // add potentially "remaining" values to first thread's range
56 | threadsComputationRanges[0] = ComputationRange(1, threadsComputationRanges[0].end)
57 |
58 | return threadsComputationRanges
59 | }
60 |
61 | private fun getNumberOfThreads(factorialArgument: Int): Int {
62 | return if (factorialArgument < 20)
63 | 1
64 | else
65 | Runtime.getRuntime().availableProcessors()
66 | }
67 |
68 | private suspend fun computePartialProducts(computationRanges: Array) : List = coroutineScope {
69 | return@coroutineScope withContext(Dispatchers.IO) {
70 | return@withContext computationRanges.map {
71 | computeProductForRangeAsync(it)
72 | }.awaitAll()
73 | }
74 | }
75 |
76 | private fun CoroutineScope.computeProductForRangeAsync(computationRange: ComputationRange) : Deferred = async(Dispatchers.IO) {
77 | val rangeStart = computationRange.start
78 | val rangeEnd = computationRange.end
79 |
80 | var product = BigInteger("1")
81 | for (num in rangeStart..rangeEnd) {
82 | if (!isActive) {
83 | break
84 | }
85 | product = product.multiply(BigInteger(num.toString()))
86 | }
87 |
88 | return@async product
89 | }
90 |
91 | private suspend fun computeFinalResult(partialProducts: List): BigInteger = withContext(Dispatchers.IO) {
92 | var result = BigInteger("1")
93 | for (partialProduct in partialProducts) {
94 | if (!isActive) {
95 | break
96 | }
97 | result = result.multiply(partialProduct)
98 | }
99 | return@withContext result
100 | }
101 |
102 | private data class ComputationRange(val start: Long, val end: Long)
103 | }
104 |
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/multithreading/solutions/exercise10/Exercise10Fragment.kt:
--------------------------------------------------------------------------------
1 | package com.techyourchance.multithreading.solutions.exercise10
2 |
3 | import android.app.Activity
4 | import android.os.Bundle
5 | import android.view.LayoutInflater
6 | import android.view.View
7 | import android.view.ViewGroup
8 | import android.view.inputmethod.InputMethodManager
9 | import android.widget.Button
10 | import android.widget.EditText
11 | import android.widget.TextView
12 |
13 | import com.techyourchance.multithreading.R
14 | import com.techyourchance.multithreading.common.BaseFragment
15 |
16 | import java.math.BigInteger
17 | import androidx.fragment.app.Fragment
18 | import com.techyourchance.multithreading.DefaultConfiguration
19 | import kotlinx.coroutines.CoroutineScope
20 | import kotlinx.coroutines.Dispatchers
21 | import kotlinx.coroutines.Job
22 | import kotlinx.coroutines.launch
23 |
24 | class SolutionExercise10Fragment : BaseFragment() {
25 |
26 | private lateinit var edtArgument: EditText
27 | private lateinit var edtTimeout: EditText
28 | private lateinit var btnStartWork: Button
29 | private lateinit var txtResult: TextView
30 |
31 | private lateinit var computeFactorialUseCase: ComputeFactorialUseCase
32 |
33 | private var job : Job? = null
34 |
35 | override fun onCreate(savedInstanceState: Bundle?) {
36 | super.onCreate(savedInstanceState)
37 | computeFactorialUseCase = ComputeFactorialUseCase()
38 | }
39 |
40 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
41 | val view = inflater.inflate(R.layout.fragment_exercise_10, container, false)
42 |
43 | view.apply {
44 | edtArgument = findViewById(R.id.edt_argument)
45 | edtTimeout = findViewById(R.id.edt_timeout)
46 | btnStartWork = findViewById(R.id.btn_compute)
47 | txtResult = findViewById(R.id.txt_result)
48 | }
49 |
50 | btnStartWork.setOnClickListener { _ ->
51 | if (edtArgument.text.toString().isEmpty()) {
52 | return@setOnClickListener
53 | }
54 |
55 | txtResult.text = ""
56 | btnStartWork.isEnabled = false
57 |
58 |
59 | val imm = requireContext().getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager
60 | imm.hideSoftInputFromWindow(btnStartWork.windowToken, 0)
61 |
62 | val argument = Integer.valueOf(edtArgument.text.toString())
63 |
64 | job = CoroutineScope(Dispatchers.Main).launch {
65 | when (val result = computeFactorialUseCase.computeFactorial(argument, getTimeout())) {
66 | is ComputeFactorialUseCase.Result.Success -> onFactorialComputed(result.result)
67 | is ComputeFactorialUseCase.Result.Timeout -> onFactorialComputationTimedOut()
68 | }
69 | }
70 | }
71 |
72 | return view
73 | }
74 |
75 | override fun onStart() {
76 | super.onStart()
77 | }
78 |
79 | override fun onStop() {
80 | super.onStop()
81 | job?.apply { cancel() }
82 | }
83 |
84 | override fun getScreenTitle(): String {
85 | return "Exercise 10"
86 | }
87 |
88 | fun onFactorialComputed(result: BigInteger) {
89 | txtResult.text = result.toString()
90 | btnStartWork.isEnabled = true
91 | }
92 |
93 | fun onFactorialComputationTimedOut() {
94 | txtResult.text = "Computation timed out"
95 | btnStartWork.isEnabled = true
96 | }
97 |
98 | private fun getTimeout() : Int {
99 | var timeout: Int
100 | if (edtTimeout.text.toString().isEmpty()) {
101 | timeout = MAX_TIMEOUT_MS
102 | } else {
103 | timeout = Integer.valueOf(edtTimeout.text.toString())
104 | if (timeout > MAX_TIMEOUT_MS) {
105 | timeout = MAX_TIMEOUT_MS
106 | }
107 | }
108 | return timeout
109 | }
110 |
111 | companion object {
112 | fun newInstance(): Fragment {
113 | return SolutionExercise10Fragment()
114 | }
115 | private const val MAX_TIMEOUT_MS = DefaultConfiguration.DEFAULT_FACTORIAL_TIMEOUT_MS
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/multithreading/solutions/exercise2/SolutionExercise2Fragment.java:
--------------------------------------------------------------------------------
1 | package com.techyourchance.multithreading.solutions.exercise2;
2 |
3 | import android.os.Bundle;
4 | import android.util.Log;
5 | import android.view.LayoutInflater;
6 | import android.view.View;
7 | import android.view.ViewGroup;
8 |
9 | import com.techyourchance.multithreading.R;
10 | import com.techyourchance.multithreading.common.BaseFragment;
11 |
12 | import java.util.concurrent.atomic.AtomicBoolean;
13 |
14 | import androidx.annotation.NonNull;
15 | import androidx.annotation.Nullable;
16 | import androidx.fragment.app.Fragment;
17 |
18 | public class SolutionExercise2Fragment extends BaseFragment {
19 |
20 | public static Fragment newInstance() {
21 | return new SolutionExercise2Fragment();
22 | }
23 |
24 | private byte[] mDummyData;
25 | private final AtomicBoolean mCountAbort = new AtomicBoolean(false);
26 |
27 | @Nullable
28 | @Override
29 | public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
30 | mDummyData = new byte[50 * 1000 * 1000];
31 | return inflater.inflate(R.layout.fragment_exercise_2, container, false);
32 | }
33 |
34 | @Override
35 | public void onStart() {
36 | super.onStart();
37 | countScreenTime();
38 | }
39 |
40 | @Override
41 | public void onStop() {
42 | super.onStop();
43 | mCountAbort.set(true);
44 | }
45 |
46 | @Override
47 | protected String getScreenTitle() {
48 | return "Exercise 2";
49 | }
50 |
51 | private void countScreenTime() {
52 |
53 | mCountAbort.set(false);
54 |
55 | new Thread(new Runnable() {
56 | @Override
57 | public void run() {
58 | int screenTimeSeconds = 0;
59 | while (true) {
60 | try {
61 | Thread.sleep(1000);
62 | } catch (InterruptedException e) {
63 | return;
64 | }
65 | if (mCountAbort.get()) {
66 | return;
67 | }
68 | screenTimeSeconds++;
69 | Log.d("Exercise 2", "screen time: " + screenTimeSeconds + "s");
70 | }
71 | }
72 | }).start();
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/multithreading/solutions/exercise3/SolutionExercise3Fragment.java:
--------------------------------------------------------------------------------
1 | package com.techyourchance.multithreading.solutions.exercise3;
2 |
3 | import android.os.Bundle;
4 | import android.os.Handler;
5 | import android.os.Looper;
6 | import android.view.LayoutInflater;
7 | import android.view.View;
8 | import android.view.ViewGroup;
9 | import android.widget.Button;
10 | import android.widget.TextView;
11 |
12 | import com.techyourchance.multithreading.R;
13 | import com.techyourchance.multithreading.common.BaseFragment;
14 |
15 | import androidx.annotation.NonNull;
16 | import androidx.annotation.Nullable;
17 | import androidx.fragment.app.Fragment;
18 |
19 | public class SolutionExercise3Fragment extends BaseFragment {
20 |
21 | private static final int SECONDS_TO_COUNT = 3;
22 |
23 | public static Fragment newInstance() {
24 | return new SolutionExercise3Fragment();
25 | }
26 |
27 | private Button mBtnCountSeconds;
28 | private TextView mTxtCount;
29 |
30 | private final Handler mUiHandler = new Handler(Looper.getMainLooper());
31 |
32 | @Nullable
33 | @Override
34 | public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
35 | View view = inflater.inflate(R.layout.fragment_exercise_3, container, false);
36 |
37 | mBtnCountSeconds = view.findViewById(R.id.btn_count_seconds);
38 | mTxtCount = view.findViewById(R.id.txt_count);
39 |
40 | mBtnCountSeconds.setOnClickListener(new View.OnClickListener() {
41 | @Override
42 | public void onClick(View v) {
43 | countIterations();
44 | }
45 | });
46 |
47 | return view;
48 | }
49 |
50 | @Override
51 | protected String getScreenTitle() {
52 | return "Exercise 3";
53 | }
54 |
55 | private void countIterations() {
56 | mBtnCountSeconds.setEnabled(false);
57 | new Thread(new Runnable() {
58 | @Override
59 | public void run() {
60 | for (int i = 1; i <= SECONDS_TO_COUNT; i++) {
61 | final int count = i;
62 | mUiHandler.post(new Runnable() {
63 | @Override
64 | public void run() {
65 | mTxtCount.setText(String.valueOf(count));
66 | }
67 | });
68 | try {
69 | Thread.sleep(1000);
70 | } catch (InterruptedException e) {
71 | return;
72 | }
73 | }
74 | mUiHandler.post(new Runnable() {
75 | @Override
76 | public void run() {
77 | mTxtCount.setText("Done!");
78 | mBtnCountSeconds.setEnabled(true);
79 | }
80 | });
81 | }
82 | }).start();
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/multithreading/solutions/exercise6/SolutionExercise6Fragment.java:
--------------------------------------------------------------------------------
1 | package com.techyourchance.multithreading.solutions.exercise6;
2 |
3 | import android.app.Activity;
4 | import android.os.Bundle;
5 | import android.view.LayoutInflater;
6 | import android.view.View;
7 | import android.view.ViewGroup;
8 | import android.view.inputmethod.InputMethodManager;
9 | import android.widget.Button;
10 | import android.widget.EditText;
11 | import android.widget.TextView;
12 |
13 | import com.techyourchance.multithreading.DefaultConfiguration;
14 | import com.techyourchance.multithreading.R;
15 | import com.techyourchance.multithreading.common.BaseFragment;
16 |
17 | import java.math.BigInteger;
18 |
19 | import androidx.annotation.NonNull;
20 | import androidx.annotation.Nullable;
21 | import androidx.fragment.app.Fragment;
22 |
23 | public class SolutionExercise6Fragment extends BaseFragment implements ComputeFactorialUseCase.Listener {
24 |
25 | public static Fragment newInstance() {
26 | return new SolutionExercise6Fragment();
27 | }
28 |
29 | private static int MAX_TIMEOUT_MS = DefaultConfiguration.DEFAULT_FACTORIAL_TIMEOUT_MS;
30 |
31 | private EditText mEdtArgument;
32 | private EditText mEdtTimeout;
33 | private Button mBtnStartWork;
34 | private TextView mTxtResult;
35 |
36 | private ComputeFactorialUseCase mComputeFactorialUseCase;
37 |
38 | @Override
39 | public void onCreate(@Nullable Bundle savedInstanceState) {
40 | super.onCreate(savedInstanceState);
41 | mComputeFactorialUseCase = new ComputeFactorialUseCase();
42 | }
43 |
44 | @Nullable
45 | @Override
46 | public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
47 | View view = inflater.inflate(R.layout.fragment_exercise_6, container, false);
48 |
49 | mEdtArgument = view.findViewById(R.id.edt_argument);
50 | mEdtTimeout = view.findViewById(R.id.edt_timeout);
51 | mBtnStartWork = view.findViewById(R.id.btn_compute);
52 | mTxtResult = view.findViewById(R.id.txt_result);
53 |
54 | mBtnStartWork.setOnClickListener(v -> {
55 | if (mEdtArgument.getText().toString().isEmpty()) {
56 | return;
57 | }
58 |
59 | mTxtResult.setText("");
60 | mBtnStartWork.setEnabled(false);
61 |
62 |
63 | InputMethodManager imm =
64 | (InputMethodManager) requireContext().getSystemService(Activity.INPUT_METHOD_SERVICE);
65 | imm.hideSoftInputFromWindow(mBtnStartWork.getWindowToken(), 0);
66 |
67 | int argument = Integer.valueOf(mEdtArgument.getText().toString());
68 |
69 | mComputeFactorialUseCase.computeFactorialAndNotify(argument, getTimeout());
70 | });
71 |
72 | return view;
73 | }
74 |
75 | @Override
76 | public void onStart() {
77 | super.onStart();
78 | mComputeFactorialUseCase.registerListener(this);
79 | }
80 |
81 | @Override
82 | public void onStop() {
83 | super.onStop();
84 | mComputeFactorialUseCase.unregisterListener(this);
85 |
86 | }
87 |
88 | @Override
89 | protected String getScreenTitle() {
90 | return "Exercise 6";
91 | }
92 |
93 | private int getTimeout() {
94 | int timeout;
95 | if (mEdtTimeout.getText().toString().isEmpty()) {
96 | timeout = MAX_TIMEOUT_MS;
97 | } else {
98 | timeout = Integer.valueOf(mEdtTimeout.getText().toString());
99 | if (timeout > MAX_TIMEOUT_MS) {
100 | timeout = MAX_TIMEOUT_MS;
101 | }
102 | }
103 | return timeout;
104 | }
105 |
106 | @Override
107 | public void onFactorialComputed(BigInteger result) {
108 | mTxtResult.setText(result.toString());
109 | mBtnStartWork.setEnabled(true);
110 | }
111 |
112 | @Override
113 | public void onFactorialComputationTimedOut() {
114 | mTxtResult.setText("Computation timed out");
115 | mBtnStartWork.setEnabled(true);
116 | }
117 |
118 | @Override
119 | public void onFactorialComputationAborted() {
120 | mTxtResult.setText("Computation aborted");
121 | mBtnStartWork.setEnabled(true);
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/multithreading/solutions/exercise7/SolutionExercise7Fragment.java:
--------------------------------------------------------------------------------
1 | package com.techyourchance.multithreading.solutions.exercise7;
2 |
3 | import android.app.Activity;
4 | import android.os.Bundle;
5 | import android.view.LayoutInflater;
6 | import android.view.View;
7 | import android.view.ViewGroup;
8 | import android.view.inputmethod.InputMethodManager;
9 | import android.widget.Button;
10 | import android.widget.EditText;
11 | import android.widget.TextView;
12 |
13 | import com.techyourchance.multithreading.DefaultConfiguration;
14 | import com.techyourchance.multithreading.R;
15 | import com.techyourchance.multithreading.common.BaseFragment;
16 |
17 | import java.math.BigInteger;
18 |
19 | import androidx.annotation.NonNull;
20 | import androidx.annotation.Nullable;
21 | import androidx.fragment.app.Fragment;
22 |
23 | public class SolutionExercise7Fragment extends BaseFragment implements ComputeFactorialUseCase.Listener {
24 |
25 | public static Fragment newInstance() {
26 | return new SolutionExercise7Fragment();
27 | }
28 |
29 | private static int MAX_TIMEOUT_MS = DefaultConfiguration.DEFAULT_FACTORIAL_TIMEOUT_MS;
30 |
31 | private EditText mEdtArgument;
32 | private EditText mEdtTimeout;
33 | private Button mBtnStartWork;
34 | private TextView mTxtResult;
35 |
36 | private ComputeFactorialUseCase mComputeFactorialUseCase;
37 |
38 | @Override
39 | public void onCreate(@Nullable Bundle savedInstanceState) {
40 | super.onCreate(savedInstanceState);
41 | mComputeFactorialUseCase = new ComputeFactorialUseCase(
42 | getCompositionRoot().getUiHandler(),
43 | getCompositionRoot().getThreadPool()
44 | );
45 | }
46 |
47 | @Nullable
48 | @Override
49 | public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
50 | View view = inflater.inflate(R.layout.fragment_exercise_7, container, false);
51 |
52 | mEdtArgument = view.findViewById(R.id.edt_argument);
53 | mEdtTimeout = view.findViewById(R.id.edt_timeout);
54 | mBtnStartWork = view.findViewById(R.id.btn_compute);
55 | mTxtResult = view.findViewById(R.id.txt_result);
56 |
57 | mBtnStartWork.setOnClickListener(v -> {
58 | if (mEdtArgument.getText().toString().isEmpty()) {
59 | return;
60 | }
61 |
62 | mTxtResult.setText("");
63 | mBtnStartWork.setEnabled(false);
64 |
65 |
66 | InputMethodManager imm =
67 | (InputMethodManager) requireContext().getSystemService(Activity.INPUT_METHOD_SERVICE);
68 | imm.hideSoftInputFromWindow(mBtnStartWork.getWindowToken(), 0);
69 |
70 | int argument = Integer.valueOf(mEdtArgument.getText().toString());
71 |
72 | mComputeFactorialUseCase.computeFactorialAndNotify(argument, getTimeout());
73 | });
74 |
75 | return view;
76 | }
77 |
78 | @Override
79 | public void onStart() {
80 | super.onStart();
81 | mComputeFactorialUseCase.registerListener(this);
82 | }
83 |
84 | @Override
85 | public void onStop() {
86 | super.onStop();
87 | mComputeFactorialUseCase.unregisterListener(this);
88 |
89 | }
90 |
91 | @Override
92 | protected String getScreenTitle() {
93 | return "Exercise 7";
94 | }
95 |
96 | private int getTimeout() {
97 | int timeout;
98 | if (mEdtTimeout.getText().toString().isEmpty()) {
99 | timeout = MAX_TIMEOUT_MS;
100 | } else {
101 | timeout = Integer.valueOf(mEdtTimeout.getText().toString());
102 | if (timeout > MAX_TIMEOUT_MS) {
103 | timeout = MAX_TIMEOUT_MS;
104 | }
105 | }
106 | return timeout;
107 | }
108 |
109 | @Override
110 | public void onFactorialComputed(BigInteger result) {
111 | mTxtResult.setText(result.toString());
112 | mBtnStartWork.setEnabled(true);
113 | }
114 |
115 | @Override
116 | public void onFactorialComputationTimedOut() {
117 | mTxtResult.setText("Computation timed out");
118 | mBtnStartWork.setEnabled(true);
119 | }
120 |
121 | @Override
122 | public void onFactorialComputationAborted() {
123 | mTxtResult.setText("Computation aborted");
124 | mBtnStartWork.setEnabled(true);
125 | }
126 | }
127 |
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/multithreading/solutions/exercise8/SolutionExercise8Fragment.java:
--------------------------------------------------------------------------------
1 | package com.techyourchance.multithreading.solutions.exercise8;
2 |
3 | import android.app.Activity;
4 | import android.os.Bundle;
5 | import android.view.LayoutInflater;
6 | import android.view.View;
7 | import android.view.ViewGroup;
8 | import android.view.inputmethod.InputMethodManager;
9 | import android.widget.Button;
10 | import android.widget.EditText;
11 | import android.widget.TextView;
12 |
13 | import com.techyourchance.multithreading.DefaultConfiguration;
14 | import com.techyourchance.multithreading.R;
15 | import com.techyourchance.multithreading.common.BaseFragment;
16 | import com.techyourchance.threadposter.BackgroundThreadPoster;
17 | import com.techyourchance.threadposter.UiThreadPoster;
18 |
19 | import java.math.BigInteger;
20 |
21 | import androidx.annotation.NonNull;
22 | import androidx.annotation.Nullable;
23 | import androidx.fragment.app.Fragment;
24 |
25 | public class SolutionExercise8Fragment extends BaseFragment implements ComputeFactorialUseCase.Listener {
26 |
27 | public static Fragment newInstance() {
28 | return new SolutionExercise8Fragment();
29 | }
30 |
31 | private static int MAX_TIMEOUT_MS = DefaultConfiguration.DEFAULT_FACTORIAL_TIMEOUT_MS;
32 |
33 | private EditText mEdtArgument;
34 | private EditText mEdtTimeout;
35 | private Button mBtnStartWork;
36 | private TextView mTxtResult;
37 |
38 | private ComputeFactorialUseCase mComputeFactorialUseCase;
39 |
40 | @Override
41 | public void onCreate(@Nullable Bundle savedInstanceState) {
42 | super.onCreate(savedInstanceState);
43 | mComputeFactorialUseCase = new ComputeFactorialUseCase(
44 | new UiThreadPoster(), // you need to get this dependency from composition root
45 | new BackgroundThreadPoster() // you need to get this dependency from composition root
46 | );
47 | }
48 |
49 | @Nullable
50 | @Override
51 | public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
52 | View view = inflater.inflate(R.layout.fragment_exercise_8, container, false);
53 |
54 | mEdtArgument = view.findViewById(R.id.edt_argument);
55 | mEdtTimeout = view.findViewById(R.id.edt_timeout);
56 | mBtnStartWork = view.findViewById(R.id.btn_compute);
57 | mTxtResult = view.findViewById(R.id.txt_result);
58 |
59 | mBtnStartWork.setOnClickListener(v -> {
60 | if (mEdtArgument.getText().toString().isEmpty()) {
61 | return;
62 | }
63 |
64 | mTxtResult.setText("");
65 | mBtnStartWork.setEnabled(false);
66 |
67 |
68 | InputMethodManager imm =
69 | (InputMethodManager) requireContext().getSystemService(Activity.INPUT_METHOD_SERVICE);
70 | imm.hideSoftInputFromWindow(mBtnStartWork.getWindowToken(), 0);
71 |
72 | int argument = Integer.valueOf(mEdtArgument.getText().toString());
73 |
74 | mComputeFactorialUseCase.computeFactorialAndNotify(argument, getTimeout());
75 | });
76 |
77 | return view;
78 | }
79 |
80 | @Override
81 | public void onStart() {
82 | super.onStart();
83 | mComputeFactorialUseCase.registerListener(this);
84 | }
85 |
86 | @Override
87 | public void onStop() {
88 | super.onStop();
89 | mComputeFactorialUseCase.unregisterListener(this);
90 |
91 | }
92 |
93 | @Override
94 | protected String getScreenTitle() {
95 | return "Exercise 8";
96 | }
97 |
98 | private int getTimeout() {
99 | int timeout;
100 | if (mEdtTimeout.getText().toString().isEmpty()) {
101 | timeout = MAX_TIMEOUT_MS;
102 | } else {
103 | timeout = Integer.valueOf(mEdtTimeout.getText().toString());
104 | if (timeout > MAX_TIMEOUT_MS) {
105 | timeout = MAX_TIMEOUT_MS;
106 | }
107 | }
108 | return timeout;
109 | }
110 |
111 | @Override
112 | public void onFactorialComputed(BigInteger result) {
113 | mTxtResult.setText(result.toString());
114 | mBtnStartWork.setEnabled(true);
115 | }
116 |
117 | @Override
118 | public void onFactorialComputationTimedOut() {
119 | mTxtResult.setText("Computation timed out");
120 | mBtnStartWork.setEnabled(true);
121 | }
122 |
123 | @Override
124 | public void onFactorialComputationAborted() {
125 | mTxtResult.setText("Computation aborted");
126 | mBtnStartWork.setEnabled(true);
127 | }
128 | }
129 |
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/multithreading/solutions/exercise9/ComputeFactorialUseCase.java:
--------------------------------------------------------------------------------
1 | package com.techyourchance.multithreading.solutions.exercise9;
2 |
3 | import android.os.Handler;
4 | import android.os.Looper;
5 |
6 | import com.techyourchance.multithreading.common.BaseObservable;
7 |
8 | import java.math.BigInteger;
9 |
10 | import androidx.annotation.WorkerThread;
11 | import io.reactivex.Flowable;
12 | import io.reactivex.Observable;
13 | import io.reactivex.schedulers.Schedulers;
14 |
15 | public class ComputeFactorialUseCase {
16 |
17 | public static class Result {
18 | private final boolean mIsAborted;
19 | private final boolean mIsTimedOut;
20 | private final BigInteger mResult;
21 |
22 | public Result(boolean isAborted, boolean isTimedOut, BigInteger result) {
23 | mIsAborted = isAborted;
24 | mIsTimedOut = isTimedOut;
25 | mResult = result;
26 | }
27 |
28 | public boolean isAborted() {
29 | return mIsAborted;
30 | }
31 |
32 | public boolean isTimedOut() {
33 | return mIsTimedOut;
34 | }
35 |
36 | public BigInteger getResult() {
37 | return mResult;
38 | }
39 | }
40 |
41 | private int mNumberOfThreads;
42 | private ComputationRange[] mThreadsComputationRanges;
43 |
44 | private long mComputationTimeoutTime;
45 |
46 | public Observable computeFactorial(final int argument, final int timeout) {
47 |
48 | initComputationParams(argument, timeout);
49 |
50 | return Flowable
51 | .range(0, mNumberOfThreads)
52 | .parallel(mNumberOfThreads)
53 | .runOn(Schedulers.io())
54 | .map(this::computePart)
55 | .sequential()
56 | .scan((bigInteger, bigInteger2) -> {
57 | if (isTimedOut()) {
58 | return bigInteger;
59 | } else {
60 | return bigInteger.multiply(bigInteger2);
61 | }
62 | })
63 | .last(new BigInteger("0"))
64 | .map(result -> {
65 | if (isTimedOut()) {
66 | return new Result(false, true, result);
67 | }
68 | return new Result(false, false, result);
69 | })
70 | .onErrorReturnItem(new Result(true, false, new BigInteger("0")))
71 | .toObservable();
72 | }
73 |
74 | @WorkerThread
75 | private BigInteger computePart(int id) {
76 | long rangeStart = mThreadsComputationRanges[id].start;
77 | long rangeEnd = mThreadsComputationRanges[id].end;
78 | BigInteger product = new BigInteger("1");
79 | for (long num = rangeStart; num <= rangeEnd; num++) {
80 | if (isTimedOut()) {
81 | break;
82 | }
83 | product = product.multiply(new BigInteger(String.valueOf(num)));
84 | }
85 | return product;
86 | }
87 |
88 | private void initComputationParams(int factorialArgument, int timeout) {
89 | mNumberOfThreads = factorialArgument < 20
90 | ? 1 : Runtime.getRuntime().availableProcessors();
91 |
92 | mThreadsComputationRanges = new ComputationRange[mNumberOfThreads];
93 |
94 | initThreadsComputationRanges(factorialArgument);
95 |
96 | mComputationTimeoutTime = System.currentTimeMillis() + timeout;
97 | }
98 |
99 | private void initThreadsComputationRanges(int factorialArgument) {
100 | int computationRangeSize = factorialArgument / mNumberOfThreads;
101 |
102 | long nextComputationRangeEnd = factorialArgument;
103 | for (int i = mNumberOfThreads - 1; i >= 0; i--) {
104 | mThreadsComputationRanges[i] = new ComputationRange(
105 | nextComputationRangeEnd - computationRangeSize + 1,
106 | nextComputationRangeEnd
107 | );
108 | nextComputationRangeEnd = mThreadsComputationRanges[i].start - 1;
109 | }
110 |
111 | // add potentially "remaining" values to first thread's range
112 | mThreadsComputationRanges[0].start = 1;
113 | }
114 |
115 | private long getRemainingMillisToTimeout() {
116 | return mComputationTimeoutTime - System.currentTimeMillis();
117 | }
118 |
119 | private boolean isTimedOut() {
120 | return System.currentTimeMillis() >= mComputationTimeoutTime;
121 | }
122 |
123 | private static class ComputationRange {
124 | private long start;
125 | private long end;
126 |
127 | public ComputationRange(long start, long end) {
128 | this.start = start;
129 | this.end = end;
130 | }
131 | }
132 | }
133 |
--------------------------------------------------------------------------------
/app/src/main/java/com/techyourchance/multithreading/solutions/exercise9/SolutionExercise9Fragment.java:
--------------------------------------------------------------------------------
1 | package com.techyourchance.multithreading.solutions.exercise9;
2 |
3 | import android.app.Activity;
4 | import android.os.Bundle;
5 | import android.view.LayoutInflater;
6 | import android.view.View;
7 | import android.view.ViewGroup;
8 | import android.view.inputmethod.InputMethodManager;
9 | import android.widget.Button;
10 | import android.widget.EditText;
11 | import android.widget.TextView;
12 |
13 | import com.techyourchance.multithreading.DefaultConfiguration;
14 | import com.techyourchance.multithreading.R;
15 | import com.techyourchance.multithreading.common.BaseFragment;
16 |
17 | import java.math.BigInteger;
18 |
19 | import androidx.annotation.NonNull;
20 | import androidx.annotation.Nullable;
21 | import androidx.fragment.app.Fragment;
22 | import io.reactivex.android.schedulers.AndroidSchedulers;
23 | import io.reactivex.disposables.Disposable;
24 | import io.reactivex.schedulers.Schedulers;
25 |
26 | public class SolutionExercise9Fragment extends BaseFragment {
27 |
28 | public static Fragment newInstance() {
29 | return new SolutionExercise9Fragment();
30 | }
31 |
32 | private static int MAX_TIMEOUT_MS = DefaultConfiguration.DEFAULT_FACTORIAL_TIMEOUT_MS;
33 |
34 | private EditText mEdtArgument;
35 | private EditText mEdtTimeout;
36 | private Button mBtnStartWork;
37 | private TextView mTxtResult;
38 |
39 | private ComputeFactorialUseCase mComputeFactorialUseCase;
40 |
41 | private @Nullable Disposable mDisposable;
42 |
43 | @Override
44 | public void onCreate(@Nullable Bundle savedInstanceState) {
45 | super.onCreate(savedInstanceState);
46 | mComputeFactorialUseCase = new ComputeFactorialUseCase();
47 | }
48 |
49 | @Nullable
50 | @Override
51 | public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
52 | View view = inflater.inflate(R.layout.fragment_exercise_9, container, false);
53 |
54 | mEdtArgument = view.findViewById(R.id.edt_argument);
55 | mEdtTimeout = view.findViewById(R.id.edt_timeout);
56 | mBtnStartWork = view.findViewById(R.id.btn_compute);
57 | mTxtResult = view.findViewById(R.id.txt_result);
58 |
59 | mBtnStartWork.setOnClickListener(v -> {
60 | if (mEdtArgument.getText().toString().isEmpty()) {
61 | return;
62 | }
63 |
64 | mTxtResult.setText("");
65 | mBtnStartWork.setEnabled(false);
66 |
67 | InputMethodManager imm =
68 | (InputMethodManager) requireContext().getSystemService(Activity.INPUT_METHOD_SERVICE);
69 | imm.hideSoftInputFromWindow(mBtnStartWork.getWindowToken(), 0);
70 |
71 | int argument = Integer.valueOf(mEdtArgument.getText().toString());
72 |
73 | mDisposable = mComputeFactorialUseCase.computeFactorial(argument, getTimeout())
74 | .subscribeOn(Schedulers.io())
75 | .observeOn(AndroidSchedulers.mainThread())
76 | .subscribe(result -> {
77 | if (result.isAborted()) {
78 | onFactorialComputationAborted();
79 | } else if (result.isTimedOut()) {
80 | onFactorialComputationTimedOut();
81 | } else {
82 | onFactorialComputed(result.getResult());
83 | }
84 | });
85 | });
86 |
87 | return view;
88 | }
89 |
90 | @Override
91 | public void onStop() {
92 | super.onStop();
93 | if (mDisposable != null) {
94 | mDisposable.dispose();
95 | }
96 | }
97 |
98 | @Override
99 | protected String getScreenTitle() {
100 | return "Exercise 9";
101 | }
102 |
103 | private int getTimeout() {
104 | int timeout;
105 | if (mEdtTimeout.getText().toString().isEmpty()) {
106 | timeout = MAX_TIMEOUT_MS;
107 | } else {
108 | timeout = Integer.valueOf(mEdtTimeout.getText().toString());
109 | if (timeout > MAX_TIMEOUT_MS) {
110 | timeout = MAX_TIMEOUT_MS;
111 | }
112 | }
113 | return timeout;
114 | }
115 |
116 | public void onFactorialComputed(BigInteger result) {
117 | mTxtResult.setText(result.toString());
118 | mBtnStartWork.setEnabled(true);
119 | }
120 |
121 | public void onFactorialComputationTimedOut() {
122 | mTxtResult.setText("Computation timed out");
123 | mBtnStartWork.setEnabled(true);
124 | }
125 |
126 | public void onFactorialComputationAborted() {
127 | mTxtResult.setText("Computation aborted");
128 | mBtnStartWork.setEnabled(true);
129 | }
130 | }
131 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_arrow_back.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/techyourchance/android-multithreading-masterclass/d68cb3839a6dd15b1a8d361de40be8dcff203686/app/src/main/res/drawable-hdpi/ic_arrow_back.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_arrow_back.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/techyourchance/android-multithreading-masterclass/d68cb3839a6dd15b1a8d361de40be8dcff203686/app/src/main/res/drawable-mdpi/ic_arrow_back.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
12 |
13 |
19 |
22 |
25 |
26 |
27 |
28 |
34 |
35 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_arrow_back.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/techyourchance/android-multithreading-masterclass/d68cb3839a6dd15b1a8d361de40be8dcff203686/app/src/main/res/drawable-xhdpi/ic_arrow_back.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_arrow_back.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/techyourchance/android-multithreading-masterclass/d68cb3839a6dd15b1a8d361de40be8dcff203686/app/src/main/res/drawable-xxhdpi/ic_arrow_back.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_arrow_back.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/techyourchance/android-multithreading-masterclass/d68cb3839a6dd15b1a8d361de40be8dcff203686/app/src/main/res/drawable-xxxhdpi/ic_arrow_back.png
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
19 |
20 |
24 |
25 |
35 |
36 |
43 |
44 |
45 |
46 |
47 |
48 |
52 |
53 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_atomicity_demonstration.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
16 |
17 |
23 |
24 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_custom_looper_demonstration.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
16 |
17 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_design_with_coroutines_demonstration.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
16 |
17 |
23 |
24 |
31 |
32 |
38 |
39 |
45 |
46 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_design_with_thread_demonstration.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
16 |
17 |
24 |
25 |
31 |
32 |
38 |
39 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_design_with_thread_pool_demonstration.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
16 |
17 |
24 |
25 |
31 |
32 |
38 |
39 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_exercise_1.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
16 |
17 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_exercise_10.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
10 |
15 |
16 |
24 |
25 |
33 |
34 |
39 |
40 |
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_exercise_2.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_exercise_3.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
16 |
17 |
23 |
24 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_exercise_4.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
10 |
15 |
16 |
24 |
25 |
33 |
34 |
39 |
40 |
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_exercise_5.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
16 |
17 |
24 |
25 |
31 |
32 |
38 |
39 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_exercise_6.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
10 |
15 |
16 |
24 |
25 |
33 |
34 |
39 |
40 |
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_exercise_7.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
10 |
15 |
16 |
24 |
25 |
33 |
34 |
39 |
40 |
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_exercise_8.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
10 |
15 |
16 |
24 |
25 |
33 |
34 |
39 |
40 |
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_exercise_9.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
10 |
15 |
16 |
24 |
25 |
33 |
34 |
39 |
40 |
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_home.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_thread_wait_demonstration.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
10 |
15 |
16 |
24 |
25 |
33 |
34 |
39 |
40 |
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_ui_handler_demonstration.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
16 |
17 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_ui_thread_demonstration.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
16 |
17 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/list_item_screen_reachable_from_home.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
18 |
19 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/techyourchance/android-multithreading-masterclass/d68cb3839a6dd15b1a8d361de40be8dcff203686/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/techyourchance/android-multithreading-masterclass/d68cb3839a6dd15b1a8d361de40be8dcff203686/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/techyourchance/android-multithreading-masterclass/d68cb3839a6dd15b1a8d361de40be8dcff203686/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/techyourchance/android-multithreading-masterclass/d68cb3839a6dd15b1a8d361de40be8dcff203686/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/techyourchance/android-multithreading-masterclass/d68cb3839a6dd15b1a8d361de40be8dcff203686/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/techyourchance/android-multithreading-masterclass/d68cb3839a6dd15b1a8d361de40be8dcff203686/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/techyourchance/android-multithreading-masterclass/d68cb3839a6dd15b1a8d361de40be8dcff203686/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/techyourchance/android-multithreading-masterclass/d68cb3839a6dd15b1a8d361de40be8dcff203686/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/techyourchance/android-multithreading-masterclass/d68cb3839a6dd15b1a8d361de40be8dcff203686/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/techyourchance/android-multithreading-masterclass/d68cb3839a6dd15b1a8d361de40be8dcff203686/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #303F9F
5 | #FF4081
6 | #FFFFFF
7 | #000000
8 | #00000000
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Android Multithreading
3 |
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 |
2 | buildscript {
3 | ext.kotlin_version = '1.8.22'
4 |
5 | repositories {
6 | google()
7 | mavenCentral()
8 | mavenCentral()
9 | }
10 |
11 | dependencies {
12 | classpath 'com.android.tools.build:gradle:8.1.4'
13 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
14 | }
15 | }
16 |
17 | allprojects {
18 | repositories {
19 | google()
20 | mavenCentral()
21 | }
22 | }
23 |
24 | task clean(type: Delete) {
25 | delete rootProject.buildDir
26 | }
27 |
--------------------------------------------------------------------------------
/fragmenthelper/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/fragmenthelper/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.library'
3 | }
4 |
5 | android {
6 | namespace 'com.techyourchance.fragmenthelper'
7 | compileSdk 34
8 |
9 | defaultConfig {
10 | minSdk 24
11 |
12 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
13 | }
14 |
15 | buildTypes {
16 | release {
17 | minifyEnabled false
18 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
19 | }
20 | }
21 | compileOptions {
22 | sourceCompatibility JavaVersion.VERSION_1_8
23 | targetCompatibility JavaVersion.VERSION_1_8
24 | }
25 | }
26 |
27 | dependencies {
28 | implementation fileTree(dir: 'libs', include: ['*.jar'])
29 |
30 | implementation "androidx.appcompat:appcompat:1.6.1"
31 |
32 | testImplementation "junit:junit:4.13.2"
33 | testImplementation "org.mockito:mockito-core:3.12.4"
34 | }
35 |
--------------------------------------------------------------------------------
/fragmenthelper/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/fragmenthelper/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/fragmenthelper/src/main/java/com/techyourchance/fragmenthelper/FragmentContainerWrapper.java:
--------------------------------------------------------------------------------
1 | package com.techyourchance.fragmenthelper;
2 |
3 | import androidx.annotation.NonNull;
4 | import android.view.ViewGroup;
5 |
6 | public interface FragmentContainerWrapper {
7 |
8 | @NonNull ViewGroup getFragmentContainer();
9 | }
10 |
--------------------------------------------------------------------------------
/fragmenthelper/src/main/java/com/techyourchance/fragmenthelper/HierarchicalFragment.java:
--------------------------------------------------------------------------------
1 | package com.techyourchance.fragmenthelper;
2 |
3 | import androidx.annotation.Nullable;
4 | import androidx.fragment.app.Fragment;
5 |
6 | public interface HierarchicalFragment {
7 | /**
8 | * In case of UP navigation when Fragments back-stack is empty, the Fragment returned by this
9 | * method will be navigated to. If this method returns null, then UP navigation will be
10 | * delegated to enclosing Activity.
11 | * @return hierarchical parent Fragment of this Fragment; null if this Fragment has
12 | * no hierarchical parent
13 | */
14 | @Nullable
15 | Fragment getHierarchicalParentFragment();
16 | }
17 |
--------------------------------------------------------------------------------
/fragmenthelper/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx1536m
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app's APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Automatically convert third-party libraries to use AndroidX
19 | android.enableJetifier=true
20 |
21 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/techyourchance/android-multithreading-masterclass/d68cb3839a6dd15b1a8d361de40be8dcff203686/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Sat Oct 19 16:55:34 IDT 2019
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/release.keystore:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/techyourchance/android-multithreading-masterclass/d68cb3839a6dd15b1a8d361de40be8dcff203686/release.keystore
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 | include ':fragmenthelper'
3 |
--------------------------------------------------------------------------------