【问题标题】:Realm instances and multithreading领域实例和多线程
【发布时间】:2016-11-02 09:22:10
【问题描述】:

我正在尝试编写一些代码以使某些可以稍后观看的内容脱机。此内容由 json、图像、视频和 pdf 组成:

{
  "elements": [
    {
      "id":"3b4c4f3da8bf9d1527010c5242e037b7",
      "type":"media"
    },
    ...
  ],
  "id":"58088318ef0b4832f6c0e70b",
  "content": "Hello World"
}

所以基本上我的问题是我在异步网络调用和领域数据库更新之间切换,我不知道如何构建它。

我首先获取上述 Json 必须将其存储在领域中,然后我为每个元素调用第二条路由以获取 DetailedElement 并将其存储。当一个元素包含一个可下载的文档时,我必须下载它并将其路径添加为DetailedElement 的成员。问题是,我的线程不正确。

理想情况下,我想要一个带有这个签名的方法:

FooBar.prefetch(Context ctx, String id, Callback cb)

我的第一步应该是确保它在非 UI Looper 线程中启动:我不知道该怎么做

public static void Bar(final Context ctx, final String id) {
    new Thread(new Runnable() {
        @Override
        public void run() {
            Looper.prepare();
            final Realm realm = Realm.getDefaultInstance();
            final Handler handler = new Handler();

            MainThingToDownload mainThingToDownload = realm.where(MainThingToDownload.class).equalTo("id", id).findFirst();
            if (mainThingToDownload != null) {
                processMain(mainThingToDownload, realm, handler);
            } else {
                Api.getInstance().backendRepresentation.getMainThing(id).enqueue(new CustomRetrofitCallBack<>(null) {
                    @Override
                    public void onResponseReceived(final MainThingToDownload mainThingToDownload) {

                        handler.post(new Runnable() {
                            @Override
                            public void run() {
                                realm.executeTransactionAsync(new Realm.Transaction() {
                                    @Override
                                    public void execute(Realm realm) {
                                        realm.copyToRealmOrUpdate(mainThingToDownload);
                                        processMain(mainThingToDownload, realm, handler);
                                    }
                                });
                            }
                        });
                    }
                });
            }
        }
    });
}

这是正确的方法吗?保留其线程的领域引用和处理程序引用。然后我总是在我的网络回调中做类似的事情:

    handler.post(new Runnable() {
        @Override
        public void run() {
            realm.executeTransactionAsync(new Realm.Transaction() {
                @Override
                public void execute(Realm realm) {
                    //... add downloaded object to realm or modifies one     
                }
            });     
        }
    });

我的代码有很多不同的错误。大多数情况下,我不会在应该使用 Realm 的线程上使用 Realm,有时我会使用已经关闭的领域实例。

这是我应该做的吗? 谢谢,


我做了什么

一个Program 包含一个Module 列表,每个Module 包含一个Element 列表。

ProgramOfflineManager

public class ProgramOfflineManager extends AbstractOfflineManager {

    private final static String TAG = "ModuleOfflineManager";

    public ProgramOfflineManager(Context ctx, String id, ErrorDisplayerInterface errorDisplayerInterface) {
        super(ctx, id, errorDisplayerInterface);
    }

    @Override
    protected void makeOffline() throws IOException {
        super.makeOffline();
        ProgramOfflineManager.makeOffline(id, ctx);
    }

    @Override
    protected void removeOffline() {
        super.removeOffline();
        ProgramOfflineManager.removeOffline(id, ctx);
    }

    public static void removeOffline(String programId, Context ctx) {

        Log.i(TAG, "to be deleted");
        Realm realm = null;
        try {
            realm = Realm.getDefaultInstance();
            //To be deleted
            final Program program = realm.where(Program.class).equalTo("id", programId).findFirst();
            Log.i("renaud", "courseDetailed matching is is not null : (detailed!=null)=>" + (program != null));
            if (program != null) {
                for (Module module : program.getModules()) {
                    CourseDetailed courseDetailed = realm.where(CourseDetailed.class).equalTo("id", module.getCourse()).equalTo("downloaded", true).findFirst();
                    if (courseDetailed != null) {
                        ModuleOfflineManager.removeOffline(module.getCourse(), ctx);
                    }
                }
                realm.executeTransaction(new Realm.Transaction() {
                    @Override
                    public void execute(Realm realm) {
                        program.deleteFromRealm();
                        Log.i(TAG, "course has been deleted");
                    }
                });
            }
        } finally {
            if (realm != null) {
                realm.close();
            }
            realm = null;
        }
    }

    public static void makeOffline(final String programId, final Context ctx) throws IOException {
        Api.Service360Interface backend = Api.getInstance().backend;

        Response<Program> programResponse = backend.getProgram(programId).execute();

        if (programResponse.isSuccessful()) {
            final Program program = programResponse.body();

            Realm realm = null;
            try {
                realm = Realm.getDefaultInstance();

                realm.executeTransaction(new Realm.Transaction() {
                    @Override
                    public void execute(Realm realm) {
                        program.setDownloaded(true);
                        realm.copyToRealmOrUpdate(program);
                    }
                });

                for (final Module module : program.getModules()) {
                    long count = realm.where(CourseDetailed.class).equalTo("id", module.getCourse()).equalTo("downloaded", true).count();
                    if (count == 0) {
                        ModuleOfflineManager.makeOffline(module.getCourse(), ctx);
                    }
                }


            } finally {
                if (realm != null) {
                    realm.close();
                }
                realm = null;
            }
        }

    }


}

ModuleOfflineManager

public class ModuleOfflineManager extends AbstractOfflineManager {

    private final static String TAG = "ModuleOfflineManager";

    public ModuleOfflineManager(Context ctx, String id, ErrorDisplayerInterface errorDisplayerInterface) {
        super(ctx, id, errorDisplayerInterface);
    }

    @Override
    protected void makeOffline() throws IOException {
        super.makeOffline();
        ModuleOfflineManager.makeOffline(id, ctx);
    }

    @Override
    protected void removeOffline() {
        super.removeOffline();
        ModuleOfflineManager.removeOffline(id, ctx);
    }

    public static void removeOffline(String courseId, Context ctx) {

        Log.i(TAG, "to be deleted");
        Realm realm = null;
        try {
            realm = Realm.getDefaultInstance();
            //To be deleted
            final CourseDetailed detailed = realm.where(CourseDetailed.class).equalTo("id", courseId).findFirst();
            Log.i("renaud", "courseDetailed matching is is not null : (detailed!=null)=>" + (detailed != null));
            if (detailed != null) {
                for (Element element : detailed.getElements()) {
                    Log.i(TAG, "next Element to suppress : " + element.getId());
                    final CourseElement courseElement = realm.where(CourseElement.class).equalTo("id", element.getId()).findFirst();
                    if (courseElement.getCollection() != null && courseElement.getCollection() == PostCollectionType.MEDIAS) {
                        Log.i(TAG, "it's a Media, erasing from db");
                        MediaDownloadUtils.eraseMedia(ctx, courseElement, realm);
                    }
                    realm.executeTransaction(new Realm.Transaction() {
                        @Override
                        public void execute(Realm realm) {
                            courseElement.deleteFromRealm();
                            Log.i(TAG, "element has been deleted");
                        }
                    });

                }
                realm.executeTransaction(new Realm.Transaction() {
                    @Override
                    public void execute(Realm realm) {
                        detailed.deleteFromRealm();
                        Log.i(TAG, "course has been deleted");
                    }
                });
            }
        } finally {
            if (realm != null) {
                realm.close();
            }
            realm = null;
        }
    }

    public static void makeOffline(final String courseId, final Context ctx) throws IOException {
        Api.Service360Interface backend = Api.getInstance().backend;

        Response<CourseDetailed> response = backend.getCourse(courseId).execute();
        if (response.isSuccessful()) {
            final CourseDetailed courseDetailedResponse = response.body();

            Realm realm = null;
            try {
                realm = Realm.getDefaultInstance();

                realm.executeTransaction(new Realm.Transaction() {
                    @Override
                    public void execute(Realm realm) {
                        courseDetailedResponse.saveEnums();
                        courseDetailedResponse.setDownloaded(true);
                        realm.copyToRealmOrUpdate(courseDetailedResponse);
                    }
                });

                for (final Element element : courseDetailedResponse.getElements()) {

                    Call<CourseElement> call = Api.getInstance().getCourseElement(element.getCollection(), element.getId(), courseId);
                    Response<CourseElement> courseElementResponse = call.execute();
                    if (courseElementResponse.isSuccessful()) {
                        final CourseElement courseElement = courseElementResponse.body();
                        realm.executeTransaction(new Realm.Transaction() {
                            @Override
                            public void execute(Realm realm) {
                                courseElement.setCourseElementType(CourseElementTypes.valueOf(element.getCollection()));
                                courseElement.saveEnums();
                                courseElement.setDownloaded(true);
                                realm.copyToRealmOrUpdate(courseElement);

                                MediaDownloadUtils.prefechMedia(ctx, courseElement);
                            }
                        });
                    }
                }
            } finally {
                if (realm != null) {
                    realm.close();
                }
                realm = null;
            }
        }
    }
}

AbstractOfflineManager

public abstract class AbstractOfflineManager implements OfflineManagerInterface {

    private final static String TAG = "AbstractOfflineManager";

    final protected Context ctx;
    final protected String id;
    final protected ErrorDisplayerInterface errorDisplayerInterface;

    protected boolean status;

    public AbstractOfflineManager(Context ctx, String id, ErrorDisplayerInterface errorDisplayerInterface) {
        this.ctx = ctx;
        this.id = id;
        this.errorDisplayerInterface = errorDisplayerInterface;
    }

    protected void makeOffline() throws IOException {
        //implementations in children
    }

    protected void removeOffline() {
        //implementations in children
    }

    @Override
    public CompoundButton.OnCheckedChangeListener getClickListener() {
        return new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton buttonView, final boolean isChecked) {

                Log.i(TAG, "clic ! isChecked : " + isChecked);

                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        Looper.prepare();
                        status = isChecked;
                        if (isChecked) {
                            try {
                                makeOffline();
                            } catch (IOException e) {
                                e.printStackTrace();
                                errorDisplayerInterface.popError(null, e);
                            }
                        } else {
                            removeOffline();
                        }
                    }
                }).start();
            }
        };
    } }

现在我将创建一个ElementOfflineManager

【问题讨论】:

  • 每个线程都必须有自己的 Realm 实例。您应该在完成后立即关闭它,而不是之前。
  • 例如你的 onResponseReceived 需要它自己的 Realm 实例,因为它运行在一个单独的线程上
  • 这就是我使用处理程序的原因,我认为“Realm.getDefaultInstance()”很重,我们应该尝试保留对它及其线程的引用。像这样的一项操作会导致数十次异步网络调用和尽可能多的数据库更新。我只是想在一切都结束后关闭它。
  • 考虑将toBeDeleted 之后的所有逻辑移动到单个事务中,目前这在 Realm 上相当繁重 - 你不需要Looper.prepare()
  • 如果没有 Looper.prepare(),我不会有例外吗?我只需要它来获取实时更新吗?我认为我不能将逻辑移动到单个事务中,因为要构建一个 ElementOfflineManager 来处理它自己的事务。

标签: android multithreading realm


【解决方案1】:

这是正确的方法吗?保留其线程的领域引用和处理程序引用。

没有必要。只要您关闭实例,Realm 就可以在任何后台线程上使用。

Realm 已经管理了 UI 线程及其自动更新的处理程序,因此您无需自己手动执行此操作。


你把一个相当简单的问题复杂化了。你应该让 Retrofit 在后台线程上同步执行,而不是在 UI 线程上。

像这样:

protected ExecutorService executor = Executors.newSingleThreadExecutor();

//...


public static void bar(final Context ctx, final String id) {
    MainThingToDownload mainThingToDownload = mRealm.where(MainThingToDownload.class)
                                                   .equalTo("id", id)
                                                   .findFirst(); 
                                       // assuming there is a UI thread Realm
    if (mainThingToDownload != null) {
        processMain(mainThingToDownload);
    } else {
        executor.execute(new Runnable() {
            @Override
            public void run() {
                Response<MainThingToDownload> response = Api.getInstance()
                                                            .backendRepresentation
                                                            .getMainThing(id)
                                                            .execute();
                MainThingToDownload mainThingToDownload = response.body();
                Realm realm = null;
                try {
                    realm = Realm.getDefaultInstance();
                    realm.executeTransaction(new Realm.Transaction() {
                        @Override
                        public void execute(Realm realm) {
                            realm.insertOrUpdate(mainThingToDownload);
                        }
                    }
                } finally {
                    if(realm != null) {
                        realm.close();
                    }
                }
            }
       });
}


如需更多一般信息,请查看the guide on the basics of Realmthis very simple gist

(这是要点:

public class NewsActivity extends AppCompatActivity {
  // ...
  private RealmChangeListener<RealmResults<NewsPost>> realmChangeListener;
  private RealmResults<NewsPost> listenerSet;
  private long postId;

  private Realm realm;

  @Override
  protected void onCreate(Bundle bundle) {
    super.onCreate(bundle);
    Injector.get().inject(this);
    postId = getIntent().getLongExtra("postId");
    setContentView(R.layout.activity_news);
    ButterKnife.bind(this);

    realm = RealmManager.getRealm();

    realmChangeListener = new RealmChangeListener<RealmResults<NewsPost>>() {
      @Override
      public void onChange(RealmResults<NewsPost> element) {
        NewsPost newsPost = realm.where(NewsPost.class)
                                    .equalTo(NewsPostFields.ID, postId)
                                    .findFirst();
        if(newsPost != null) { // if news post was downloaded on background thread, initalize view
          initView(newsPost);
        }
      }
    };
    listenerSet = realm.where(NewsPost.class)
                       .findAll();
    listenerSet.addChangeListener(realmChangeListener); // listen to changes in `NewsPost` table

    NewsPost newsPost = realm.where(NewsPost.class)
                                .equalTo(NewsPostFields.ID, postId)
                                .findFirst();
    if(newsPost == null) {
      // download news post if not available
      getNewsPostInteractor.getNewsPost(postId);
    } else {
      initView(newsPost);
    }
  }

  private void initView(NewsPost newsPost) {
    // set views
  }
}

)


哦,还有you should close any Realm instance that you open with getDefaultInstance()。我可以看到你没有这样做。

【讨论】:

  • 您好 EpicPandaForce 先生,我正在重构以获得更类似于您之前帮助我编写的代码。更新我的问题
【解决方案2】:

好吧,在这种情况下,最好的解决方案是使用函数式反应式编程(用于 android 的 rxJava 库)。通过这样做,您可以创建触发器,每次更新数据时发出事件(通过使用 PublishSubject)并轻松处理并发。 如果这样做,请避免使用 async.. 方法,因为同步将通过重定向 rx 'pipelines' 内的事件流来完成。 如果这些对象是从另一个线程创建的,则 Realm 无法访问来自另一个线程的对象,因此,对于多线程环境,分配新的 Realm 实例,执行事务并释放实例。这不是强制性的,你仍然可以使用 async 方法,但它会使交互更加全面。

【讨论】:

  • 我稍后会添加 RxJava :)。不想混合我不擅长的技术。
  • 作为您的其余评论,您将加入 Tim Castelijns,我将在所有回调中实例化一个领域实例,并查看它的行为。
  • 用 RxJava 引入额外的复杂性并不能解决这个问题
  • 其实会的。这将有助于避免这种乏味的回调,并有助于操作时间。
  • 不会,因为自 2.0.0 以来,在后台线程上提交事务后,主线程没有立即更新。如果他没有在线程之间不必要地跳转,他就不会有回调地狱,而是​​按预期使用RealmChangeListener
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-01-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多