a SharedPreferences replacement for Android with multiprocess support

Last update: Aug 19, 2022

DEPRECATED - no longer actively maintained

Tray - a SharedPreferences replacement for Android

Build Status License

If you have read the documentation of the SharedPreferences you might have seen one of these warnings:

Note: This class does not support use across multiple processes.

Google even deprecated the multiprocess support because it never worked relieable

Tray is this mentioned explicit cross-process data management approach powered by a ContentProvider. Tray also provides an advanced API which makes it super easy to access and maintain your data with upgrade and migrate mechanisms. Welcome to SharedPreferences 2.0 aka Tray.

Features

Usage

Simple tutorial how to use Tray in your project instead of the SharedPreferences

Save and read preferences

// create a preference accessor. This is for global app preferences.
final AppPreferences appPreferences = new AppPreferences(getContext()); // this Preference comes for free from the library
// save a key value pair
appPreferences.put("key", "lorem ipsum");

// read the value for your key. the second parameter is a fallback (optional otherwise throws)
final String value = appPreferences.getString("key", "default");
Log.v(TAG, "value: " + value); // value: lorem ipsum

// read a key that isn't saved. returns the default (or throws without default)
final String defaultValue = appPreferences.getString("key2", "default");
Log.v(TAG, "value: " + defaultValue); // value: default

No Editor, no commit() or apply() 😉

Create your own preference module

It's recommended to bundle preferences in groups, so called modules instead of putting everything in one global module. If you were using SharedPreferences before, you might have used different files to group your preferences. Extending the TrayModulePreferences and put all Keys inside this class is a recommended way to keep your code clean.

// create a preference accessor for a module
public class MyModulePreference extends TrayPreferences {

    public static String KEY_IS_FIRST_LAUNCH = "first_launch";

    public MyModulePreference(final Context context) {
        super(context, "myModule", 1);
    }
}
// accessing the preferences for my own module
final MyModulePreference myModulePreference = new MyModulePreference(getContext());
myModulePreference.put(MyModulePreference.KEY_IS_FIRST_LAUNCH, false);

See the sample project for more

Like the Android SQLiteOpenHelper a TrayPreference lets you implement methods to handle versioning.

public class MyModulePreference extends TrayPreferences {

    public MyModulePreference(final Context context) {
        super(context, "myModule", 1);
    }

    @Override
    protected void onCreate(final int initialVersion) {
        super.onCreate(initialVersion);
    }

    @Override
    protected void onUpgrade(final int oldVersion, final int newVersion) {
        super.onUpgrade(oldVersion, newVersion);
    }

    @Override
    protected void onDowngrade(final int oldVersion, final int newVersion) {
        super.onDowngrade(oldVersion, newVersion);
    }
}

// TOOD add clear sample

Migrate from SharedPreferences to Tray

To migrate values from SharedPreferences you have to create you own preference module. This module will be now store all of your SharedPreferences values.

public class ImportPreferences extends TrayPreferences {

    // The SharedPreferences name
    private static final String SHARED_PREFERENCES_FILE_NAME = "PREF_NAME";
    
    // The key inside the SHARED_PREFERENCES_NAME
    private static final String KEY_FIRST_LAUNCH = "KEY_FIRST_LAUNCH";
    
    // The new key for this module
    private static final String KEY_FIRST_LAUNCH_TRAY = "KEY_FIRST_LAUNCH_TRAY";
    
    public ImportPreferences(@NonNull Context context) {
        super(context, "myImportModule", 1);
    }    
    
    // Called only once when the module was created
    @Override
    protected void onCreate(int initialVersion) {
        super.onCreate(initialVersion);
            
        // Create a SharedPreferencesImport object
        SharedPreferencesImport importPref = 
            new SharedPreferencesImport(getContext(), 
                SHARED_PREFERENCES_FILE_NAME, KEY_FIRST_LAUNCH, KEY_FIRST_LAUNCH_TRAY);
            
        // Finally migrate it
        migrate(importPref);
    }
}

Getting Started

Add Tray to your project
GitHub Packages
repositories {
    maven {
        url = uri("https://maven.pkg.github.com/GCX-HCI/tray")
    }
}

dependencies {
    implementation "net.grandcentrix.tray:tray:0.12.0"
}
JCenter (deprecated)
repositories {
    jcenter()
}

dependencies {
    implementation "net.grandcentrix.tray:tray:0.12.0"
}

More on the ContentProvider configuration can be found in the wiki

Testcoverage 100%

Tray has 100% test coverage and we'll try to keep it at that level for stable releases.

You can run the coverage report with ./gradlew createDebugCoverageReport. You'll find the output in library/build/outputs/coverage/debug/index.html which looks like this:

coverage report

You can check the coverage report at codecov.io

Those ~170 tests will help us indicate bugs in the future before we publish them. Don't think the code is 100% bug free based on the test coverage.

Build state

Build Status

codecov.io

ContentProvider is overkill

At first, it was the simplest way to use IPC with Binder to solve the multiprocess problem. Using the ContentProvider with a database turned out to be very handy when it comes to save metadata. We thought about replacing the database with the real SharedPreferences to boost the performance (the SharedPreferences do not access the disk for every read/write action which causes the multiprocess problem btw) but the metadata seemed to be more valuable to us. see more informations

If you have found a better solution implement the TrayStorage and contribute to this project! We would appreciate it.

That said, yes the performance isn't as good as the SharedPreferences. But the performance is good enough to save/access single key value pairs synchron. If you want to save more you should think about a simple database.

Missing Features

Tray is ready to use without showblockers! But here are some nice to have features for the future:

  • Reactive wrapper to observe values
  • no support to save Set<String>. Is someone using this?
  • more metadata fields: (i.e. app version code/name)

Roadmap

  • performance tests
  • memory cache for based on contentobservers
  • prevent app crashes due to database errors
  • rx wrapper for changes
  • save additional data types (Set<String>, byte[])

Versions

Version 0.11.1 07.02.17
  • preference key cannot be empty #84
  • clearBut(TrayPreference) -> clearBut(AbstractTrayPreference) #89
Version 0.11.0 07.09.16
  • all accessor methods return boolean indicating the success of i.e. put, remove. They will never again throw an error. #69
  • new contains() method #74
Version 0.10.0 31.05.16
  • All features and changes of the 1.0.0-rc preview builds
  • #65 Fix deletion of non string migrated shared preferences.
Version 1.0.0 preview - postponed until the memory cache is ready
1.0.0-rc3 05.11.15
  • hotfix for listener on Android 6.0 which has caused a infinity loop #55
  • the sample project includes now a way to test the multi process support compared to the SharedPreferences
  • removed unnecessary write operation for every version check #54
1.0.0-rc2 24.09.15
  • added logging for all data changing methods. Enable via adb shell setprop log.tag.Tray VERBOSE
1.0.0-rc1 21.09.15
  • Android M Auto Backup feature support (see the Documentation)
    • split up database for user and device specific data (device specific data can now be excluded from the auto backup)
    • TrayPreferences has now an optional 3. constructor parameter TrayStorage.Type, USER or DEVICE indicating the internal database (required for Android M Auto Backup). Default is USER
  • New methods and changes
    • PreferenceAccessor#wipe() clears the preference data and it's internal data (version)
    • TrayPreferences#annexModule(String name) imports a module by name and wipes it afterwards. This allows renaming of preferences without losing data
    • AbstractTrayPreference#annex(ModularizedStorage<TrayItem>) allows a storage to import another storage, wipes the imported afterwards
    • Preference #onCreate(...) and #onUpgrade(...) aren't abstract anymore because they don't require an implementation
  • Deprecations (will be removed soon)
    • TrayAppPreferences is now deprecated. Use AppPreferences instead (renaming)
    • TrayModulePreferences is now deprecated. Use TrayPreferences instead to extend from for your own Preferences
  • Internal structure
    • new package structure. merged packages accessor, migration and storage into core
    • package provider contains a TrayStorage implementation with a ContentProvider. Is easy exchangeable with another TrayStorage implementation
    • ModularizedTrayPreference is now called AbstractTrayPreference
    • ModularizedStorage was renamed to TrayStorage
Version 0.9.2 02.06.15
  • getContext() is working in TrayModulePreference#onCreate
Version 0.9.1 18.05.15
  • saving null with mPref.put(KEY, null) works now
  • access to preference with throwing methods instead of default value (throws ItemNotFoundException). Example: mPref.getString(KEY); instead of mPref.getString(KEY, "defaultValue");
  • WrongTypeException when accessing a preference with a different type and the data isn't parsable. Float (10.1f) -> String works, String ("10.1") -> Float works, String ("test") -> Float throws!
  • javadoc in now included in aar
Version 0.9 27.04.15
  • initial public release
Version 0.2 - 0.8
  • Refactoring
  • 100% Testing
  • Bugfixing
Version 0.1 17.09.14
  • first working prototype

Contributors

License

Copyright 2015 grandcentrix GmbH

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

GitHub

https://github.com/grandcentrix/tray
Comments
  • 1. Can't initialise new AppPreferences(getContext())

    Hi everyone, here asking for some help!

    It crashed in the TrayContract.java

    I get a nullpointerexpection, in this method @NonNull private static String getAuthority(@NonNull final Context context) { return TextUtils.isEmpty(sTestAuthority) ? context.getString(R.string.tray__authority) : sTestAuthority; }

    I believe it can't get the context.getString(R.string.tray__authority) in here.

    • I set the the tray__authority and I cleaned the project.
    • I set one different value for every flavour, based on the the ApplicationId.
    • if I do context.getString(R.string.tray__authority) I get the right value.
    • It's the same context has the one I gave to your AppPreferences.

    I don't understand the issue, can somebody help?

    Reviewed by dfloureiro at 2017-03-10 18:26
  • 2. java.lang.IllegalStateException

    java.lang.IllegalStateException: could not access stored data with uri content://com.taobao.qianniu.tray/internal_preferences/qianniu/version. Is the provider registered in the manifest of your application? at net.grandcentrix.tray.provider.TrayProviderHelper.queryProvider(SourceFile:196) at net.grandcentrix.tray.storage.TrayStorage.getVersion(SourceFile:85) at net.grandcentrix.tray.accessor.Preference.changeVersion(SourceFile:158) at net.grandcentrix.tray.accessor.Preference.(SourceFile:52) at net.grandcentrix.tray.accessor.TrayPreference.(SourceFile:33) at net.grandcentrix.tray.TrayModulePreferences.(SourceFile:40)

    MediaPad X1 7.0 11 PE-TL00M 7 Coolpad 9150W 6 HUAWEI P7-L07 6 PE-TL20 6 Che2-UL00 6 G621-TL00 5 vivo X5Pro D 5 Coolpad 8675-A 4 SM-N900

    Reviewed by HandsomeL at 2015-10-10 02:20
  • 3. Set unique authority prefix automatically

    If developer include tray library in their own library, it's difficult to get app applicationId in library build.gradle. For users who use that library may don't have to realize what tray__authority is.

    Reviewed by b95505017 at 2016-09-08 16:44
  • 4. Compiler complains passing TrayStorage.Type.USER

    The following causes the compiler to complain; TrayStorage.Type.USER needs to be an int.

    private class MyPreference extends TrayPreferences {
        public MyPreference(@NonNull final Context context) {
            super(context, "myModule", VERSION, TrayStorage.Type.USER);
        }
    }
    
    Reviewed by jonameson at 2016-07-22 13:14
  • 5. NullPointerException at unregisterOnTrayPreferenceChangeListener in Service

    tray Version 0.12.0

    tray is initialized in onStartCommand of a service

    Device(s) (issues reported in Play store on Samsung Galaxy S8, A8 and S8+, but in pre launch test on multiple other devices as well )

    minSdkVersion 21, compile and targetSDK 29

    Stacktrace

    java.lang.RuntimeException: at android.app.ActivityThread.handleStopService (ActivityThread.java:3948) at android.app.ActivityThread.access$1800 (ActivityThread.java:237) at android.app.ActivityThread$H.handleMessage (ActivityThread.java:1828) at android.os.Handler.dispatchMessage (Handler.java:106) at android.os.Looper.loop (Looper.java:214) at android.app.ActivityThread.main (ActivityThread.java:7078) at java.lang.reflect.Method.invoke (Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run (RuntimeInit.java:494) at com.android.internal.os.ZygoteInit.main (ZygoteInit.java:964) Caused by: java.lang.NullPointerException: at com.android.internal.util.Preconditions.checkNotNull (Preconditions.java:128) at android.content.ContentResolver.unregisterContentObserver (ContentResolver.java:1993) at net.grandcentrix.tray.provider.ContentProviderStorage.unregisterOnTrayPreferenceChangeListener (ContentProviderStorage.java:362) at net.grandcentrix.tray.core.AbstractTrayPreference.unregisterOnTrayPreferenceChangeListener (AbstractTrayPreference.java:173) at com.twom.bico.services.JepsterService.destroyService (JepsterService.java:583) at com.twom.bico.services.JepsterService.onDestroy (JepsterService.java:345) at android.app.ActivityThread.handleStopService (ActivityThread.java:3928) ```

    For some reason nullpointer exceptions are reported when the code wants to unregister the listener. In the code this is called just before stopping the Service. Code:

    ...

        if (mMultiProcessPreferences != null && mAppPrefsListener != null){
            mMultiProcessPreferences.unregisterOnTrayPreferenceChangeListener(mAppPrefsListener);
        }
    
        // Stop running service in foreground.
        this.stopForeground(true);
    
        // Cancel the persistent notification.
        mNotificationManager.cancel(NOTIFICATION_ID);
    
        //Stop service
        stopSelf();
    

    }

    Do you have any explanation or hint why this happens? I can't reproduce it in Android Studio, but i have got multiple reports in the Play Store and also in the automatic pre-launch tests.

    Thanks a lot in advance!

    Reviewed by tomwassink at 2020-08-24 20:33
  • 6. has lot of blocked operations reported from blockcanary

    I saw lot of blocked reported from blockcanary when i cannot any type prefs read (getInt getBoolean), i have around 100 try items saved in TrayPreferences table. it should be very faster to read

    Reviewed by eggcaker at 2017-07-13 02:18
  • 7. "could not access stored data with uri content" error

    My application is the only launcher of the android firmware.I got the follow error: The TrayPreferences is initialized in MyApplication.onCreate

    08-12 14:52:14.283 E/ActivityThread( 1462): Failed to find provider info for com.testtay.user_preference
    08-12 14:52:14.293 E/CrashHandler( 1462): encountered a fatal error
    08-12 14:52:14.293 E/CrashHandler( 1462): java.lang.RuntimeException: Unable to create application com.testtay.application.MyApplication: java.lang.IllegalStateException: could not access stored data with uri content://com.testtay.user_preference/internal_preferences/group/version?backup=true. Is the provider registered in the manifest of your application?
    08-12 14:52:14.293 E/CrashHandler( 1462):       at android.app.ActivityThread.handleBindApplication(ActivityThread.java:4347)
    08-12 14:52:14.293 E/CrashHandler( 1462):       at android.app.ActivityThread.access$1500(ActivityThread.java:135)
    08-12 14:52:14.293 E/CrashHandler( 1462):       at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1256)
    08-12 14:52:14.293 E/CrashHandler( 1462):       at android.os.Handler.dispatchMessage(Handler.java:102)
    08-12 14:52:14.293 E/CrashHandler( 1462):       at android.os.Looper.loop(Looper.java:136)
    08-12 14:52:14.293 E/CrashHandler( 1462):       at android.app.ActivityThread.main(ActivityThread.java:5017)
    08-12 14:52:14.293 E/CrashHandler( 1462):       at java.lang.reflect.Method.invokeNative(Native Method)
    08-12 14:52:14.293 E/CrashHandler( 1462):       at java.lang.reflect.Method.invoke(Method.java:515)
    08-12 14:52:14.293 E/CrashHandler( 1462):       at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:788)
    08-12 14:52:14.293 E/CrashHandler( 1462):       at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:604)
    08-12 14:52:14.293 E/CrashHandler( 1462):       at dalvik.system.NativeStart.main(Native Method)
    08-12 14:52:14.293 E/CrashHandler( 1462): Caused by: java.lang.IllegalStateException: could not access stored data with uri content://com.testtay.user_preference/internal_preferences/group/version?backup=true. Is the provider registered in the manifest of your application?
    08-12 14:52:14.293 E/CrashHandler( 1462):       at net.grandcentrix.tray.provider.TrayProviderHelper.queryProvider(TrayProviderHelper.java:145)
    08-12 14:52:14.293 E/CrashHandler( 1462):       at net.grandcentrix.tray.provider.ContentProviderStorage.getVersion(ContentProviderStorage.java:214)
    08-12 14:52:14.293 E/CrashHandler( 1462):       at net.grandcentrix.tray.core.Preferences.changeVersion(Preferences.java:224)
    08-12 14:52:14.293 E/CrashHandler( 1462):       at net.grandcentrix.tray.core.Preferences.<init>(Preferences.java:51)
    08-12 14:52:14.293 E/CrashHandler( 1462):       at net.grandcentrix.tray.core.AbstractTrayPreference.<init>(AbstractTrayPreference.java:31)
    08-12 14:52:14.293 E/CrashHandler( 1462):       at net.grandcentrix.tray.TrayPreferences.<init>(TrayPreferences.java:43)
    08-12 14:52:14.293 E/CrashHandler( 1462):       at net.grandcentrix.tray.TrayPreferences.<init>(TrayPreferences.java:48)
    08-12 14:52:14.293 E/CrashHandler( 1462):       at com.testtay.common.preferences.CacheTrayPreferences.<init>(CacheTrayPreferences.java:24)
    08-12 14:52:14.293 E/CrashHandler( 1462):       at com.testtay.common.preferences.UserPreferences.<init>(UserPreferences.java:34)
    08-12 14:52:14.293 E/CrashHandler( 1462):       at com.testtay.common.preferences.UserPreferences.init(UserPreferences.java:21)
    08-12 14:52:14.293 E/CrashHandler( 1462):       at com.testtay.utils.ProcessUtils.initCommon(ProcessUtils.java:149)
    08-12 14:52:14.293 E/CrashHandler( 1462):       at com.testtay.application.process.MainProcessApp.create(MainProcessApp.java:64)
    08-12 14:52:14.293 E/CrashHandler( 1462):       at com.testtay.application.MyApplication.onCreate(MyApplication.java:50)
    08-12 14:52:14.293 E/CrashHandler( 1462):       at android.app.Instrumentation.callApplicationOnCreate(Instrumentation.java:1021)
    08-12 14:52:14.293 E/CrashHandler( 1462):       at android.app.ActivityThread.handleBindApplication(ActivityThread.java:4344)
    08-12 14:52:14.293 E/CrashHandler( 1462):       ... 10 more
    
    Reviewed by ElegantWang at 2016-08-12 07:58
  • 8. TrayContentProvider does not found when using Gradle 1.5.0-beta2 & proguard enabled

    Though I've just reported the issue in Google's issue tracker, I thought it would be good to also report here.

    I just got following error when tried to use Android Gradle plugin 1.5.0-beta2 with proguard enabled. without proguard, it works fine. but when I enable proguard, it starts crashing right after I launch the app. With Gradle plugin 1.3.0, it works fine.

    I'm using 1.0.0-rc3 of the tray.

    java.lang.RuntimeException: Unable to get provider net.grandcentrix.tray.provider.TrayContentProvider: java.lang.ClassNotFoundException: Didn't find class "net.grandcentrix.tray.provider.TrayContentProvider" on path: DexPathList[[zip file "/data/app/net.yslibrary.omnitweety-1/base.apk"],nativeLibraryDirectories=[/data/app/net.yslibrary.omnitweety-1/lib/arm64, /data/app/net.yslibrary.omnitweety-1/base.apk!/lib/arm64-v8a, /vendor/lib64, /system/lib64]]
                                                         at android.app.ActivityThread.installProvider(ActivityThread.java:5156)
                                                         at android.app.ActivityThread.installContentProviders(ActivityThread.java:4748)
                                                         at android.app.ActivityThread.handleBindApplication(ActivityThread.java:4688)
                                                         at android.app.ActivityThread.-wrap1(ActivityThread.java)
                                                         at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1405)
                                                         at android.os.Handler.dispatchMessage(Handler.java:102)
                                                         at android.os.Looper.loop(Looper.java:148)
                                                         at android.app.ActivityThread.main(ActivityThread.java:5417)
                                                         at java.lang.reflect.Method.invoke(Native Method)
                                                         at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
                                                         at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)
                                                      Caused by: java.lang.ClassNotFoundException: Didn't find class "net.grandcentrix.tray.provider.TrayContentProvider" on path: DexPathList[[zip file "/data/app/net.yslibrary.omnitweety-1/base.apk"],nativeLibraryDirectories=[/data/app/net.yslibrary.omnitweety-1/lib/arm64, /data/app/net.yslibrary.omnitweety-1/base.apk!/lib/arm64-v8a, /vendor/lib64, /system/lib64]]
                                                         at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:56)
                                                         at java.lang.ClassLoader.loadClass(ClassLoader.java:511)
                                                         at java.lang.ClassLoader.loadClass(ClassLoader.java:469)
                                                         at android.app.ActivityThread.installProvider(ActivityThread.java:5141)
                                                         at android.app.ActivityThread.installContentProviders(ActivityThread.java:4748) 
                                                         at android.app.ActivityThread.handleBindApplication(ActivityThread.java:4688) 
                                                         at android.app.ActivityThread.-wrap1(ActivityThread.java) 
                                                         at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1405) 
                                                         at android.os.Handler.dispatchMessage(Handler.java:102) 
                                                         at android.os.Looper.loop(Looper.java:148) 
                                                         at android.app.ActivityThread.main(ActivityThread.java:5417) 
                                                         at java.lang.reflect.Method.invoke(Native Method) 
                                                         at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726) 
                                                         at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616) 
                                                        Suppressed: java.lang.ClassNotFoundException: net.grandcentrix.tray.provider.TrayContentProvider
                                                         at java.lang.Class.classForName(Native Method)
                                                         at java.lang.BootClassLoader.findClass(ClassLoader.java:781)
                                                         at java.lang.BootClassLoader.loadClass(ClassLoader.java:841)
                                                         at java.lang.ClassLoader.loadClass(ClassLoader.java:504)
                                                                ... 12 more
                                                      Caused by: java.lang.NoClassDefFoundError: Class not found using the boot class loader; no stack trace available
    
    Reviewed by yshrsmz at 2015-11-10 02:59
  • 9. Failed to open database '/data/user/0/com.xxx.xxx/databases/tray.db'

    tray Version (E.g. 0.12.0)

    0.11.1

    How have you setup tray (E.g. Initialized in Application.onCreate, in an Activity, BroadcastReceiver, IntentService, MainThread)

    compile 'net.grandcentrix.tray:tray:0.11.1'

    Device(s) (E.g. Samsung Galaxy S8)

    360Phone

    Android Version (E.g. Marshmallow or better API 23)

    Android7.1.1

    Stacktrace

    Stacktrace
    10-28 11:54:03.324 20407 20407 W ContextImpl: Failed to ensure /data/user/0/com.xxx.xxx/databases: mkdir failed: EACCES (Permission denied)
    10-28 11:54:03.324 20407 20407 E SQLiteLog: (14) cannot open file at line 32456 of [bda77dda96]
    10-28 11:54:03.324 20407 20407 E SQLiteLog: (14) os_unix.c:32456: (13) open(/data/user/0/com.xxx.xxx/databases/tray.db) - 
    10-28 11:54:03.326 20407 20407 E SQLiteDatabase: Failed to open database '/data/user/0/com.xxx.xxx/databases/tray.db'.
    10-28 11:54:03.326 20407 20407 E SQLiteDatabase: android.database.sqlite.SQLiteCantOpenDatabaseException: unknown error (code 14): Could not open database
    10-28 11:54:03.326 20407 20407 E SQLiteDatabase: 	at android.database.sqlite.SQLiteConnection.nativeOpen(Native Method)
    10-28 11:54:03.326 20407 20407 E SQLiteDatabase: 	at android.database.sqlite.SQLiteConnection.open(SQLiteConnection.java:209)
    10-28 11:54:03.326 20407 20407 E SQLiteDatabase: 	at android.database.sqlite.SQLiteConnection.open(SQLiteConnection.java:193)
    10-28 11:54:03.326 20407 20407 E SQLiteDatabase: 	at android.database.sqlite.SQLiteConnectionPool.openConnectionLocked(SQLiteConnectionPool.java:463)
    10-28 11:54:03.326 20407 20407 E SQLiteDatabase: 	at android.database.sqlite.SQLiteConnectionPool.open(SQLiteConnectionPool.java:185)
    10-28 11:54:03.326 20407 20407 E SQLiteDatabase: 	at android.database.sqlite.SQLiteConnectionPool.open(SQLiteConnectionPool.java:177)
    10-28 11:54:03.326 20407 20407 E SQLiteDatabase: 	at android.database.sqlite.SQLiteDatabase.openInner(SQLiteDatabase.java:808)
    10-28 11:54:03.326 20407 20407 E SQLiteDatabase: 	at android.database.sqlite.SQLiteDatabase.open(SQLiteDatabase.java:793)
    10-28 11:54:03.326 20407 20407 E SQLiteDatabase: 	at android.database.sqlite.SQLiteDatabase.openDatabase(SQLiteDatabase.java:696)
    10-28 11:54:03.326 20407 20407 E SQLiteDatabase: 	at android.app.ContextImpl.openOrCreateDatabase(ContextImpl.java:656)
    10-28 11:54:03.326 20407 20407 E SQLiteDatabase: 	at android.content.ContextWrapper.openOrCreateDatabase(ContextWrapper.java:289)
    10-28 11:54:03.326 20407 20407 E SQLiteDatabase: 	at android.database.sqlite.SQLiteOpenHelper.getDatabaseLocked(SQLiteOpenHelper.java:223)
    10-28 11:54:03.326 20407 20407 E SQLiteDatabase: 	at android.database.sqlite.SQLiteOpenHelper.getReadableDatabase(SQLiteOpenHelper.java:187)
    10-28 11:54:03.326 20407 20407 E SQLiteDatabase: 	at net.grandcentrix.tray.provider.TrayContentProvider.getReadableDatabase(TrayContentProvider.java:117)
    10-28 11:54:03.326 20407 20407 E SQLiteDatabase: 	at net.grandcentrix.tray.provider.TrayContentProvider.query(TrayContentProvider.java:265)
    10-28 11:54:03.326 20407 20407 E SQLiteDatabase: 	at android.content.ContentProvider.query(ContentProvider.java:1045)
    10-28 11:54:03.326 20407 20407 E SQLiteDatabase: 	at android.content.ContentProvider$Transport.query(ContentProvider.java:245)
    10-28 11:54:03.326 20407 20407 E SQLiteDatabase: 	at android.content.ContentResolver.query(ContentResolver.java:534)
    10-28 11:54:03.326 20407 20407 E SQLiteDatabase: 	at android.content.ContentResolver.query(ContentResolver.java:475)
    10-28 11:54:03.326 20407 20407 E SQLiteDatabase: 	at net.grandcentrix.tray.provider.TrayProviderHelper.queryProvider(TrayProviderHelper.java:169)
    10-28 11:54:03.326 20407 20407 E SQLiteDatabase: 	at net.grandcentrix.tray.provider.ContentProviderStorage.getVersion(ContentProviderStorage.java:216)
    10-28 11:54:03.326 20407 20407 E SQLiteDatabase: 	at net.grandcentrix.tray.core.Preferences.changeVersion(Preferences.java:258)
    10-28 11:54:03.326 20407 20407 E SQLiteDatabase: 	at net.grandcentrix.tray.core.Preferences.isVersionChangeChecked(Preferences.java:292)
    10-28 11:54:03.326 20407 20407 E SQLiteDatabase: 	at net.grandcentrix.tray.core.Preferences.<init>(Preferences.java:58)
    10-28 11:54:03.326 20407 20407 E SQLiteDatabase: 	at net.grandcentrix.tray.core.AbstractTrayPreference.<init>(AbstractTrayPreference.java:31)
    10-28 11:54:03.326 20407 20407 E SQLiteDatabase: 	at net.grandcentrix.tray.TrayPreferences.<init>(TrayPreferences.java:43)
    10-28 11:54:03.326 20407 20407 E SQLiteDatabase: 	at net.grandcentrix.tray.TrayPreferences.<init>(TrayPreferences.java:48)
    10-28 11:54:03.326 20407 20407 E SQLiteDatabase: 	at com.xxx.xxx.module.sp.DebugPreferences.<init>(DebugPreferences.java:15)
    10-28 11:54:03.326 20407 20407 E SQLiteDatabase: 	at com.xxx.xxx.module.sp.DebugPreferences.getInstance(DebugPreferences.java:24)
    10-28 11:54:03.326 20407 20407 E SQLiteDatabase: 	at com.xxx.xxx.module.sp.DebugPreferences.doUseHttp(DebugPreferences.java:42)
    10-28 11:54:03.326 20407 20407 E SQLiteDatabase: 	at com.xxx.xxx.QikuApplication.debugSetting(QikuApplication.java:86)
    10-28 11:54:03.326 20407 20407 E SQLiteDatabase: 	at com.xxx.xxx.QikuApplication.onCreate(QikuApplication.java:44)
    10-28 11:54:03.326 20407 20407 E SQLiteDatabase: 	at android.app.Instrumentation.callApplicationOnCreate(Instrumentation.java:1025)
    10-28 11:54:03.326 20407 20407 E SQLiteDatabase: 	at android.app.ActivityThread.handleBindApplication(ActivityThread.java:5509)
    10-28 11:54:03.326 20407 20407 E SQLiteDatabase: 	at android.app.ActivityThread.-wrap2(ActivityThread.java)
    10-28 11:54:03.326 20407 20407 E SQLiteDatabase: 	at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1579)
    10-28 11:54:03.326 20407 20407 E SQLiteDatabase: 	at android.os.Handler.dispatchMessage(Handler.java:102)
    10-28 11:54:03.326 20407 20407 E SQLiteDatabase: 	at android.os.Looper.loop(Looper.java:154)
    10-28 11:54:03.326 20407 20407 E SQLiteDatabase: 	at android.app.ActivityThread.main(ActivityThread.java:6244)
    10-28 11:54:03.326 20407 20407 E SQLiteDatabase: 	at java.lang.reflect.Method.invoke(Native Method)
    10-28 11:54:03.326 20407 20407 E SQLiteDatabase: 	at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:907)
    10-28 11:54:03.326 20407 20407 E SQLiteDatabase: 	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:797)
    

    Description (Just a place with additional information, more == better)

    Reviewed by toy-lin at 2017-10-31 09:52
  • 10. Performance

    Hi,

    I stumble upon another library called DPreference and did a quick test (using latest Tray version). Looks like Tray performance is not that good compare to DPreference, maybe due to DPreference is a simple library. Anyway hopefully will see some improvement for Tray in future.

    OnePlus1
    DPreference called setString 1000 times cost : 403
    DPreference called getString 1000 times cost : 543
    
    Tray called setString 1000 times cost : 8737
    Tray called getString 1000 times cost : 3023
    
    Samsung S8
    DPreference called setString 1000 times cost : 231
    DPreference called getString 1000 times cost : 154
    
    Tray called setString 1000 times cost : 12946
    Tray called getString 1000 times cost : 7965
    
    Reviewed by xDragonZ at 2017-07-09 09:36
  • 11. error SQLiteConstraintException on emulator with Android N

    04-28 08:37:18.840 8826-9078/*** E/SQLiteDatabase: Error inserting KEY=version MIGRATED_KEY=null UPDATED=1493368638631 CREATED=1493368638631 MODULE=cookie VALUE=1 android.database.sqlite.SQLiteConstraintException: UNIQUE constraint failed: TrayInternal.MODULE, TrayInternal.KEY (code 2067) at android.database.sqlite.SQLiteConnection.nativeExecuteForLastInsertedRowId(Native Method) at android.database.sqlite.SQLiteConnection.executeForLastInsertedRowId(SQLiteConnection.java:782) at android.database.sqlite.SQLiteSession.executeForLastInsertedRowId(SQLiteSession.java:788) at android.database.sqlite.SQLiteStatement.executeInsert(SQLiteStatement.java:86) at android.database.sqlite.SQLiteDatabase.insertWithOnConflict(SQLiteDatabase.java:1472) at android.database.sqlite.SQLiteDatabase.insert(SQLiteDatabase.java:1343) at net.grandcentrix.tray.provider.SqliteHelper.insertOrUpdate(SqliteHelper.java:148) at net.grandcentrix.tray.provider.TrayContentProvider.insertOrUpdate(TrayContentProvider.java:206) at net.grandcentrix.tray.provider.TrayContentProvider.insert(TrayContentProvider.java:187) at android.content.ContentProvider$Transport.insert(ContentProvider.java:264) at android.content.ContentResolver.insert(ContentResolver.java:1274) at net.grandcentrix.tray.provider.TrayProviderHelper.persist(TrayProviderHelper.java:151) at net.grandcentrix.tray.provider.TrayProviderHelper.persist(TrayProviderHelper.java:142) at net.grandcentrix.tray.provider.ContentProviderStorage.setVersion(ContentProviderStorage.java:350) at net.grandcentrix.tray.core.Preferences.changeVersion(Preferences.java:272) at net.grandcentrix.tray.core.Preferences.isVersionChangeChecked(Preferences.java:292) at net.grandcentrix.tray.core.Preferences.(Preferences.java:58) at net.grandcentrix.tray.core.AbstractTrayPreference.(AbstractTrayPreference.java:31) at net.grandcentrix.tray.TrayPreferences.(TrayPreferences.java:43) at net.grandcentrix.tray.TrayPreferences.(TrayPreferences.java:48) at ***.module.sp.CookiePreferences.(CookiePreferences.java:23) at ***.util.FileTypeUtil.getCookies(FileTypeUtil.java:839) at ***.util.Util.SendPhoneInfo(Util.java:386) at ***.util.Util.access$000(Util.java:124) at .util.Util$1.run(Util.java:371) 04-28 08:37:18.840 8826-9078/ W/Tray: Couldn't update or insert data. Uri: content://***.tray/internal_preferences/cookie/version?backup=true

    Reference:

    compile 'net.grandcentrix.tray:tray:0.11.1'

    And code at "net.grandcentrix.tray.core.Preferences.changeVersion(Preferences.java:272)" is below:

    getStorage().setVersion(newVersion);

    Reviewed by toy-lin at 2017-04-28 08:57
  • 12. ANR that leads to app crashing, support needed

    Screenshot_20200927-185251_Play Console

    See attached picture, is self explanatory

    The main issue is when update and migrate your data from one app version to next one with versioned Preferences and a onUpgrade() method - onUpgrade took more than 5 seconds and the foreground service crashes since startForeground() not reached within the 5 seconds.

    Reviewed by Duna at 2020-09-27 15:55
  • 13. 不知道怎么回事的bug

    net.grandcentrix.tray.core.TrayRuntimeException:Internal tray error. Could not find the provider authority. Please fill an issue at https://github.com/grandcentrix/tray/issues

    Reviewed by yangjunjin at 2020-06-10 03:59
  • 14. can not support multi app share data

    I custom authorities in my A app manifest,but the B app can not appoint the authorities to visit the A app data, so why not public the TrayContract.sAuthority set method?

    Reviewed by qiurudong at 2020-03-19 11:28
  • 15. java.lang.NoClassDefFoundError: Failed resolution of: Lnet/grandcentrix/tray/R$string;

    tray Version (E.g. 0.12.0)

    How have you setup tray (E.g. Initialized in Application.onCreate, in an Activity, BroadcastReceiver, IntentService, MainThread)

    Application.onCreate

    Device(s) (E.g. Samsung Galaxy S8)

    RedMi

    Android Version (E.g. Marshmallow or better API 23)

    API23

    Stacktrace

    Stacktrace
    03-05 15:55:05.045 15503-15503/com.knew.feed E/AndroidRuntime: FATAL EXCEPTION: main
      Process: com.knew.feed, PID: 15503
      java.lang.NoClassDefFoundError: Failed resolution of: Lnet/grandcentrix/tray/R$string;
          at net.grandcentrix.tray.provider.TrayContract.checkOldWayToSetAuthority(TrayContract.java:92)
          at net.grandcentrix.tray.provider.TrayContract.getAuthority(TrayContract.java:122)
          at net.grandcentrix.tray.provider.TrayContract.generateContentUri(TrayContract.java:109)
          at net.grandcentrix.tray.provider.TrayContract.generateContentUri(TrayContract.java:80)
          at net.grandcentrix.tray.provider.TrayUri.<init>(TrayUri.java:75)
          at net.grandcentrix.tray.provider.ContentProviderStorage.<init>(ContentProviderStorage.java:146)
          at net.grandcentrix.tray.TrayPreferences.<init>(TrayPreferences.java:43)
          at net.grandcentrix.tray.TrayPreferences.<init>(TrayPreferences.java:48)
          at com.knew.feed.component.MyAppPreferences.<init>(MyAppPreferences.kt:10)
          at com.knew.feed.App$prefs$2.invoke(App.kt:45)
          at com.knew.feed.App$prefs$2.invoke(App.kt:36)
          at kotlin.SynchronizedLazyImpl.getValue(LazyJVM.kt:74)
          at com.knew.feed.App.getPrefs(App.kt)
          at com.knew.feed.utils.DistributionChannelUtils$prefs$2.invoke(DistributionChannelUtils.kt:17)
          at com.knew.feed.utils.DistributionChannelUtils$prefs$2.invoke(DistributionChannelUtils.kt:14)
          at kotlin.SynchronizedLazyImpl.getValue(LazyJVM.kt:74)
          at com.knew.feed.utils.DistributionChannelUtils.getPrefs(DistributionChannelUtils.kt)
          at com.knew.feed.utils.DistributionChannelUtils.access$getPrefs$p(DistributionChannelUtils.kt:14)
          at com.knew.feed.utils.DistributionChannelUtils$distributionChannel$2.invoke(DistributionChannelUtils.kt:22)
          at com.knew.feed.utils.DistributionChannelUtils$distributionChannel$2.invoke(DistributionChannelUtils.kt:14)
          at kotlin.SynchronizedLazyImpl.getValue(LazyJVM.kt:74)
          at com.knew.feed.utils.DistributionChannelUtils.getDistributionChannel(DistributionChannelUtils.kt)
          at com.knew.feed.utils.DistributionChannelUtils.isDevelopment(DistributionChannelUtils.kt:29)
          at com.knew.feed.utils.LoggerUtilsKt.initLogger(LoggerUtils.kt:35)
          at com.knew.feed.App.init(App.kt:85)
          at com.knew.feed.App.onCreate(App.kt:57)
          at android.app.Instrumentation.callApplicationOnCreate(Instrumentation.java:1018)
          at android.app.ActivityThread.handleBindApplication(ActivityThread.java:5019)
          at android.app.ActivityThread.access$1800(ActivityThread.java:178)
          at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1578)
          at android.os.Handler.dispatchMessage(Handler.java:111)
          at android.os.Looper.loop(Looper.java:207)
          at android.app.ActivityThread.main(ActivityThread.java:5845)
          at java.lang.reflect.Method.invoke(Native Method)
          at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:907)
          at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:768)
       Caused by: java.lang.ClassNotFoundException: Didn't find class "net.grandcentrix.tray.R$string" on path: DexPathList[[zip file "/data/app/com.knew.feed-1/base.apk"],nativeLibraryDirectories=[/data/app/com.knew.feed-1/lib/arm, /data/app/com.knew.feed-1/base.apk!/lib/armeabi-v7a, /vendor/lib, /system/lib]]
          at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:56)
          at java.lang.ClassLoader.loadClass(ClassLoader.java:511)
          at java.lang.ClassLoader.loadClass(ClassLoader.java:469)
          at net.grandcentrix.tray.provider.TrayContract.checkOldWayToSetAuthority(TrayContract.java:92) 
          at net.grandcentrix.tray.provider.TrayContract.getAuthority(TrayContract.java:122) 
          at net.grandcentrix.tray.provider.TrayContract.generateContentUri(TrayContract.java:109) 
          at net.grandcentrix.tray.provider.TrayContract.generateContentUri(TrayContract.java:80) 
          at net.grandcentrix.tray.provider.TrayUri.<init>(TrayUri.java:75) 
          at net.grandcentrix.tray.provider.ContentProviderStorage.<init>(ContentProviderStorage.java:146) 
          at net.grandcentrix.tray.TrayPreferences.<init>(TrayPreferences.java:43) 
          at net.grandcentrix.tray.TrayPreferences.<init>(TrayPreferences.java:48) 
          at com.knew.feed.component.MyAppPreferences.<init>(MyAppPreferences.kt:10) 
          at com.knew.feed.App$prefs$2.invoke(App.kt:45) 
          at com.knew.feed.App$prefs$2.invoke(App.kt:36) 
          at kotlin.SynchronizedLazyImpl.getValue(LazyJVM.kt:74) 
          at com.knew.feed.App.getPrefs(App.kt) 
          at com.knew.feed.utils.DistributionChannelUtils$prefs$2.invoke(DistributionChannelUtils.kt:17) 
          at com.knew.feed.utils.DistributionChannelUtils$prefs$2.invoke(DistributionChannelUtils.kt:14) 
          at kotlin.SynchronizedLazyImpl.getValue(LazyJVM.kt:74) 
          at com.knew.feed.utils.DistributionChannelUtils.getPrefs(DistributionChannelUtils.kt) 
          at com.knew.feed.utils.DistributionChannelUtils.access$getPrefs$p(DistributionChannelUtils.kt:14) 
          at com.knew.feed.utils.DistributionChannelUtils$distributionChannel$2.invoke(DistributionChannelUtils.kt:22) 
          at com.knew.feed.utils.DistributionChannelUtils$distributionChannel$2.invoke(DistributionChannelUtils.kt:14) 
          at kotlin.SynchronizedLazyImpl.getValue(LazyJVM.kt:74) 
          at com.knew.feed.utils.DistributionChannelUtils.getDistributionChannel(DistributionChannelUtils.kt) 
          at com.knew.feed.utils.DistributionChannelUtils.isDevelopment(DistributionChannelUtils.kt:29) 
          at com.knew.feed.utils.LoggerUtilsKt.initLogger(LoggerUtils.kt:35) 
          at com.knew.feed.App.init(App.kt:85) 
          at com.knew.feed.App.onCreate(App.kt:57) 
          at android.app.Instrumentation.callApplicationOnCreate(Instrumentation.java:1018) 
          at android.app.ActivityThread.handleBindApplication(ActivityThread.java:5019) 
          at android.app.ActivityThread.access$1800(ActivityThread.java:178) 
          at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1578) 
          at android.os.Handler.dispatchMessage(Handler.java:111) 
          at android.os.Looper.loop(Looper.java:207) 
          at android.app.ActivityThread.main(ActivityThread.java:5845) 
          at java.lang.reflect.Method.invoke(Native Method) 
          at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:907) 
          at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:768) 
      	Suppressed: java.lang.ClassNotFoundException: net.grandcentrix.tray.R$string
          at java.lang.Class.classForName(Native Method)
          at java.lang.BootClassLoader.findClass(ClassLoader.java:781)
          at java.lang.BootClassLoader.loadClass(ClassLoader.java:841)
          at java.lang.ClassLoader.loadClass(ClassLoader.java:504)
          		... 37 more
       Caused by: java.lang.NoClassDefFoundError: Class not found using the boot class loader; no stack trace available
    
    

    Description (Just a place with additional information, more == better)

    Reviewed by kennir at 2020-03-05 07:59
  • 16. Accessing Outside of Container App 2.0

    I understand that you recommend using the ContentProvider API externally to access the main program data. Whether you will access it in a tray compatible way, and whether you can distinguish between internal access and external access by applying package names. The purpose of compatibility is to access the data of the main program when the component application is used as a stand-alone application, so that it can run directly without modifying the code, which will greatly improve the efficiency of component development. Would you consider it?

    Reviewed by cdslily at 2019-03-21 07:50
A lightweight library for config and using SharedPreferences

preferences-helper SharePreferences is very popular with any project and all most all project has SharePreferences for saving data. This library will

May 8, 2021
Perfect replacement for startActivityForResult(), based on the Activity Result API.
Perfect replacement for startActivityForResult(), based on the Activity Result API.

ActivityResultLauncher English | 中文 Activity Result API is an official tool used to replace the method of startActivityForResult() and onActivityResul

Aug 9, 2022
A support library for VectorDrawable and AnimatedVectorDrawable classes introduced in Lollipop

vector-compat A support library for VectorDrawable and AnimatedVectorDrawable introduced in Lollipop with fully backwards compatible tint support (api

Jul 22, 2022
A library to quickly and easily enable multiple monitoring & support platforms for your mobile apps

You have a small team. Setting up crash reporting tools, event tracking tools, and log management services is not what you want to spend your hours do

Aug 8, 2022
Long-term support releases of Birday updated with the latest translations
Long-term support releases of Birday updated with the latest translations

Note: this document is still a work in progress. Birday LTS This repository is based on the 2.1.0 release of https://github.com/m-i-n-a-r/birday. You

May 16, 2022
Android Shared preference wrapper than encrypts the values of Shared Preferences. It's not bullet proof security but rather a quick win for incrementally making your android app more secure.
Android Shared preference wrapper than encrypts the values of Shared Preferences. It's not bullet proof security but rather a quick win for incrementally making your android app more secure.

Secure-preferences - Deprecated Please use EncryptedSharedPreferences from androidx.security in preferenced to secure-preference. (There are no active

Aug 8, 2022
Android library which makes it easy to handle the different obstacles while calling an API (Web Service) in Android App.
Android library which makes it  easy to handle the different obstacles while calling an API (Web Service) in Android App.

API Calling Flow API Calling Flow is a Android library which can help you to simplify handling different conditions while calling an API (Web Service)

Nov 9, 2021
Gesture detector framework for multitouch handling on Android, based on Android's ScaleGestureDetector

Android Gesture Detectors Framework Introduction Since I was amazed Android has a ScaleGestureDetector since API level 8 but (still) no such thing as

Jul 28, 2022
Use Android as Rubber Ducky against another Android device
Use Android as Rubber Ducky against another Android device

Use Android as Rubber Ducky against another Android device

Aug 13, 2022
Android Utilities Library build in kotlin Provide user 100 of pre defined method to create advanced native android app.

Android Utilities Library build in kotlin Provide user 100 of pre defined method to create advanced native android app.

May 12, 2022
A util for setting status bar style on Android App.
A util for setting status bar style on Android App.

StatusBarUtil A util for setting status bar style on Android App. It can work above API 19(KitKat 4.4). 中文版点我 Sample Download StatusBarUtil-Demo Chang

Aug 4, 2022
A logger with a small, extensible API which provides utility on top of Android's normal Log class.
A logger with a small, extensible API which provides utility on top of Android's normal Log class.

This is a logger with a small, extensible API which provides utility on top of Android's normal Log class. I copy this class into all the little apps

Aug 17, 2022
Java implementation of a Disk-based LRU cache which specifically targets Android compatibility.

Disk LRU Cache A cache that uses a bounded amount of space on a filesystem. Each cache entry has a string key and a fixed number of values. Each key m

Aug 14, 2022
a simple cache for android and java

ASimpleCache ASimpleCache 是一个为android制定的 轻量级的 开源缓存框架。轻量到只有一个java文件(由十几个类精简而来)。 1、它可以缓存什么东西? 普通的字符串、JsonObject、JsonArray、Bitmap、Drawable、序列化的java对象,和 b

Aug 10, 2022
gRPC and protocol buffers for Android, Kotlin, and Java.

Wire “A man got to have a code!” - Omar Little See the project website for documentation and APIs. As our teams and programs grow, the variety and vol

Aug 13, 2022
✔️ Secure, simple key-value storage for Android
✔️ Secure, simple key-value storage for Android

Hawk 2.0 Secure, simple key-value storage for android Important Note This version has no backward compatibility with Hawk 1+ versions. If you still wa

Aug 14, 2022
A robust native library loader for Android.
A robust native library loader for Android.

ReLinker A robust native library loader for Android. More information can be found in our blog post Min SDK: 9 JavaDoc Overview The Android PackageMan

Aug 11, 2022
A lightning fast, transactional, file-based FIFO for Android and Java.

Tape by Square, Inc. Tape is a collection of queue-related classes for Android and Java. QueueFile is a lightning-fast, transactional, file-based FIFO

Aug 8, 2022
Joda-Time library with Android specialization

joda-time-android This library is a version of Joda-Time built with Android in mind. Why Joda-Time? Android has built-in date and time handling - why

Aug 15, 2022