mirror of
https://github.com/keymanapp/keyman.git
synced 2026-09-24 08:37:42 +00:00
Partially addressing performance issues
This commit is contained in:
parent
2843672e07
commit
2ebd3dbaa2
9 changed files with 165 additions and 77 deletions
|
|
@ -22,7 +22,7 @@ import com.tavultesoft.kmea.data.Dataset;
|
|||
import com.tavultesoft.kmea.data.Keyboard;
|
||||
import com.tavultesoft.kmea.data.adapters.NestedAdapter;
|
||||
|
||||
final class KMKeyboardPickerAdapter extends NestedAdapter<Keyboard, Dataset.Keyboards> implements OnClickListener {
|
||||
final class KMKeyboardPickerAdapter extends NestedAdapter<Keyboard, Dataset.Keyboards, Void> implements OnClickListener {
|
||||
private final static int KEYBOARD_LAYOUT_RESOURCE = R.layout.list_row_layout3;
|
||||
|
||||
private Context context;
|
||||
|
|
|
|||
|
|
@ -202,15 +202,15 @@ public final class ModelPickerActivity extends AppCompatActivity {
|
|||
|
||||
// Uses the repo dataset's master lexical model list to create a filtered adapter for use here.
|
||||
// As this one is specific to this class, we can implement Activity-specific functionality within it.
|
||||
static private class FilteredLexicalModelAdapter extends NestedAdapter<LexicalModel, Dataset.LexicalModels> {
|
||||
static private class FilteredLexicalModelAdapter extends NestedAdapter<LexicalModel, Dataset.LexicalModels, Void> {
|
||||
static final int RESOURCE = R.layout.models_list_row_layout;
|
||||
private final Context context;
|
||||
|
||||
public FilteredLexicalModelAdapter(@NonNull Context context, Dataset.LexicalModels lexicalModels, final String languageCode) {
|
||||
// Goal: to not need a custom filter here, instead relying on LanguageDataset's built-in filters.
|
||||
super(context, RESOURCE, lexicalModels, new AdapterFilter<LexicalModel, Dataset.LexicalModels>() {
|
||||
super(context, RESOURCE, lexicalModels, new AdapterFilter<LexicalModel, Dataset.LexicalModels, Void>() {
|
||||
@Override
|
||||
public List<LexicalModel> selectFrom(Dataset.LexicalModels adapter) {
|
||||
public List<LexicalModel> selectFrom(Dataset.LexicalModels adapter, Void dummy) {
|
||||
List<LexicalModel> list = new ArrayList<>();
|
||||
|
||||
// Highly unoptimized version: O(n^2).
|
||||
|
|
@ -222,7 +222,7 @@ public final class ModelPickerActivity extends AppCompatActivity {
|
|||
|
||||
return list;
|
||||
}
|
||||
});
|
||||
}, null);
|
||||
|
||||
this.context = context;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -262,9 +262,10 @@ public class CloudRepository {
|
|||
String kbVersion = keyboardJSON.optString(KMManager.KMKey_KeyboardVersion, "1.0");
|
||||
String kbFont = keyboardJSON.optString(KMManager.KMKey_Font, "");
|
||||
|
||||
String kbKey = String.format("%s_%s", langID, kbID);
|
||||
//String kbKey = String.format("%s_%s", langID, kbID);
|
||||
HashMap<String, String> hashMap = new HashMap<String, String>();
|
||||
hashMap.put(KMManager.KMKey_KeyboardName, kbName);
|
||||
hashMap.put(KMManager.KMKey_KeyboardID, kbID);
|
||||
hashMap.put(KMManager.KMKey_LanguageName, langName);
|
||||
hashMap.put(KMManager.KMKey_LanguageID, langID);
|
||||
hashMap.put(KMManager.KMKey_KeyboardVersion, kbVersion);
|
||||
|
|
|
|||
|
|
@ -12,38 +12,41 @@ import com.tavultesoft.kmea.data.adapters.ListBacked;
|
|||
import com.tavultesoft.kmea.data.adapters.NestedAdapter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
public class Dataset extends ArrayAdapter<LanguageDataset> {
|
||||
public class LanguageFilter<Type extends LanguageCoded, Adapter extends LanguageCodedAdapter<Type>> implements AdapterFilter<Type, Adapter> {
|
||||
private final String lgCode;
|
||||
|
||||
LanguageFilter(String lgCode) {
|
||||
this.lgCode = lgCode;
|
||||
}
|
||||
public abstract class LanguageFilter<Type extends LanguageCoded, Adapter extends LanguageCodedAdapter<Type>> implements AdapterFilter<Type, Adapter, String> {
|
||||
abstract Set<Type> getSetFrom(LanguageMetadata metadata);
|
||||
|
||||
@Override
|
||||
public List<Type> selectFrom(Adapter adapter) {
|
||||
public List<Type> selectFrom(Adapter adapter, String lgCode) {
|
||||
// Since we know that the adapter is connected to the master Dataset, we can pre-filter and optimize
|
||||
// performance of our linked adapters.
|
||||
LanguageMetadata metadata = Dataset.this.languageMetadata.get(lgCode);
|
||||
|
||||
List<Type> list = new ArrayList<>();
|
||||
|
||||
// Highly unoptimized version: O(n^2).
|
||||
for(Type elem: adapter.asList()) {
|
||||
if(elem.getLanguageCode().equals(lgCode)) {
|
||||
list.add(elem);
|
||||
}
|
||||
}
|
||||
|
||||
return list;
|
||||
return new ArrayList<>(getSetFrom(metadata));
|
||||
}
|
||||
}
|
||||
|
||||
private final LanguageFilter<Keyboard, Keyboards> keyboardFilter = new LanguageFilter<Keyboard, Keyboards>() {
|
||||
Set<Keyboard> getSetFrom(LanguageMetadata metadata) {
|
||||
return metadata.keyboards;
|
||||
}
|
||||
};
|
||||
|
||||
private final LanguageFilter<LexicalModel, LexicalModels> lexicalModelFilter = new LanguageFilter<LexicalModel, LexicalModels>() {
|
||||
Set<LexicalModel> getSetFrom(LanguageMetadata metadata) {
|
||||
return metadata.lexicalModels;
|
||||
}
|
||||
};
|
||||
|
||||
// Implements common language-tracking functionality for each internally-managed master list.
|
||||
private class LanguageCodedAdapter<Type extends LanguageCoded> extends ArrayAdapter<Type> implements ListBacked<Type> {
|
||||
private final List<Type> data;
|
||||
|
|
@ -99,20 +102,7 @@ public class Dataset extends ArrayAdapter<LanguageDataset> {
|
|||
|
||||
@Override
|
||||
public void addAll(Type... items) {
|
||||
boolean notify = doNotify; // Save initial value.
|
||||
if(notify) {
|
||||
Dataset.this.setNotifyOnChange(false);
|
||||
}
|
||||
|
||||
super.addAll(items);
|
||||
|
||||
for(Type kbd: items) {
|
||||
ensureLanguageDatasetExists(kbd);
|
||||
}
|
||||
|
||||
if(notify) {
|
||||
Dataset.this.notifyDataSetChanged();
|
||||
}
|
||||
this.addAll(Arrays.asList(items));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -124,8 +114,30 @@ public class Dataset extends ArrayAdapter<LanguageDataset> {
|
|||
|
||||
super.addAll(collection);
|
||||
|
||||
for(Type kbd: collection) {
|
||||
ensureLanguageDatasetExists(kbd);
|
||||
HashSet<String> mutatedLanguages = new HashSet<>();
|
||||
|
||||
for(Type item: collection) {
|
||||
LanguageMetadata data = getMetadataFor(item);
|
||||
|
||||
if(item instanceof Keyboard) {
|
||||
if(!data.keyboards.contains(item)) {
|
||||
data.keyboards.add((Keyboard) item);
|
||||
}
|
||||
} else if(item instanceof LexicalModel) {
|
||||
if(!data.lexicalModels.contains(item)) {
|
||||
data.lexicalModels.add((LexicalModel) item);
|
||||
}
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
mutatedLanguages.add(data.code);
|
||||
}
|
||||
|
||||
// Ensures that the LanguageDataset is only constructed after we've already analyzed the keyboards
|
||||
// and determined the language sets.
|
||||
for(String lgCode: mutatedLanguages) {
|
||||
ensureLanguageDatasetExists(Dataset.this.languageMetadata.get(lgCode).name, lgCode);
|
||||
}
|
||||
|
||||
if(notify) {
|
||||
|
|
@ -187,6 +199,10 @@ public class Dataset extends ArrayAdapter<LanguageDataset> {
|
|||
super.notifyDataSetChanged();
|
||||
}
|
||||
|
||||
void _notifyDataSetChanged() {
|
||||
super.notifyDataSetChanged();
|
||||
}
|
||||
|
||||
// Override other class methods (following 'Decorator' pattern) to facilitate data tracking as needed.
|
||||
}
|
||||
|
||||
|
|
@ -202,13 +218,31 @@ public class Dataset extends ArrayAdapter<LanguageDataset> {
|
|||
}
|
||||
}
|
||||
|
||||
class LanguageMetadata {
|
||||
public final String name;
|
||||
public final String code;
|
||||
public Set<Keyboard> keyboards = new HashSet<>();
|
||||
public Set<LexicalModel> lexicalModels = new HashSet<>();
|
||||
|
||||
public LanguageMetadata(String name, String code) {
|
||||
this.name = name;
|
||||
this.code = code;
|
||||
}
|
||||
}
|
||||
|
||||
// Stores internal adapters for keyboard, lexical model listings
|
||||
public final Keyboards keyboards;
|
||||
public final LexicalModels lexicalModels;
|
||||
|
||||
private Context context;
|
||||
|
||||
// Maps the relevant adapters for UI usage.
|
||||
private final Map<String, LanguageDataset> languageIndexMap = new HashMap<>();
|
||||
|
||||
// Tracks keyboards and model by language tag separately from the adapters; useful for
|
||||
// optimizing Adapter functionality.
|
||||
private final Map<String, LanguageMetadata> languageMetadata = new HashMap<>();
|
||||
|
||||
// 'Items' are language-specific datasets holding their own language-specific adapters.
|
||||
|
||||
public Dataset(@NonNull Context context) {
|
||||
|
|
@ -223,37 +257,52 @@ public class Dataset extends ArrayAdapter<LanguageDataset> {
|
|||
String lgCode = coded.getLanguageCode();
|
||||
LanguageDataset lgData = languageIndexMap.get(lgCode);
|
||||
|
||||
if(lgData.keyboards.getCount() == 0 && lgData.lexicalModels.getCount() == 0) {
|
||||
// We're no longer storing anything for this language - remove lgData from tracking.
|
||||
this.remove(lgData);
|
||||
languageIndexMap.remove(lgCode);
|
||||
}
|
||||
// if(lgData.keyboards.getCount() == 0 && lgData.lexicalModels.getCount() == 0) {
|
||||
// // We're no longer storing anything for this language - remove lgData from tracking.
|
||||
// this.remove(lgData);
|
||||
// languageIndexMap.remove(lgCode);
|
||||
// }
|
||||
}
|
||||
|
||||
protected void ensureLanguageDatasetExists(LanguageCoded coded) {
|
||||
String lgCode = coded.getLanguageCode();
|
||||
protected LanguageMetadata getMetadataFor(LanguageCoded coded) {
|
||||
final String lgCode = coded.getLanguageCode();
|
||||
LanguageMetadata data = Dataset.this.languageMetadata.get(lgCode);
|
||||
|
||||
if(data == null) {
|
||||
data = new LanguageMetadata(coded.getLanguageName(), coded.getLanguageCode());
|
||||
Dataset.this.languageMetadata.put(lgCode, data);
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
protected void ensureLanguageDatasetExists(String lgName, String lgCode) {
|
||||
LanguageDataset lgData = languageIndexMap.get(lgCode);
|
||||
|
||||
if(lgData == null) {
|
||||
lgData = constructLanguageDataset(coded.getLanguageName(), lgCode);
|
||||
lgData = constructLanguageDataset(lgName, lgCode);
|
||||
languageIndexMap.put(lgCode, lgData);
|
||||
Dataset.this.add(lgData);
|
||||
}
|
||||
}
|
||||
|
||||
protected LanguageDataset constructLanguageDataset(String languageName, String languageCode) {
|
||||
// Construct nested adapters for the individual language datasets.
|
||||
NestedAdapter<Keyboard, Keyboards> nestedKbds =
|
||||
new NestedAdapter<>(context, 0, keyboards, new LanguageFilter<Keyboard, Keyboards>(languageCode));
|
||||
NestedAdapter<LexicalModel, LexicalModels> nestedLexicals =
|
||||
new NestedAdapter<>(context, 0, lexicalModels, new LanguageFilter<LexicalModel, LexicalModels>(languageCode));
|
||||
protected void ensureLanguageDatasetExists(LanguageCoded coded) {
|
||||
this.ensureLanguageDatasetExists(coded.getLanguageName(), coded.getLanguageCode());
|
||||
}
|
||||
|
||||
return new LanguageDataset(nestedKbds, nestedLexicals, languageName, languageCode);
|
||||
protected LanguageDataset constructLanguageDataset(String languageName, String languageCode) {
|
||||
// Building a large set of nested adapters seriously drains performance.
|
||||
// FIXME: replace LanguageDatasets with the current LanguageMetadata - we want to work from that.
|
||||
|
||||
return new LanguageDataset(/*nestedKbds, nestedLexicals,*/ languageName, languageCode);
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
keyboards.clear();
|
||||
lexicalModels.clear();
|
||||
|
||||
this.languageIndexMap.clear();
|
||||
this.languageMetadata.clear();
|
||||
}
|
||||
|
||||
// TODO: Do we need to override any of this class's menbers in case someone SOMEHOW constructs
|
||||
|
|
@ -272,4 +321,8 @@ public class Dataset extends ArrayAdapter<LanguageDataset> {
|
|||
keyboards.notifyDataSetChanged();
|
||||
lexicalModels.notifyDataSetChanged();
|
||||
}
|
||||
|
||||
void _notifyDataSetChanged() {
|
||||
super.notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,4 +35,22 @@ public class Keyboard implements Serializable, LanguageCoded {
|
|||
public String getLanguageName() {
|
||||
return this.map.get(KMManager.KMKey_LanguageName);
|
||||
}
|
||||
|
||||
public boolean equals(Object obj) {
|
||||
if(obj instanceof Keyboard) {
|
||||
boolean lgCodeMatch = ((Keyboard) obj).getLanguageCode().equals(this.getLanguageCode());
|
||||
boolean idMatch = ((Keyboard) obj).getId().equals(this.getId());
|
||||
|
||||
return lgCodeMatch && idMatch;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
String id = getId();
|
||||
String lgCode = getLanguageCode();
|
||||
return id.hashCode() * lgCode.hashCode();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,21 +8,19 @@ import com.tavultesoft.kmea.data.adapters.AdapterFilter;
|
|||
import com.tavultesoft.kmea.data.adapters.NestedAdapter;
|
||||
|
||||
public class LanguageDataset {
|
||||
// The following adapters should actually be CUSTOM subclassed versions, not normal ArrayAdapters.
|
||||
// See https://guides.codepath.com/android/Using-an-ArrayAdapter-with-ListView, "Using a Custom ArrayAdapter".
|
||||
public final NestedAdapter<Keyboard, Dataset.Keyboards> keyboards;
|
||||
public final NestedAdapter<LexicalModel, Dataset.LexicalModels> lexicalModels;
|
||||
// public final NestedAdapter<Keyboard, Dataset.Keyboards, String> keyboards;
|
||||
// public final NestedAdapter<LexicalModel, Dataset.LexicalModels, String> lexicalModels;
|
||||
|
||||
public final String languageName;
|
||||
public final String languageCode;
|
||||
|
||||
public LanguageDataset(NestedAdapter<Keyboard, Dataset.Keyboards> keyboards,
|
||||
NestedAdapter<LexicalModel, Dataset.LexicalModels> lexicalModels,
|
||||
public LanguageDataset(/*NestedAdapter<Keyboard, Dataset.Keyboards, String> keyboards,
|
||||
NestedAdapter<LexicalModel, Dataset.LexicalModels, String> lexicalModels,*/
|
||||
String languageName,
|
||||
String languageCode) {
|
||||
|
||||
this.keyboards = keyboards;
|
||||
this.lexicalModels = lexicalModels;
|
||||
// this.keyboards = keyboards;
|
||||
// this.lexicalModels = lexicalModels;
|
||||
|
||||
this.languageName = languageName;
|
||||
this.languageCode = languageCode;
|
||||
|
|
|
|||
|
|
@ -36,4 +36,20 @@ public class LexicalModel implements Serializable, LanguageCoded{
|
|||
public String getLanguageName() {
|
||||
return this.map.get(KMManager.KMKey_LanguageName);
|
||||
}
|
||||
|
||||
public boolean equals(Object obj) {
|
||||
if(obj instanceof LexicalModel) {
|
||||
boolean lgCodeMatch = ((LexicalModel) obj).getLanguageCode().equals(this.getLanguageCode());
|
||||
boolean idMatch = ((LexicalModel) obj).getId().equals(this.getId());
|
||||
|
||||
return lgCodeMatch && idMatch;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return getId().hashCode() * getLanguageCode().hashCode();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,11 +8,11 @@ import java.util.List;
|
|||
* Used by NestedAdapters to selectively filter which elements are selected from the original Adapter.
|
||||
* @param <Element> The type of both Adapters' elements.
|
||||
*/
|
||||
public interface AdapterFilter<Element, Adapter extends ArrayAdapter<Element>> {
|
||||
public interface AdapterFilter<Element, Adapter extends ArrayAdapter<Element>, FilterArg> {
|
||||
/**
|
||||
* Filters the specified adapter for matching elements.
|
||||
* @param adapter An existing adapter with elements, some of which should be excluded by this filter.
|
||||
* @return A List of all elements of the adapter that match the filter's conditions.
|
||||
*/
|
||||
List<Element> selectFrom(Adapter adapter);
|
||||
List<Element> selectFrom(Adapter adapter, FilterArg argument);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,9 +18,9 @@ import java.util.List;
|
|||
* @param <Element> The shared item type of the two Adapters.
|
||||
* @param <A> The type specification of the Adapter to be nested/linked.
|
||||
*/
|
||||
public class NestedAdapter<Element, A extends ArrayAdapter<Element> & ListBacked<Element>> extends ArrayAdapter<Element> {
|
||||
public class NestedAdapter<Element, A extends ArrayAdapter<Element> & ListBacked<Element>, FilterArg> extends ArrayAdapter<Element> {
|
||||
final A wrappedAdapter;
|
||||
final AdapterFilter<Element, A> filter;
|
||||
final AdapterFilter<Element, A, FilterArg> filter;
|
||||
final List<Element> filteredList;
|
||||
|
||||
boolean isMutating = false;
|
||||
|
|
@ -33,15 +33,17 @@ public class NestedAdapter<Element, A extends ArrayAdapter<Element> & ListBacked
|
|||
* @param <S> The type of the 'wrapped' source ArrayAdapter (possibly a subclass).
|
||||
* @param <A> The type of the 'listener' NestedAdapter, complete with its generic type parameters.
|
||||
*/
|
||||
static class WrapperObserver<E, S extends ArrayAdapter<E> & ListBacked<E>, A extends NestedAdapter<E, S>> extends DataSetObserver {
|
||||
static class WrapperObserver<E, S extends ArrayAdapter<E> & ListBacked<E>, A extends NestedAdapter<E, S, F>, F> extends DataSetObserver {
|
||||
// By being static and using a WeakReference here, we avoid memory leaks that would otherwise
|
||||
// prevent our owner from being GC'd.
|
||||
private WeakReference<A> listenerRef;
|
||||
private S source;
|
||||
private final F filterArg;
|
||||
|
||||
WrapperObserver(A listener, S source) {
|
||||
WrapperObserver(A listener, S source, F filterArg) {
|
||||
this.listenerRef = new WeakReference<>(listener);
|
||||
this.source = source;
|
||||
this.filterArg = filterArg;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -57,7 +59,7 @@ public class NestedAdapter<Element, A extends ArrayAdapter<Element> & ListBacked
|
|||
}
|
||||
listener.setNotifyOnChange(false); // Disable event notifications temporarily.
|
||||
listener._internalClear();;
|
||||
listener._internalAddAll(listener.filter.selectFrom(listener.wrappedAdapter));
|
||||
listener._internalAddAll(listener.filter.selectFrom(listener.wrappedAdapter, this.filterArg));
|
||||
listener.notifyDataSetChanged(); // Re-enables events and signals that we did change.
|
||||
}
|
||||
|
||||
|
|
@ -73,26 +75,26 @@ public class NestedAdapter<Element, A extends ArrayAdapter<Element> & ListBacked
|
|||
}
|
||||
}
|
||||
|
||||
private final WrapperObserver<Element, A, NestedAdapter<Element, A>> observer;
|
||||
private final WrapperObserver<Element, A, NestedAdapter<Element, A, FilterArg>, FilterArg> observer;
|
||||
|
||||
public NestedAdapter(@NonNull Context context, int resource, @NonNull A adapter) {
|
||||
this(context, resource, adapter, new AdapterFilter<Element, A>() {
|
||||
this(context, resource, adapter, new AdapterFilter<Element, A, FilterArg>() {
|
||||
@Override
|
||||
public List<Element> selectFrom(A adapter) {
|
||||
public List<Element> selectFrom(A adapter, FilterArg dummy) {
|
||||
return adapter.asList();
|
||||
}
|
||||
});
|
||||
}, null);
|
||||
}
|
||||
|
||||
public NestedAdapter(@NonNull Context context, int resource, @NonNull A adapter, AdapterFilter<Element, A> filter) {
|
||||
this(context, resource, adapter, filter, filter.selectFrom(adapter));
|
||||
public NestedAdapter(@NonNull Context context, int resource, @NonNull A adapter, AdapterFilter<Element, A, FilterArg> filter, FilterArg filterArg) {
|
||||
this(context, resource, adapter, filter, filterArg, filter.selectFrom(adapter, filterArg));
|
||||
}
|
||||
|
||||
public NestedAdapter(@NonNull Context context, int resource, @NonNull A adapter, AdapterFilter<Element, A> filter, List<Element> filteredList) {
|
||||
public NestedAdapter(@NonNull Context context, int resource, @NonNull A adapter, AdapterFilter<Element, A, FilterArg> filter, FilterArg filterArg, List<Element> filteredList) {
|
||||
super(context, resource, filteredList);
|
||||
|
||||
this.wrappedAdapter = adapter;
|
||||
observer = new WrapperObserver<>(this, adapter);
|
||||
observer = new WrapperObserver<>(this, adapter, filterArg);
|
||||
this.wrappedAdapter.registerDataSetObserver(observer);
|
||||
|
||||
this.filter = filter;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue