Fix some FastTextView cache key issues

multisite
Floens 10 years ago
parent 2b13269a07
commit a3b0a1ff52
  1. 34
      Clover/app/src/main/java/org/floens/chan/chan/ChanParser.java
  2. 16
      Clover/app/src/main/java/org/floens/chan/ui/cell/PostCell.java
  3. 39
      Clover/app/src/main/java/org/floens/chan/ui/span/AbsoluteSizeSpanHashed.java
  4. 33
      Clover/app/src/main/java/org/floens/chan/ui/span/ForegroundColorSpanHashed.java
  5. 65
      Clover/app/src/main/java/org/floens/chan/ui/text/FastTextView.java

@ -21,9 +21,7 @@ package org.floens.chan.chan;
import android.graphics.Typeface;
import android.text.SpannableString;
import android.text.TextUtils;
import android.text.style.AbsoluteSizeSpan;
import android.text.style.BackgroundColorSpan;
import android.text.style.ForegroundColorSpan;
import android.text.style.StrikethroughSpan;
import android.text.style.StyleSpan;
import android.text.style.TypefaceSpan;
@ -34,6 +32,8 @@ import org.floens.chan.core.database.DatabaseManager;
import org.floens.chan.core.model.Post;
import org.floens.chan.core.model.PostLinkable;
import org.floens.chan.core.settings.ChanSettings;
import org.floens.chan.ui.span.AbsoluteSizeSpanHashed;
import org.floens.chan.ui.span.ForegroundColorSpanHashed;
import org.floens.chan.ui.theme.Theme;
import org.floens.chan.ui.theme.ThemeHelper;
import org.floens.chan.utils.Logger;
@ -129,19 +129,19 @@ public class ChanParser {
post.subjectSpan = new SpannableString(post.subject);
// Do not set another color when the post is in stub mode, it sets text_color_secondary
if (!post.filterStub) {
post.subjectSpan.setSpan(new ForegroundColorSpan(theme.subjectColor), 0, post.subjectSpan.length(), 0);
post.subjectSpan.setSpan(new ForegroundColorSpanHashed(theme.subjectColor), 0, post.subjectSpan.length(), 0);
}
}
if (!TextUtils.isEmpty(post.name) && !post.name.equals("Anonymous")) {
post.nameSpan = new SpannableString(post.name);
post.nameSpan.setSpan(new ForegroundColorSpan(theme.nameColor), 0, post.nameSpan.length(), 0);
post.nameSpan.setSpan(new ForegroundColorSpanHashed(theme.nameColor), 0, post.nameSpan.length(), 0);
}
if (!TextUtils.isEmpty(post.tripcode)) {
post.tripcodeSpan = new SpannableString(post.tripcode);
post.tripcodeSpan.setSpan(new ForegroundColorSpan(theme.nameColor), 0, post.tripcodeSpan.length(), 0);
post.tripcodeSpan.setSpan(new AbsoluteSizeSpan(detailsSizePx), 0, post.tripcodeSpan.length(), 0);
post.tripcodeSpan.setSpan(new ForegroundColorSpanHashed(theme.nameColor), 0, post.tripcodeSpan.length(), 0);
post.tripcodeSpan.setSpan(new AbsoluteSizeSpanHashed(detailsSizePx), 0, post.tripcodeSpan.length(), 0);
}
if (!TextUtils.isEmpty(post.id)) {
@ -158,15 +158,15 @@ public class ChanParser {
boolean lightColor = (r * 0.299f) + (g * 0.587f) + (b * 0.114f) > 125f;
int idBgColor = lightColor ? theme.idBackgroundLight : theme.idBackgroundDark;
post.idSpan.setSpan(new ForegroundColorSpan(idColor), 0, post.idSpan.length(), 0);
post.idSpan.setSpan(new ForegroundColorSpanHashed(idColor), 0, post.idSpan.length(), 0);
post.idSpan.setSpan(new BackgroundColorSpan(idBgColor), 0, post.idSpan.length(), 0);
post.idSpan.setSpan(new AbsoluteSizeSpan(detailsSizePx), 0, post.idSpan.length(), 0);
post.idSpan.setSpan(new AbsoluteSizeSpanHashed(detailsSizePx), 0, post.idSpan.length(), 0);
}
if (!TextUtils.isEmpty(post.capcode)) {
post.capcodeSpan = new SpannableString("Capcode: " + post.capcode);
post.capcodeSpan.setSpan(new ForegroundColorSpan(theme.capcodeColor), 0, post.capcodeSpan.length(), 0);
post.capcodeSpan.setSpan(new AbsoluteSizeSpan(detailsSizePx), 0, post.capcodeSpan.length(), 0);
post.capcodeSpan.setSpan(new ForegroundColorSpanHashed(theme.capcodeColor), 0, post.capcodeSpan.length(), 0);
post.capcodeSpan.setSpan(new AbsoluteSizeSpanHashed(detailsSizePx), 0, post.capcodeSpan.length(), 0);
}
post.nameTripcodeIdCapcodeSpan = new SpannableString("");
@ -234,7 +234,7 @@ public class ChanParser {
Set<String> classes = span.classNames();
if (classes.contains("deadlink")) {
quote = new SpannableString(span.text());
quote.setSpan(new ForegroundColorSpan(theme.quoteColor), 0, quote.length(), 0);
quote.setSpan(new ForegroundColorSpanHashed(theme.quoteColor), 0, quote.length(), 0);
quote.setSpan(new StrikethroughSpan(), 0, quote.length(), 0);
} else if (classes.contains("fortune")) {
// html looks like <span class="fortune" style="color:#0893e1"><br><br><b>Your fortune:</b>
@ -260,7 +260,7 @@ public class ChanParser {
}
if (hexColor >= 0 && hexColor <= 0xffffff) {
quote.setSpan(new ForegroundColorSpan(0xff000000 + hexColor), 0, quote.length(), 0);
quote.setSpan(new ForegroundColorSpanHashed(0xff000000 + hexColor), 0, quote.length(), 0);
quote.setSpan(new StyleSpan(Typeface.BOLD), 0, quote.length(), 0);
}
}
@ -268,7 +268,7 @@ public class ChanParser {
return null;
} else {
quote = new SpannableString(span.text());
quote.setSpan(new ForegroundColorSpan(theme.inlineQuoteColor), 0, quote.length(), 0);
quote.setSpan(new ForegroundColorSpanHashed(theme.inlineQuoteColor), 0, quote.length(), 0);
detectLinks(theme, post, span.text(), quote);
}
@ -306,8 +306,8 @@ public class ChanParser {
}
SpannableString tableTotal = new SpannableString(TextUtils.concat(parts.toArray(new CharSequence[parts.size()])));
tableTotal.setSpan(new ForegroundColorSpan(theme.inlineQuoteColor), 0, tableTotal.length(), 0);
tableTotal.setSpan(new AbsoluteSizeSpan(sp(12f)), 0, tableTotal.length(), 0);
tableTotal.setSpan(new ForegroundColorSpanHashed(theme.inlineQuoteColor), 0, tableTotal.length(), 0);
tableTotal.setSpan(new AbsoluteSizeSpanHashed(sp(12f)), 0, tableTotal.length(), 0);
return tableTotal;
}
@ -315,7 +315,7 @@ public class ChanParser {
Element strong = (Element) node;
SpannableString red = new SpannableString(strong.text());
red.setSpan(new ForegroundColorSpan(theme.quoteColor), 0, red.length(), 0);
red.setSpan(new ForegroundColorSpanHashed(theme.quoteColor), 0, red.length(), 0);
red.setSpan(new StyleSpan(Typeface.BOLD), 0, red.length(), 0);
return red;
@ -347,7 +347,7 @@ public class ChanParser {
String text = getNodeText(pre);
SpannableString monospace = new SpannableString(text);
monospace.setSpan(new TypefaceSpan("monospace"), 0, monospace.length(), 0);
monospace.setSpan(new AbsoluteSizeSpan(sp(12f)), 0, monospace.length(), 0);
monospace.setSpan(new AbsoluteSizeSpanHashed(sp(12f)), 0, monospace.length(), 0);
return monospace;
} else {
return pre.text();

@ -37,10 +37,8 @@ import android.text.TextPaint;
import android.text.TextUtils;
import android.text.format.DateUtils;
import android.text.method.LinkMovementMethod;
import android.text.style.AbsoluteSizeSpan;
import android.text.style.BackgroundColorSpan;
import android.text.style.ClickableSpan;
import android.text.style.ForegroundColorSpan;
import android.text.style.UnderlineSpan;
import android.util.AttributeSet;
import android.view.MotionEvent;
@ -59,6 +57,8 @@ import org.floens.chan.core.model.Post;
import org.floens.chan.core.model.PostImage;
import org.floens.chan.core.model.PostLinkable;
import org.floens.chan.core.settings.ChanSettings;
import org.floens.chan.ui.span.AbsoluteSizeSpanHashed;
import org.floens.chan.ui.span.ForegroundColorSpanHashed;
import org.floens.chan.ui.text.FastTextView;
import org.floens.chan.ui.text.FastTextViewMovementMethod;
import org.floens.chan.ui.theme.Theme;
@ -355,8 +355,8 @@ public class PostCell extends LinearLayout implements PostCellInterface, PostLin
String noText = "No." + post.no;
SpannableString date = new SpannableString(noText + " " + time);
date.setSpan(new ForegroundColorSpan(theme.detailsColor), 0, date.length(), 0);
date.setSpan(new AbsoluteSizeSpan(detailsSizePx), 0, date.length(), 0);
date.setSpan(new ForegroundColorSpanHashed(theme.detailsColor), 0, date.length(), 0);
date.setSpan(new AbsoluteSizeSpanHashed(detailsSizePx), 0, date.length(), 0);
boolean noClickable = ChanSettings.tapNoReply.get();
if (noClickable) {
@ -372,8 +372,8 @@ public class PostCell extends LinearLayout implements PostCellInterface, PostLin
if (postFileName) {
String filename = image.spoiler ? getString(R.string.image_spoiler_filename) : image.filename + "." + image.extension;
SpannableString fileInfo = new SpannableString("\n" + filename);
fileInfo.setSpan(new ForegroundColorSpan(theme.detailsColor), 0, fileInfo.length(), 0);
fileInfo.setSpan(new AbsoluteSizeSpan(detailsSizePx), 0, fileInfo.length(), 0);
fileInfo.setSpan(new ForegroundColorSpanHashed(theme.detailsColor), 0, fileInfo.length(), 0);
fileInfo.setSpan(new AbsoluteSizeSpanHashed(detailsSizePx), 0, fileInfo.length(), 0);
fileInfo.setSpan(new UnderlineSpan(), 0, fileInfo.length(), 0);
titleParts.add(fileInfo);
}
@ -382,8 +382,8 @@ public class PostCell extends LinearLayout implements PostCellInterface, PostLin
SpannableString fileInfo = new SpannableString((postFileName ? " " : "\n") + image.extension.toUpperCase() + " " +
AndroidUtils.getReadableFileSize(image.size, false) + " " +
image.imageWidth + "x" + image.imageHeight);
fileInfo.setSpan(new ForegroundColorSpan(theme.detailsColor), 0, fileInfo.length(), 0);
fileInfo.setSpan(new AbsoluteSizeSpan(detailsSizePx), 0, fileInfo.length(), 0);
fileInfo.setSpan(new ForegroundColorSpanHashed(theme.detailsColor), 0, fileInfo.length(), 0);
fileInfo.setSpan(new AbsoluteSizeSpanHashed(detailsSizePx), 0, fileInfo.length(), 0);
titleParts.add(fileInfo);
}
}

@ -0,0 +1,39 @@
package org.floens.chan.ui.span;
import android.os.Parcel;
import android.text.style.AbsoluteSizeSpan;
/**
* A version of AbsoluteSizeSpan that has proper equals and hashCode implementations. Used to fix the hashcode result from SpannableStringBuilder.
*/
public class AbsoluteSizeSpanHashed extends AbsoluteSizeSpan {
public AbsoluteSizeSpanHashed(int size) {
super(size);
}
public AbsoluteSizeSpanHashed(int size, boolean dip) {
super(size, dip);
}
public AbsoluteSizeSpanHashed(Parcel src) {
super(src);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
AbsoluteSizeSpanHashed that = (AbsoluteSizeSpanHashed) o;
if (getSize() != that.getSize()) return false;
return getDip() == that.getDip();
}
@Override
public int hashCode() {
int result = getSize();
result = 31 * result + (getDip() ? 1 : 0);
return result;
}
}

@ -0,0 +1,33 @@
package org.floens.chan.ui.span;
import android.os.Parcel;
import android.text.style.ForegroundColorSpan;
/**
* A version of ForegroundColorSpan that has proper equals and hashCode implementations. Used to fix the hashcode result from SpannableStringBuilder.
*/
public class ForegroundColorSpanHashed extends ForegroundColorSpan {
public ForegroundColorSpanHashed(int color) {
super(color);
}
public ForegroundColorSpanHashed(Parcel src) {
super(src);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ForegroundColorSpanHashed that = (ForegroundColorSpanHashed) o;
return getForegroundColor() == that.getForegroundColor();
}
@Override
public int hashCode() {
return getForegroundColor();
}
}

@ -42,7 +42,7 @@ import static org.floens.chan.utils.AndroidUtils.sp;
*/
public class FastTextView extends View {
private static final String TAG = "FastTextView";
private static LruCache<Long, StaticLayout> textCache = new LruCache<>(250);
private static LruCache<FastTextViewItem, StaticLayout> textCache = new LruCache<>(250);
private TextPaint paint;
private boolean singleLine;
@ -215,31 +215,16 @@ public class FastTextView extends View {
// long start = Time.startTiming();
// The StaticLayouts are cached with the static textCache LRU map
FastTextViewItem item = new FastTextViewItem(text, paint, layoutWidth);
// Use .toString() to make sure we take the hashcode from the string representation
// and not from any spannables that are in it
long cacheKey = text.toString().hashCode();
cacheKey = 31 * cacheKey + paint.getColor();
cacheKey = 31 * cacheKey + Float.floatToIntBits(paint.getTextSize());
cacheKey = 31 * cacheKey + layoutWidth;
StaticLayout cached = textCache.get(cacheKey);
StaticLayout cached = textCache.get(item);
if (cached == null) {
// Logger.test("staticlayout cache miss: text = %s", text);
cached = getStaticLayout(layoutWidth);
textCache.put(cacheKey, cached);
} else {
// Logger.test("staticlayout cache hit");
// Make sure the layout has the actual text, color, size and width, hashcodes almost never collide
Paint cachedPaint = cached.getPaint();
if (!text.toString().equals(cached.getText().toString()) ||
cachedPaint.getColor() != paint.getColor() ||
cachedPaint.getTextSize() != paint.getTextSize() ||
cached.getWidth() != layoutWidth) {
Logger.w(TAG, "Cache miss with the same hashcode %x: \"%s\" \"%s\"!", cacheKey, text.toString(), cached.getText().toString());
cached = getStaticLayout(layoutWidth);
}
}
textCache.put(item, cached);
}/* else {
Logger.test("staticlayout cache hit");
}*/
layout = cached;
// Time.endTiming(Integer.toHexString(System.identityHashCode(this)) + " staticlayout for width = " + layoutWidth + "\t", start);
@ -257,4 +242,40 @@ public class FastTextView extends View {
// Logger.test("new staticlayout width=%d", layoutWidth);
return new StaticLayout(text, paint, layoutWidth, Layout.Alignment.ALIGN_NORMAL, 1f, 0f, false);
}
private static class FastTextViewItem {
private CharSequence text;
private int color;
private float textSize;
private int layoutWidth;
public FastTextViewItem(CharSequence text, TextPaint textPaint, int layoutWidth) {
this.text = text;
color = textPaint.getColor();
textSize = textPaint.getTextSize();
this.layoutWidth = layoutWidth;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
FastTextViewItem that = (FastTextViewItem) o;
if (color != that.color) return false;
if (Float.compare(that.textSize, textSize) != 0) return false;
if (layoutWidth != that.layoutWidth) return false;
return text.equals(that.text);
}
@Override
public int hashCode() {
int result = text.hashCode();
result = 31 * result + color;
result = 31 * result + (textSize != +0.0f ? Float.floatToIntBits(textSize) : 0);
result = 31 * result + layoutWidth;
return result;
}
}
}

Loading…
Cancel
Save