nature1729 nature1729 - 1 month ago 25
Android Question

In Realm in Android is it possible to do Inter Process Communication?

Is it possible to do inter-process communication in Realm (Java, Android). So, say, if we update value of a field of RealmObject from one Process, will we get trigger on RealmChangeListener (corresponding to the mentioned RealmObject) in another process. I assume Realm's version is at least 2.0.2.

Answer

Realm 2.0.0 and beyond supports interprocess notifications for non-encrypted Realms, but there are some edge cases that aren't covered yet (multi-process migrations, primarily).



I've put together a sample project and it works for me.

In here, the RecyclerView shows the dog inserted from a broadcast receiver that belongs to a remote process.

10-11 20:53:46.340 7302-7302/com.zhuinden.realm_multiprocess_example I/MainActivity: Activity in Process [7302]
10-11 20:53:46.344 7302-7302/com.zhuinden.realm_multiprocess_example D/RealmManager: Incrementing Activity Count [0]: opening Realm.
10-11 20:53:46.352 7302-7302/com.zhuinden.realm_multiprocess_example D/RealmManager: Increment: Count [1]

10-11 20:54:01.352 7560-7560/com.zhuinden.realm_multiprocess_example:remote D/AlarmReceiver: Alarm received in process [7560]
10-11 20:54:01.352 7560-8705/com.zhuinden.realm_multiprocess_example:remote I/AlarmReceiver: Inserting DOGE [Juice]

Complete source code:

public class RealmManager {
    private static final String TAG = "RealmManager";

    static Realm realm;

    static RealmConfiguration realmConfiguration;

    public static RealmConfiguration buildDefaultRealmConfiguration() {
        return new RealmConfiguration.Builder()
                .deleteRealmIfMigrationNeeded()
                .build();
    }

    public static void initializeRealmConfig(Context appContext) {
        if(realmConfiguration == null) {
            Realm.init(appContext);
            Log.d(TAG, "Initializing Realm configuration.");
            setRealmConfiguration(buildDefaultRealmConfiguration());
        }
    }

    public static void setRealmConfiguration(RealmConfiguration realmConfiguration) {
        RealmManager.realmConfiguration = realmConfiguration;
        Realm.setDefaultConfiguration(realmConfiguration);
    }

    private static int activityCount = 0;

    public static Realm getRealm() {
        return realm;
    }

    public static void incrementCount() {
        if(activityCount == 0) {
            if(realm != null) {
                if(!realm.isClosed()) {
                    Log.w(TAG, "Unexpected open Realm found.");
                    realm.close();
                }
            }
            Log.d(TAG, "Incrementing Activity Count [0]: opening Realm.");
            realm = Realm.getDefaultInstance();
        }
        activityCount++;
        Log.d(TAG, "Increment: Count [" + activityCount + "]");
    }

    public static void decrementCount() {
        activityCount--;
        Log.d(TAG, "Decrement: Count [" + activityCount + "]");
        if(activityCount <= 0) {
            Log.d(TAG, "Decrementing Activity Count: closing Realm.");
            activityCount = 0;
            realm.close();
            if(Realm.compactRealm(realmConfiguration)) {
                Log.d(TAG, "Realm compacted successfully.");
            }
            realm = null;
        }
    }
}

public class MainActivity
        extends AppCompatActivity {
    private static final String TAG = "MainActivity";

    @BindView(R.id.recyclerview)
    RecyclerView recyclerView;

    Realm realm;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        RealmManager.initializeRealmConfig(getApplicationContext());
        Log.i(TAG, "Activity in Process [" + android.os.Process.myPid() + "]");
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);
        RealmManager.incrementCount(); //lazy for retained fragment
        realm = RealmManager.getRealm();

        recyclerView.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false));
        recyclerView.setAdapter(new RealmRecyclerViewAdapter<Dog, DogViewHolder>(this, realm.where(Dog.class).findAllAsync(), true) {
            @Override
            public DogViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
                TextView textView = new TextView(MainActivity.this);
                RecyclerView.LayoutParams layoutParams = new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                        ViewGroup.LayoutParams.WRAP_CONTENT);
                textView.setLayoutParams(layoutParams);
                return new DogViewHolder(textView);
            }

            @Override
            public void onBindViewHolder(DogViewHolder holder, int position) {
                holder.bind(getData().get(position));
            }
        });

        AlarmManager alarmManager = (AlarmManager)getSystemService(ALARM_SERVICE);
        Calendar calendar = Calendar.getInstance();
        calendar.add(Calendar.SECOND, 15);
        Intent intent = new Intent(this, AlarmReceiver.class);
        PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
        addNotification(alarmManager, pendingIntent, calendar.getTimeInMillis());
    }

    public void addNotification(AlarmManager alarmManager, PendingIntent pendingIntent, long timeOfNotification) {
        if(Build.VERSION.SDK_INT < 19) {
            alarmManager.set(AlarmManager.RTC_WAKEUP, timeOfNotification, pendingIntent);
        } else if(Build.VERSION.SDK_INT >= 19 && Build.VERSION.SDK_INT < 23) {
            alarmManager.setExact(AlarmManager.RTC_WAKEUP, timeOfNotification, pendingIntent);
        } else if(Build.VERSION.SDK_INT >= 23) {
            alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, timeOfNotification, pendingIntent);
        }
    }

    @Override
    protected void onDestroy() {
        RealmManager.decrementCount();
        super.onDestroy();
    }
}

public class DogViewHolder extends RecyclerView.ViewHolder {

    TextView textView;

    public DogViewHolder(View itemView) {
        super(itemView);
        textView = (TextView) itemView;
    }

    public void bind(Dog dog) {
        textView.setText(dog.getName());
    }
}

public enum DogNames {
    Jack,
    Jill,
    Joe,
    Jarble,
    Juice,
    Jen,
    Jim,
    Munch,
    Slurp,
    Boop,
    Largo,
    Boson,
    Pete,
    Boner,
    Derp,
    Roger,
    Bunbun,
    Twix,
    Dog
}

public class Dog extends RealmObject {
    @PrimaryKey
    private long id;

    private String name;

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

public class AlarmReceiver extends BroadcastReceiver {
    private static final String TAG = "AlarmReceiver";

    RealmConfiguration realmConfiguration;

    Executor executor;

    @Override
    @SuppressLint("NewApi")
    public void onReceive(Context context, Intent intent) {
        Log.d(TAG, "Alarm received in process [" + android.os.Process.myPid() + "]");
        Realm.init(context);
        if(realmConfiguration == null) {
            realmConfiguration = RealmManager.buildDefaultRealmConfiguration();
        }
        if(executor == null) {
            executor = Executors.newSingleThreadExecutor();
        }
        executor.execute(() -> {
            try(Realm r = Realm.getInstance(realmConfiguration)) {
                if(Looper.getMainLooper() == Looper.myLooper()) {
                    Log.i(TAG, "UI thread transaction: this should be fixed!");
                }
                r.executeTransaction(realm -> {
                    Dog dog = new Dog();
                    long id = realm.where(Dog.class).findAll().size()+1;
                    dog.setId(id);
                    dog.setName(DogNames.values()[((Long)id).intValue() % DogNames.values().length].name());
                    realm.insert(dog);
                    Log.i(TAG, "Inserting DOGE [" + dog.getName() + "]");
                });
            }
        });
    }
}

<?xml version="1.0" encoding="utf-8"?>
<manifest package="com.zhuinden.realm_multiprocess_example"
          xmlns:android="http://schemas.android.com/apk/res/android">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>

                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
        <receiver
            android:name=".AlarmReceiver"
            android:process=":remote"/>
    </application>

</manifest>

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    android:id="@+id/activity_main"
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.zhuinden.realm_multiprocess_example.MainActivity">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/recyclerview"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
</RelativeLayout>

// Top-level build file where you can add configuration options common to all sub-projects/modules.

buildscript {
    repositories {
        mavenCentral()
        jcenter()
        maven {url "https://clojars.org/repo/"}
        maven { url "https://jitpack.io" }
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:2.2.0'
        classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
        classpath "io.realm:realm-gradle-plugin:2.0.2"
        classpath 'me.tatarka:gradle-retrolambda:3.2.5'
        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

allprojects {
    repositories {
        mavenCentral()
        jcenter()
        maven {url "https://clojars.org/repo/"}
        maven { url "https://jitpack.io" }
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}

apply plugin: 'com.android.application'
apply plugin: 'com.neenbedankt.android-apt'
apply plugin: 'me.tatarka.retrolambda'
apply plugin: 'realm-android'

android {
    compileSdkVersion 24
    buildToolsVersion "24.0.2"
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
    defaultConfig {
        applicationId "com.zhuinden.realm_multiprocess_example"
        minSdkVersion 14
        targetSdkVersion 24
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
    packagingOptions {
        // Exclude file to avoid
        // Error: Duplicate files during packaging of APK
        exclude 'META-INF/DEPENDENCIES'
        exclude 'META-INF/LICENSE'
        exclude 'META-INF/LICENSE.txt'
        exclude 'META-INF/license.txt'
        exclude 'META-INF/NOTICE'
        exclude 'META-INF/NOTICE.txt'
        exclude 'META-INF/notice.txt'
        exclude 'META-INF/ASL2.0'
        exclude 'META-INF/services/javax.annotation.processing.Processor'
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
        exclude group: 'com.android.support', module: 'support-annotations'
    })
    compile 'com.android.support:appcompat-v7:24.2.1'
    testCompile 'junit:junit:4.12'
    compile "com.android.support:recyclerview-v7:24.2.1"

    apt 'com.jakewharton:butterknife-compiler:8.4.0'
    compile 'com.jakewharton:butterknife:8.4.0'

    apt 'dk.ilios:realmfieldnameshelper:1.1.0'
    compile 'io.realm:android-adapters:1.3.0'
}
Comments