From b9a55799feb823971cb3768dcfa5cfdaceb3e483 Mon Sep 17 00:00:00 2001 From: Sunmisc Date: Wed, 5 Oct 2022 15:49:25 +0300 Subject: [PATCH 1/5] fixed redundant thread creation --- .../java/ru/vtosters/lite/net/NetCall.java | 41 +++++++++---------- .../java/ru/vtosters/lite/net/NetClient.java | 6 ++- 2 files changed, 25 insertions(+), 22 deletions(-) diff --git a/app/src/main/java/ru/vtosters/lite/net/NetCall.java b/app/src/main/java/ru/vtosters/lite/net/NetCall.java index d13545618..23d1f9484 100644 --- a/app/src/main/java/ru/vtosters/lite/net/NetCall.java +++ b/app/src/main/java/ru/vtosters/lite/net/NetCall.java @@ -15,24 +15,32 @@ import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.util.Map; import java.util.TreeMap; +import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; public final class NetCall { private final NetRequest req; private final NetClient cl; + private final Executor executor; - protected NetCall(NetClient cl, NetRequest req) { + protected NetCall(NetClient cl, NetRequest req, Executor executor) { this.req = req; this.cl = cl; + this.executor = executor; } private static String constructArgs(Map args) throws UnsupportedEncodingException { args = new TreeMap<>(args); - String str = ""; + StringBuilder str = new StringBuilder(); for (Map.Entry en : args.entrySet()) { - if (!str.isEmpty()) str += "&"; - str += URLEncoder.encode(en.getKey(), "UTF-8") + "=" + URLEncoder.encode(en.getValue(), "UTF-8"); + if (str.length() > 0) + str.append("&"); + str.append(URLEncoder.encode(en.getKey(), "UTF-8")) + .append("=") + .append(URLEncoder.encode(en.getValue(), "UTF-8")); } - return str; + return str.toString(); } public NetRequest request() { @@ -77,22 +85,13 @@ public final class NetCall { } public void enqueue(NetCallback cb) { - new Thread() { - @Override - public void run() { - try { - NetResponse resp = execute(); - cb.onResponse(NetCall.this, resp); - } catch (IOException e) { - cb.onFailure(NetCall.this, e); - } - try { - interrupt(); - join(); - } catch (Exception e) { - Log.e("NetCall", "Failed to interrupt and join thread", e); - } + executor.execute(() -> { + try { + NetResponse resp = execute(); + cb.onResponse(NetCall.this, resp); + } catch (IOException e) { + cb.onFailure(NetCall.this, e); } - }.start(); + }); } } diff --git a/app/src/main/java/ru/vtosters/lite/net/NetClient.java b/app/src/main/java/ru/vtosters/lite/net/NetClient.java index d3a89961e..2d5169cae 100644 --- a/app/src/main/java/ru/vtosters/lite/net/NetClient.java +++ b/app/src/main/java/ru/vtosters/lite/net/NetClient.java @@ -2,12 +2,16 @@ package ru.vtosters.lite.net; import java.net.PasswordAuthentication; import java.net.Proxy; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; public class NetClient { private final long timeout; private final Proxy proxy; private final PasswordAuthentication authenticator; + private final Executor executor + = Executors.newSingleThreadExecutor(); protected NetClient(Builder b) { timeout = b.timeout; @@ -24,7 +28,7 @@ public class NetClient { } public NetCall newCall(NetRequest req) { - return new NetCall(this, req); + return new NetCall(this, req, executor); } public PasswordAuthentication getAuthenticator() { From e79322a18cbb6be58bc0dc11649a2198dab3d677 Mon Sep 17 00:00:00 2001 From: Sunmisc Date: Wed, 5 Oct 2022 16:16:34 +0300 Subject: [PATCH 2/5] cleaned up and got rid of deprecated class --- .../java/ru/vtosters/lite/net/Request.java | 52 ++++++++++--------- 1 file changed, 27 insertions(+), 25 deletions(-) diff --git a/app/src/main/java/ru/vtosters/lite/net/Request.java b/app/src/main/java/ru/vtosters/lite/net/Request.java index b2aa1ec02..72483e47a 100644 --- a/app/src/main/java/ru/vtosters/lite/net/Request.java +++ b/app/src/main/java/ru/vtosters/lite/net/Request.java @@ -2,75 +2,77 @@ package ru.vtosters.lite.net; import static ru.vtosters.lite.utils.AccountManagerUtils.getUserToken; -import android.os.AsyncTask; - import com.vk.core.network.Network; import java.io.BufferedReader; import java.io.InputStreamReader; import java.net.HttpURLConnection; import java.net.URL; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; public class Request { - public static void makeRequest(byte[] bArr, RequestCallback RequestCallback) { - new MakeRequestTask(bArr, RequestCallback).execute(); + + private static final Executor EXECUTOR = + Executors.newSingleThreadExecutor(); + + public static void makeRequest(byte[] bArr, RequestCallback callback) { + EXECUTOR.execute(new MakeRequestTask(bArr, callback)); } - public static void makeRequest(String str, RequestCallback RequestCallback) { - new MakeRequestTask(str.getBytes(), RequestCallback).execute(); + public static void makeRequest(String str, RequestCallback callback) { + makeRequest(str.getBytes(), callback); } public interface RequestCallback { void onResponse(String str); } - private static class MakeRequestTask extends AsyncTask { + private static class MakeRequestTask implements Runnable { private final RequestCallback callback; private final byte[] url; private final boolean mIsPut; private final boolean mIsVTostersRequest; - public MakeRequestTask(byte[] bArr, RequestCallback RequestCallback) { - mIsVTostersRequest = false; - mIsPut = false; - callback = RequestCallback; - url = bArr; + public MakeRequestTask(byte[] bArr, RequestCallback callback) { + this.mIsVTostersRequest = false; + this.mIsPut = false; + this.callback = callback; + this.url = bArr; } - public String doInBackground(Void... voidArr) { + @Override + public void run() { try { HttpURLConnection httpURLConnection = (HttpURLConnection) new URL(new String(url)).openConnection(); httpURLConnection.setRequestMethod(mIsPut ? "PUT" : "GET"); if (mIsVTostersRequest) { httpURLConnection.setRequestProperty("Token", getUserToken()); - } - if (!mIsVTostersRequest) { + } else { httpURLConnection.setRequestProperty("User-Agent", Network.l.c().a()); } - httpURLConnection.setRequestProperty("Content-Type", mIsVTostersRequest ? "Content-Type" : "application/x-www-form-urlencoded"); + httpURLConnection.setRequestProperty("Content-Type", mIsVTostersRequest + ? "Content-Type" + : "application/x-www-form-urlencoded"); BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(httpURLConnection.getInputStream())); - StringBuffer stringBuffer = new StringBuffer(); + StringBuilder builder = new StringBuilder(); while (true) { String readLine = bufferedReader.readLine(); if (readLine != null) { - stringBuffer.append(readLine); + builder.append(readLine); } else { bufferedReader.close(); - return stringBuffer.toString(); + callback.onResponse(builder.toString()); + break; } } } catch (Exception e) { - return e.getLocalizedMessage(); + callback.onResponse(e.getLocalizedMessage()); } } - - public void onPostExecute(String str) { - super.onPostExecute(str); - callback.onResponse(str); - } } } \ No newline at end of file From b62de5b90eeb1f34ab31524d46daa7ecc1ef3415 Mon Sep 17 00:00:00 2001 From: Sunmisc Date: Wed, 5 Oct 2022 16:21:59 +0300 Subject: [PATCH 3/5] cleanup --- .../lite/ui/components/NewsfeedListManager.java | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/ru/vtosters/lite/ui/components/NewsfeedListManager.java b/app/src/main/java/ru/vtosters/lite/ui/components/NewsfeedListManager.java index 9bdad9f5d..283717c29 100644 --- a/app/src/main/java/ru/vtosters/lite/ui/components/NewsfeedListManager.java +++ b/app/src/main/java/ru/vtosters/lite/ui/components/NewsfeedListManager.java @@ -18,21 +18,19 @@ public class NewsfeedListManager { public static void callEditorPopup(Activity activity) { var selectedItems = getDefaultPrefs().getString("news_feed_selected_items", ""); - var filtersSet = getDefaultPrefs().getStringSet("news_feed_items_set", Collections.synchronizedSet(new LinkedHashSet<>())); - var list = new ArrayList(); + var filtersSet = getDefaultPrefs().getStringSet("news_feed_items_set", Collections.emptySet()); + var list = new ArrayList(filtersSet.size()); - if (filtersSet.size() == 0) { + if (filtersSet.isEmpty()) { sendToast(AndroidUtils.getString("newsfeed_list_update_feed")); return; } - synchronized (filtersSet) { - for (String item : filtersSet) { - var id = item.substring(0, item.indexOf("|")); - var title = item.substring(item.indexOf("|") + 1); + for (String item : filtersSet) { + var id = item.substring(0, item.indexOf("|")); + var title = item.substring(item.indexOf("|") + 1); - list.add(new VBListItem(id, title, selectedItems.contains(id))); - } + list.add(new VBListItem(id, title, selectedItems.contains(id))); } VBottomSheetBuilder.show(activity, new VBottomSheetBuilder.VBSContent( From 5bc44513552778a91c17247a8ebf3411de872d69 Mon Sep 17 00:00:00 2001 From: Sunmisc Date: Wed, 5 Oct 2022 16:22:20 +0300 Subject: [PATCH 4/5] cleanup --- .../java/ru/vtosters/lite/ui/components/NewsfeedListManager.java | 1 - 1 file changed, 1 deletion(-) diff --git a/app/src/main/java/ru/vtosters/lite/ui/components/NewsfeedListManager.java b/app/src/main/java/ru/vtosters/lite/ui/components/NewsfeedListManager.java index 283717c29..0874b2b1f 100644 --- a/app/src/main/java/ru/vtosters/lite/ui/components/NewsfeedListManager.java +++ b/app/src/main/java/ru/vtosters/lite/ui/components/NewsfeedListManager.java @@ -9,7 +9,6 @@ import android.app.Activity; import java.util.ArrayList; import java.util.Collections; -import java.util.LinkedHashSet; import ru.vtosters.lite.ui.vkui.VBottomSheetBuilder; import ru.vtosters.lite.utils.AndroidUtils; From e7db1e2359161f2efcdcab971c421fbe6541779e Mon Sep 17 00:00:00 2001 From: zelva Date: Fri, 7 Oct 2022 14:42:04 +0300 Subject: [PATCH 5/5] cleanup and fixed unwanted races --- .../ru/vtosters/lite/hooks/AdBlockHook.java | 10 ++++- .../ru/vtosters/lite/hooks/JsonInjectors.java | 40 +++++++++---------- .../ru/vtosters/lite/hooks/NewsfeedHook.java | 21 ++++++---- .../ru/vtosters/lite/music/M3UDownloader.java | 2 +- .../lite/translators/BaseTranslator.java | 16 ++++---- .../lite/translators/DeepLTranslator.java | 18 +++++---- .../lite/translators/GoogleTranslator.java | 8 ++-- .../lite/translators/YandexTranslator.java | 8 ++-- .../vtosters/lite/ui/dialogs/Translate.java | 30 +++++++++++--- 9 files changed, 95 insertions(+), 58 deletions(-) diff --git a/app/src/main/java/ru/vtosters/lite/hooks/AdBlockHook.java b/app/src/main/java/ru/vtosters/lite/hooks/AdBlockHook.java index 8fea29341..8a1eef738 100644 --- a/app/src/main/java/ru/vtosters/lite/hooks/AdBlockHook.java +++ b/app/src/main/java/ru/vtosters/lite/hooks/AdBlockHook.java @@ -339,7 +339,10 @@ public class AdBlockHook { return true; } - var whitelist = AndroidUtils.getDefaultPrefs().getStringSet("whitelisted_filters_groups", Collections.synchronizedSet(new LinkedHashSet<>())); + var whitelist = AndroidUtils.getDefaultPrefs().getStringSet( + "whitelisted_filters_groups", + Collections.emptySet() + ); return whitelist.contains(id); } @@ -351,7 +354,10 @@ public class AdBlockHook { id = String.valueOf(list.optInt("source_id")); } - var whitelist = AndroidUtils.getDefaultPrefs().getStringSet("whitelisted_ad_groups", Collections.synchronizedSet(new LinkedHashSet<>())); + var whitelist = AndroidUtils.getDefaultPrefs().getStringSet( + "whitelisted_ad_groups", + Collections.emptySet() + ); return whitelist.contains(id); } diff --git a/app/src/main/java/ru/vtosters/lite/hooks/JsonInjectors.java b/app/src/main/java/ru/vtosters/lite/hooks/JsonInjectors.java index c2dc406cd..ca2f34ecb 100644 --- a/app/src/main/java/ru/vtosters/lite/hooks/JsonInjectors.java +++ b/app/src/main/java/ru/vtosters/lite/hooks/JsonInjectors.java @@ -336,36 +336,34 @@ public class JsonInjectors { public static JSONArray newsfeedlist(JSONArray items) throws JSONException { var selectedItems = getDefaultPrefs().getString("news_feed_selected_items", ""); var filtersSet = getDefaultPrefs().getStringSet("news_feed_items_set", null); - var mutableFiltersSet = Collections.synchronizedSet(new LinkedHashSet()); + var mutableFiltersSet = new LinkedHashSet(); if (filtersSet != null) mutableFiltersSet.addAll(filtersSet); - synchronized (mutableFiltersSet) { - for (int i = 0; i < items.length(); i++) { - var item = items.optJSONObject(i); - if (item == null) continue; + for (int i = 0; i < items.length(); i++) { + var item = items.optJSONObject(i); + if (item == null) continue; - var id = item.optString("id"); - var title = item.optString("title"); + var id = item.optString("id"); + var title = item.optString("title"); - if (TextUtils.isEmpty(id) || TextUtils.isEmpty(title) - // this items not working - || id.equals("kpop") || id.equals("foryou") - || id.equals("qazaqstan") || id.equals("podcasts")) - continue; + if (TextUtils.isEmpty(id) || TextUtils.isEmpty(title) + // this items not working + || id.equals("kpop") || id.equals("foryou") + || id.equals("qazaqstan") || id.equals("podcasts")) + continue; - mutableFiltersSet.add(id + "|" + title); + mutableFiltersSet.add(id + "|" + title); - var hide = selectedItems.contains(id); + var hide = selectedItems.contains(id); - Log.d("NewsfeedList", "Added list " + id + " to feed"); - item.put("is_hidden", hide) - .put("is_unavailable", hide); - if (dev()) Log.d("NewsfeedListInj", "Unlocked " + id + " in newsfeed list"); - } - getDefaultPrefs().edit().putStringSet("news_feed_items_set", mutableFiltersSet) - .apply(); + Log.d("NewsfeedList", "Added list " + id + " to feed"); + item.put("is_hidden", hide) + .put("is_unavailable", hide); + if (dev()) Log.d("NewsfeedListInj", "Unlocked " + id + " in newsfeed list"); } + getDefaultPrefs().edit().putStringSet("news_feed_items_set", mutableFiltersSet) + .apply(); return items; } diff --git a/app/src/main/java/ru/vtosters/lite/hooks/NewsfeedHook.java b/app/src/main/java/ru/vtosters/lite/hooks/NewsfeedHook.java index 13df0be47..f15bf848c 100644 --- a/app/src/main/java/ru/vtosters/lite/hooks/NewsfeedHook.java +++ b/app/src/main/java/ru/vtosters/lite/hooks/NewsfeedHook.java @@ -70,19 +70,25 @@ public class NewsfeedHook { } public static boolean isWhitelistedFilter(ExtendedCommunityProfile eup) { - return getDefaultPrefs().getStringSet("whitelisted_filters_groups", Collections.synchronizedSet(new LinkedHashSet<>())).contains(String.valueOf(AccountManagerUtils.getUserID(eup))); + return getDefaultPrefs().getStringSet( + "whitelisted_filters_groups", Collections.emptySet() + ).contains(String.valueOf(AccountManagerUtils.getUserID(eup))); } public static boolean isWhitelistedAd(ExtendedCommunityProfile eup) { - return getDefaultPrefs().getStringSet("whitelisted_ad_groups", Collections.synchronizedSet(new LinkedHashSet<>())).contains(String.valueOf(AccountManagerUtils.getUserID(eup))); + return getDefaultPrefs().getStringSet( + "whitelisted_ad_groups", + Collections.emptySet() + ).contains(String.valueOf(AccountManagerUtils.getUserID(eup))); } public static void setWhitelistedFilter(ExtendedCommunityProfile eup, boolean needWhitelist) { - var mutableAdsSet = Collections.synchronizedSet(new LinkedHashSet()); var id = String.valueOf(AccountManagerUtils.getUserID(eup)); - mutableAdsSet.addAll(getDefaultPrefs().getStringSet("whitelisted_filters_groups", mutableAdsSet)); - + var mutableAdsSet = new LinkedHashSet<>(getDefaultPrefs().getStringSet( + "whitelisted_filters_groups", + Collections.emptySet()) + ); if (needWhitelist) { mutableAdsSet.add(id); } else { @@ -93,10 +99,11 @@ public class NewsfeedHook { } public static void setWhitelistedAd(ExtendedCommunityProfile eup, boolean needWhitelist) { - var mutableAdsSet = Collections.synchronizedSet(new LinkedHashSet()); var id = String.valueOf(AccountManagerUtils.getUserID(eup)); - mutableAdsSet.addAll(getDefaultPrefs().getStringSet("whitelisted_ad_groups", mutableAdsSet)); + var mutableAdsSet = new LinkedHashSet<>(getDefaultPrefs().getStringSet( + "whitelisted_ad_groups", + Collections.emptySet())); if (needWhitelist) { mutableAdsSet.add(id); diff --git a/app/src/main/java/ru/vtosters/lite/music/M3UDownloader.java b/app/src/main/java/ru/vtosters/lite/music/M3UDownloader.java index 79cbf02fb..480467915 100644 --- a/app/src/main/java/ru/vtosters/lite/music/M3UDownloader.java +++ b/app/src/main/java/ru/vtosters/lite/music/M3UDownloader.java @@ -60,7 +60,7 @@ public class M3UDownloader implements ITrackDownloader { VKM3UParser parser = new VKM3UParser(payload); List tses = parser.getTransportStreams(); AtomicInteger progress = new AtomicInteger(0); - List> futures = new ArrayList<>(); + List> futures = new ArrayList<>(tses.size()); var tsesDir = new File(outDir, String.valueOf(track.d)); tsesDir.mkdirs(); diff --git a/app/src/main/java/ru/vtosters/lite/translators/BaseTranslator.java b/app/src/main/java/ru/vtosters/lite/translators/BaseTranslator.java index fde294298..9d089e215 100644 --- a/app/src/main/java/ru/vtosters/lite/translators/BaseTranslator.java +++ b/app/src/main/java/ru/vtosters/lite/translators/BaseTranslator.java @@ -14,7 +14,12 @@ public abstract class BaseTranslator { private static final int YANDEX = 0; private static final int GOOGLE = 1; private static final int DEEPL = 2; - private final LruCache cache = new LruCache<>(200); + private final LruCache cache = new LruCache<>(200) { + @Override + protected String create(@NonNull String text) { + return translate(text, getToLanguage()); + } + }; public static String getToLanguage() { var lang = preferences.getString("lang_target", "system"); @@ -43,11 +48,8 @@ public abstract class BaseTranslator { @NonNull public final String getTranslation(String text) { - var val = cache.get(text); - if (val != null) - return val; - var translation = translate(text, getToLanguage()); - cache.put(text, translation); - return translation; + String v = cache.get(text); + assert v != null; + return v; } } diff --git a/app/src/main/java/ru/vtosters/lite/translators/DeepLTranslator.java b/app/src/main/java/ru/vtosters/lite/translators/DeepLTranslator.java index c93532280..d9c0e2262 100644 --- a/app/src/main/java/ru/vtosters/lite/translators/DeepLTranslator.java +++ b/app/src/main/java/ru/vtosters/lite/translators/DeepLTranslator.java @@ -10,7 +10,6 @@ import org.json.JSONObject; import java.nio.charset.StandardCharsets; import java.util.UUID; import java.util.concurrent.ThreadLocalRandom; -import java.util.concurrent.atomic.AtomicLong; import java.util.regex.Pattern; import okhttp3.MediaType; @@ -49,14 +48,17 @@ public class DeepLTranslator extends BaseTranslator { private static DeepLTranslator instance; public static DeepLTranslator getInstance() { - if (instance == null) - instance = new DeepLTranslator(); - return instance; + // Race safe when the new object does not contain the state of the previous one + DeepLTranslator translator = instance; + if (translator == null) { + return instance = new DeepLTranslator(); + } + return translator; } private static Long getTimestamp(int iNumber) { var now = System.currentTimeMillis(); - return Long.valueOf((((long) iNumber) + now) - (now % ((long) iNumber))); + return (((long) iNumber) + now) - (now % ((long) iNumber)); } @NonNull @@ -73,16 +75,16 @@ public class DeepLTranslator extends BaseTranslator { i++; } - var id = new AtomicLong(ThreadLocalRandom.current().nextLong(Long.parseLong("10000000000"))); + var id = ThreadLocalRandom.current().nextLong(1, 10000000000L); var requestBody = String.format(REQUEST_BODY_FORMAT, - id.incrementAndGet(), + id, text, tl, getTimestamp(i) ); - requestBody = (id.get() + 3) % 13 == 0 || (id.get() + 5) % 29 == 0 + requestBody = (id + 3) % 13 == 0 || (id + 5) % 29 == 0 ? requestBody.replace("hod\":\"", "hod\" : \"") : requestBody.replace("hod\":\"", "hod\": \""); diff --git a/app/src/main/java/ru/vtosters/lite/translators/GoogleTranslator.java b/app/src/main/java/ru/vtosters/lite/translators/GoogleTranslator.java index f0bc87787..fec710ddf 100644 --- a/app/src/main/java/ru/vtosters/lite/translators/GoogleTranslator.java +++ b/app/src/main/java/ru/vtosters/lite/translators/GoogleTranslator.java @@ -38,9 +38,11 @@ public class GoogleTranslator extends BaseTranslator { private static GoogleTranslator instance; public static GoogleTranslator getInstance() { - if (instance == null) - instance = new GoogleTranslator(); - return instance; + // Race safe when the new object does not contain the state of the previous one + GoogleTranslator translator = instance; + if (translator == null) + return instance = new GoogleTranslator(); + return translator; } @NonNull diff --git a/app/src/main/java/ru/vtosters/lite/translators/YandexTranslator.java b/app/src/main/java/ru/vtosters/lite/translators/YandexTranslator.java index b1de3b144..7192727c0 100644 --- a/app/src/main/java/ru/vtosters/lite/translators/YandexTranslator.java +++ b/app/src/main/java/ru/vtosters/lite/translators/YandexTranslator.java @@ -22,9 +22,11 @@ public class YandexTranslator extends BaseTranslator { private static YandexTranslator instance; public static YandexTranslator getInstance() { - if (instance == null) - instance = new YandexTranslator(); - return instance; + // Race safe when the new object does not contain the state of the previous one + YandexTranslator translator = instance; + if (translator == null) + return instance = new YandexTranslator(); + return translator; } @NonNull diff --git a/app/src/main/java/ru/vtosters/lite/ui/dialogs/Translate.java b/app/src/main/java/ru/vtosters/lite/ui/dialogs/Translate.java index e6fc542d5..5260a1760 100644 --- a/app/src/main/java/ru/vtosters/lite/ui/dialogs/Translate.java +++ b/app/src/main/java/ru/vtosters/lite/ui/dialogs/Translate.java @@ -11,21 +11,30 @@ import android.util.Log; import com.vk.core.dialogs.alert.VkAlertDialog; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; + import ru.vtosters.lite.translators.BaseTranslator; import ru.vtosters.lite.utils.AndroidUtils; public class Translate { + private static final Executor ASYNC_EXECUTOR + = Executors.newSingleThreadExecutor(); + private static final Executor MAIN_EXECUTOR + = new DelegateExecutor(); + public static void showTranslatedText(Context context, String text) { - new Thread(() -> { + BaseTranslator translator = BaseTranslator.getInstance(); + if (translator == null) return; + ASYNC_EXECUTOR.execute(() -> { try { - var translation = BaseTranslator.getInstance().getTranslation(text); - new Handler(Looper.getMainLooper()).post(() -> { - showDialog(context, translation); - }); + var translation = translator.getTranslation(text); + MAIN_EXECUTOR.execute(() -> showDialog(context, translation)); } catch (Exception e) { Log.e("Translate", e + ""); } - }).start(); + }); } private static void showDialog(Context context, String text) { @@ -42,4 +51,13 @@ public class Translate { }) .show(); } + + private static class DelegateExecutor implements Executor { + private final Handler handler = new Handler(Looper.getMainLooper()); + + @Override + public void execute(Runnable command) { + handler.post(command); + } + } }