2016-11-02 52 views
1

我試圖編寫一些代碼來獲得脫機的一些內容,可以稍後觀看。此內容是由jsons,圖像,視頻和PDF文件:領域實例和多線程

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

所以基本上我的問題是,我異步網絡調用和境界數據庫更新之間切換,並且我無法弄清楚如何構建它做好。

我抓住上面的JSON必須存儲它的領域,然後我打電話給每個元素的第二條路線得到DetailedElement並存儲它。當元素包含可下載的文檔時,我必須下載它並將其路徑添加爲DetailedElement的成員。事情是,我無法讓我的線程正確。

我idealy喜歡這個簽名的方法:

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

我的第一個步驟應該是確保它在非UI彎針線推出:我不知道該怎麼辦呢

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

+0

每個線程都必須有它自己的Realm實例。你應該在完成之後立即關閉它,但不要以前。 –

+0

例如你的onResponseReceived需要它自己的Realm實例,因爲它在一個單獨的線程上運行 –

+0

這就是爲什麼我使用一個處理程序,我認爲「Realm.getDefaultInstance()」非常重要,我們應該嘗試保留對它的引用和它的線程。像這樣的一個操作會導致數十個異步網絡調用和更新數據庫。我只想在一切結束時關閉它。 –

回答

2

這是正確的做法嗎?保留其線程的領域引用和處理程序引用。

沒有必要。 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 Realm,或this 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()。我可以看到你沒有這樣做。

+0

您好EpicPandaForce先生,我正在重構一些更類似於您之前幫助過我的代碼。更新我的問題 –

0

那麼,在這種情況下,最好的解決方案 - 使用功能反應式編程(Android的rxJava庫)。通過這樣做,您可以創建觸發器,每次更新數據(通過使用PublishSubject)發出事件並輕鬆處理併發。 如果這樣做,請使用async ..方法,因爲通過重定向rx'pipeline'中的事件流來完成同步。 如果這些對象是從另一個線程創建的,則領域不能從另一個線程訪問對象,因此,對於多線程環境,分配新領域實例,執行事務和發佈實例。這不是強制性的,你仍然可以使用異步方法,但它會使交互更加全面。

+0

我會在稍後添加RxJava :)。不想混合我不喜歡的技術人員。 –

+0

作爲您評論的其餘部分,您將加入Tim Castelijns,我將在所有回調中創建一個領域實例,並查看其行爲。 –

+0

引入額外的複雜性與RxJava不會解決這個 – EpicPandaForce