Skip to content

Commit 594f0b1

Browse files
committed
Move TextLinkifier computation out of main thread
1 parent 79e98db commit 594f0b1

4 files changed

Lines changed: 92 additions & 63 deletions

File tree

app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1229,19 +1229,20 @@ private void prepareDescription(final Description description) {
12291229
return;
12301230
}
12311231

1232-
if (description.getType() == Description.HTML) {
1233-
TextLinkifier.createLinksFromHtmlBlock(requireContext(), description.getContent(),
1234-
videoDescriptionView, HtmlCompat.FROM_HTML_MODE_LEGACY);
1235-
videoDescriptionView.setVisibility(View.VISIBLE);
1236-
} else if (description.getType() == Description.MARKDOWN) {
1237-
TextLinkifier.createLinksFromMarkdownText(requireContext(), description.getContent(),
1238-
videoDescriptionView);
1239-
videoDescriptionView.setVisibility(View.VISIBLE);
1240-
} else {
1241-
//== Description.PLAIN_TEXT
1242-
TextLinkifier.createLinksFromPlainText(requireContext(), description.getContent(),
1243-
videoDescriptionView);
1244-
videoDescriptionView.setVisibility(View.VISIBLE);
1232+
switch (description.getType()) {
1233+
case Description.HTML:
1234+
disposables.add(TextLinkifier.createLinksFromHtmlBlock(requireContext(),
1235+
description.getContent(), videoDescriptionView,
1236+
HtmlCompat.FROM_HTML_MODE_LEGACY));
1237+
break;
1238+
case Description.MARKDOWN:
1239+
disposables.add(TextLinkifier.createLinksFromMarkdownText(requireContext(),
1240+
description.getContent(), videoDescriptionView));
1241+
break;
1242+
case Description.PLAIN_TEXT: default:
1243+
disposables.add(TextLinkifier.createLinksFromPlainText(requireContext(),
1244+
description.getContent(), videoDescriptionView));
1245+
break;
12451246
}
12461247
}
12471248

@@ -1557,8 +1558,8 @@ public void handleResult(@NonNull final StreamInfo info) {
15571558
prepareDescription(info.getDescription());
15581559
updateProgressInfo(info);
15591560
initThumbnailViews(info);
1560-
showMetaInfoInTextView(info.getMetaInfo(), detailMetaInfoTextView, detailMetaInfoSeparator);
1561-
1561+
disposables.add(showMetaInfoInTextView(info.getMetaInfo(), detailMetaInfoTextView,
1562+
detailMetaInfoSeparator));
15621563

15631564
if (player == null || player.isStopped()) {
15641565
updateOverlayData(info.getName(), info.getUploaderName(), info.getThumbnailUrl());

app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -280,8 +280,8 @@ public void onResume() {
280280

281281
handleSearchSuggestion();
282282

283-
showMetaInfoInTextView(metaInfo == null ? null : Arrays.asList(metaInfo),
284-
metaInfoTextView, metaInfoSeparator);
283+
disposables.add(showMetaInfoInTextView(metaInfo == null ? null : Arrays.asList(metaInfo),
284+
metaInfoTextView, metaInfoSeparator));
285285

286286
if (suggestionDisposable == null || suggestionDisposable.isDisposed()) {
287287
initSuggestionObserver();
@@ -1002,11 +1002,11 @@ public void handleResult(@NonNull final SearchInfo result) {
10021002
// List<MetaInfo> cannot be bundled without creating some containers
10031003
metaInfo = new MetaInfo[result.getMetaInfo().size()];
10041004
metaInfo = result.getMetaInfo().toArray(metaInfo);
1005+
disposables.add(showMetaInfoInTextView(result.getMetaInfo(), metaInfoTextView,
1006+
metaInfoSeparator));
10051007

10061008
handleSearchSuggestion();
10071009

1008-
showMetaInfoInTextView(result.getMetaInfo(), metaInfoTextView, metaInfoSeparator);
1009-
10101010
lastSearchedString = searchString;
10111011
nextPage = result.getNextPage();
10121012

app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222
import android.content.Context;
2323
import android.content.Intent;
2424
import android.os.Handler;
25-
import android.text.method.LinkMovementMethod;
2625
import android.util.Log;
2726
import android.view.View;
2827
import android.widget.TextView;
@@ -68,6 +67,7 @@
6867

6968
import io.reactivex.rxjava3.core.Maybe;
7069
import io.reactivex.rxjava3.core.Single;
70+
import io.reactivex.rxjava3.disposables.Disposable;
7171

7272
import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
7373

@@ -325,17 +325,19 @@ public static void handleGeneralException(final Context context, final int servi
325325
* @param metaInfos a list of meta information, can be null or empty
326326
* @param metaInfoTextView the text view in which to show the formatted HTML
327327
* @param metaInfoSeparator another view to be shown or hidden accordingly to the text view
328+
* @return a disposable to be stored somewhere and disposed when activity/fragment is destroyed
328329
*/
329-
public static void showMetaInfoInTextView(@Nullable final List<MetaInfo> metaInfos,
330-
final TextView metaInfoTextView,
331-
final View metaInfoSeparator) {
330+
public static Disposable showMetaInfoInTextView(@Nullable final List<MetaInfo> metaInfos,
331+
final TextView metaInfoTextView,
332+
final View metaInfoSeparator) {
332333
final Context context = metaInfoTextView.getContext();
333334
final boolean showMetaInfo = PreferenceManager.getDefaultSharedPreferences(context)
334335
.getBoolean(context.getString(R.string.show_meta_info_key), true);
335336

336337
if (!showMetaInfo || metaInfos == null || metaInfos.isEmpty()) {
337338
metaInfoTextView.setVisibility(View.GONE);
338339
metaInfoSeparator.setVisibility(View.GONE);
340+
return Disposable.empty();
339341

340342
} else {
341343
final StringBuilder stringBuilder = new StringBuilder();
@@ -365,11 +367,9 @@ public static void showMetaInfoInTextView(@Nullable final List<MetaInfo> metaInf
365367
}
366368
}
367369

368-
TextLinkifier.createLinksFromHtmlBlock(context, stringBuilder.toString(),
369-
metaInfoTextView, HtmlCompat.FROM_HTML_SEPARATOR_LINE_BREAK_HEADING);
370-
metaInfoTextView.setMovementMethod(LinkMovementMethod.getInstance());
371-
metaInfoTextView.setVisibility(View.VISIBLE);
372370
metaInfoSeparator.setVisibility(View.VISIBLE);
371+
return TextLinkifier.createLinksFromHtmlBlock(context, stringBuilder.toString(),
372+
metaInfoTextView, HtmlCompat.FROM_HTML_SEPARATOR_LINE_BREAK_HEADING);
373373
}
374374
}
375375

app/src/main/java/org/schabi/newpipe/util/TextLinkifier.java

Lines changed: 64 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,23 @@
66
import android.text.style.ClickableSpan;
77
import android.text.style.URLSpan;
88
import android.text.util.Linkify;
9+
import android.util.Log;
910
import android.view.View;
1011
import android.widget.TextView;
1112

13+
import androidx.annotation.NonNull;
1214
import androidx.core.text.HtmlCompat;
1315

1416
import io.noties.markwon.Markwon;
1517
import io.noties.markwon.linkify.LinkifyPlugin;
18+
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
19+
import io.reactivex.rxjava3.core.Single;
20+
import io.reactivex.rxjava3.disposables.Disposable;
21+
import io.reactivex.rxjava3.schedulers.Schedulers;
1622

1723
public final class TextLinkifier {
24+
public static final String TAG = TextLinkifier.class.getSimpleName();
25+
1826
private TextLinkifier() {
1927
}
2028

@@ -23,40 +31,42 @@ private TextLinkifier() {
2331
* <p>
2432
* This will call
2533
* {@link TextLinkifier#changeIntentsOfDescriptionLinks(Context, CharSequence, TextView)}
26-
* after linked the URLs with {@link HtmlCompat#fromHtml(String, int)}.
34+
* after having linked the URLs with {@link HtmlCompat#fromHtml(String, int)}.
2735
*
2836
* @param context the context to use
2937
* @param htmlBlock the htmlBlock to be linked
3038
* @param textView the TextView to set the htmlBlock linked
3139
* @param htmlCompatFlag the int flag to be set when {@link HtmlCompat#fromHtml(String, int)}
3240
* will be called
41+
* @return a disposable to be stored somewhere and disposed when activity/fragment is destroyed
3342
*/
34-
public static void createLinksFromHtmlBlock(final Context context,
35-
final String htmlBlock,
36-
final TextView textView,
37-
final int htmlCompatFlag) {
38-
changeIntentsOfDescriptionLinks(context, HtmlCompat.fromHtml(htmlBlock, htmlCompatFlag),
39-
textView);
43+
public static Disposable createLinksFromHtmlBlock(final Context context,
44+
final String htmlBlock,
45+
final TextView textView,
46+
final int htmlCompatFlag) {
47+
return changeIntentsOfDescriptionLinks(context,
48+
HtmlCompat.fromHtml(htmlBlock, htmlCompatFlag), textView);
4049
}
4150

4251
/**
4352
* Create web links for contents with a plain text description.
4453
* <p>
4554
* This will call
4655
* {@link TextLinkifier#changeIntentsOfDescriptionLinks(Context, CharSequence, TextView)}
47-
* after linked the URLs with {@link TextView#setAutoLinkMask(int)} and
56+
* after having linked the URLs with {@link TextView#setAutoLinkMask(int)} and
4857
* {@link TextView#setText(CharSequence, TextView.BufferType)}.
4958
*
5059
* @param context the context to use
5160
* @param plainTextBlock the block of plain text to be linked
5261
* @param textView the TextView to set the plain text block linked
62+
* @return a disposable to be stored somewhere and disposed when activity/fragment is destroyed
5363
*/
54-
public static void createLinksFromPlainText(final Context context,
55-
final String plainTextBlock,
56-
final TextView textView) {
64+
public static Disposable createLinksFromPlainText(final Context context,
65+
final String plainTextBlock,
66+
final TextView textView) {
5767
textView.setAutoLinkMask(Linkify.WEB_URLS);
5868
textView.setText(plainTextBlock, TextView.BufferType.SPANNABLE);
59-
changeIntentsOfDescriptionLinks(context, textView.getText(), textView);
69+
return changeIntentsOfDescriptionLinks(context, textView.getText(), textView);
6070
}
6171

6272
/**
@@ -70,48 +80,66 @@ public static void createLinksFromPlainText(final Context context,
7080
* @param context the context to use
7181
* @param markdownBlock the block of markdown text to be linked
7282
* @param textView the TextView to set the plain text block linked
83+
* @return a disposable to be stored somewhere and disposed when activity/fragment is destroyed
7384
*/
74-
public static void createLinksFromMarkdownText(final Context context,
75-
final String markdownBlock,
76-
final TextView textView) {
85+
public static Disposable createLinksFromMarkdownText(final Context context,
86+
final String markdownBlock,
87+
final TextView textView) {
7788
final Markwon markwon = Markwon.builder(context).usePlugin(LinkifyPlugin.create()).build();
7889
markwon.setMarkdown(textView, markdownBlock);
79-
changeIntentsOfDescriptionLinks(context, textView.getText(), textView);
90+
return changeIntentsOfDescriptionLinks(context, textView.getText(), textView);
8091
}
8192

8293
/**
8394
* Change links generated by libraries in the description of a content to a custom link action.
8495
* <p>
85-
* Instead of using an ACTION_VIEW intent in the description of a content, this method will
86-
* parse the CharSequence and replace all current web links with
87-
* {@link ShareUtils#openUrlInBrowser(Context, String, boolean)}.
96+
* Instead of using an {@link android.content.Intent#ACTION_VIEW} intent in the description of a
97+
* content, this method will parse the {@link CharSequence} and replace all current web links
98+
* with {@link ShareUtils#openUrlInBrowser(Context, String, boolean)}.
8899
* <p>
89-
* This method is required in order to intercept links and maybe, show a confirmation dialog
100+
* This method is required in order to intercept links and e.g. show a confirmation dialog
90101
* before opening a web link.
91102
*
92103
* @param context the context to use
93104
* @param chars the CharSequence to be parsed
94105
* @param textView the TextView in which the converted CharSequence will be applied
106+
* @return a disposable to be stored somewhere and disposed when activity/fragment is destroyed
95107
*/
96-
private static void changeIntentsOfDescriptionLinks(final Context context,
97-
final CharSequence chars,
98-
final TextView textView) {
99-
final SpannableStringBuilder textBlockLinked = new SpannableStringBuilder(chars);
100-
final URLSpan[] urls = textBlockLinked.getSpans(0, chars.length(), URLSpan.class);
108+
private static Disposable changeIntentsOfDescriptionLinks(final Context context,
109+
final CharSequence chars,
110+
final TextView textView) {
111+
return Single.fromCallable(() -> {
112+
final SpannableStringBuilder textBlockLinked = new SpannableStringBuilder(chars);
113+
final URLSpan[] urls = textBlockLinked.getSpans(0, chars.length(), URLSpan.class);
114+
115+
for (final URLSpan span : urls) {
116+
final ClickableSpan clickableSpan = new ClickableSpan() {
117+
public void onClick(@NonNull final View view) {
118+
ShareUtils.openUrlInBrowser(context, span.getURL(), false);
119+
}
120+
};
101121

102-
for (final URLSpan span : urls) {
103-
final ClickableSpan clickableSpan = new ClickableSpan() {
104-
public void onClick(final View view) {
105-
ShareUtils.openUrlInBrowser(context, span.getURL(), false);
106-
}
107-
};
122+
textBlockLinked.setSpan(clickableSpan, textBlockLinked.getSpanStart(span),
123+
textBlockLinked.getSpanEnd(span), textBlockLinked.getSpanFlags(span));
124+
textBlockLinked.removeSpan(span);
125+
}
108126

109-
textBlockLinked.setSpan(clickableSpan, textBlockLinked.getSpanStart(span),
110-
textBlockLinked.getSpanEnd(span), textBlockLinked.getSpanFlags(span));
111-
textBlockLinked.removeSpan(span);
112-
}
127+
return textBlockLinked;
128+
}).subscribeOn(Schedulers.computation())
129+
.observeOn(AndroidSchedulers.mainThread())
130+
.subscribe(
131+
textBlockLinked -> setTextViewCharSequence(textView, textBlockLinked),
132+
throwable -> {
133+
Log.e(TAG, "Unable to linkify text", throwable);
134+
// this should never happen, but if it does, just fallback to it
135+
setTextViewCharSequence(textView, chars);
136+
});
137+
}
113138

114-
textView.setText(textBlockLinked);
139+
private static void setTextViewCharSequence(final TextView textView,
140+
final CharSequence charSequence) {
141+
textView.setText(charSequence);
115142
textView.setMovementMethod(LinkMovementMethod.getInstance());
143+
textView.setVisibility(View.VISIBLE);
116144
}
117145
}

0 commit comments

Comments
 (0)