如何修复 'Cannot access database on the main thread since it may potentially lock the UI for a long period of time.'

How to fix 'Cannot access database on the main thread since it may potentially lock the UI for a long period of time.'

我正在尝试创建一个 Procduct 变量,但我需要从现有的 java class 调用 suspend fun,但是当我调用创建它所需的方法:

    Process: com.some.app.debug, PID: 6758
    java.lang.IllegalStateException: Cannot access database on the main thread since it may potentially lock the UI for a long period of time.
        at androidx.room.RoomDatabase.assertNotMainThread(RoomDatabase.java:209)
        at androidx.room.RoomDatabase.query(RoomDatabase.java:237)
        at com.some.app.thread.local.ProductDAO_Impl.getProductByStyleColor(ProductDAO_Impl.java:513)
        at com.some.app.thread.local.ThreadPersister.getProductByStyleColor(ThreadPersister.kt:315)
        at com.some.app.user.orders.OrderDetailsFragment.loadDetails(OrderDetailsFragment.java:189)
        at com.some.app.user.orders.OrderDetailsFragment.onCreateView(OrderDetailsFragment.java:131)
        at androidx.fragment.app.Fragment.performCreateView(Fragment.java:2439)
        at androidx.fragment.app.FragmentManagerImpl.moveToState(FragmentManager.java:1460)
        at androidx.fragment.app.FragmentManagerImpl.moveFragmentToExpectedState(FragmentManager.java:1784)
        at androidx.fragment.app.FragmentManagerImpl.moveToState(FragmentManager.java:1852)
        at androidx.fragment.app.BackStackRecord.executeOps(BackStackRecord.java:802)
        at androidx.fragment.app.FragmentManagerImpl.executeOps(FragmentManager.java:2625)
        at androidx.fragment.app.FragmentManagerImpl.executeOpsTogether(FragmentManager.java:2411)
        at androidx.fragment.app.FragmentManagerImpl.removeRedundantOperationsAndExecute(FragmentManager.java:2366)
        at androidx.fragment.app.FragmentManagerImpl.execPendingActions(FragmentManager.java:2273)
        at androidx.fragment.app.FragmentManagerImpl.run(FragmentManager.java:733)
        at android.os.Handler.handleCallback(Handler.java:873)
        at android.os.Handler.dispatchMessage(Handler.java:99)
        at android.os.Looper.loop(Looper.java:193)
        at android.app.ActivityThread.main(ActivityThread.java:6718)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)

我正在尝试从 OrderDetailsFragment.java class 调用方法 getProductByStyleColorSuspended()。方法在ThreadPersister.kt

这里是 classes:

public class OrderDetailsFragment extends BaseToolbarFragment {

  @BindView(R.id.item_order_imageview)
  protected ImageView mHeaderItemImageView;
  @BindView(R.id.item_order_progressbar)
  protected ProgressBar mLoadingProgressBar;
  @BindView(R.id.item_order_title_textview)
  protected TextView mItemTitleTextView;
  @BindView(R.id.item_order_status_textview)
  protected TextView mItemStatusTextView;
  @BindView(R.id.fragment_order_detail_color_value)
  protected TextView mColorTextView;
  @BindView(R.id.fragment_order_detail_size_value)
  protected TextView mSizeTextView;
  @BindView(R.id.fragment_order_detail_order_number_value)
  protected TextView mOrderNumberTextView;
  @BindView(R.id.fragment_order_detail_order_date_value)
  protected TextView mOrderDateTextView;
  @BindView(R.id.fragment_order_detail_tax_value)
  protected TextView mTaxTextView;
  @BindView(R.id.fragment_order_detail_shipping_value)
  protected TextView mShippingTextView;
  @BindView(R.id.fragment_order_detail_total_value)
  protected TextView mTotalTextView;
  @BindView(R.id.fragment_order_detail_address_textview)
  protected TextView mAddressTextView;
  @BindView(R.id.fragment_order_detail_top_button)
  protected Button mTopButton;
  @BindView(R.id.fragment_order_detail_bottom_button)
  protected Button mBottomButton;
  @BindView(R.id.fragment_order_detail_relativelayout)
  protected RelativeLayout mRelativeLayout;
  @BindView(R.id.fragment_order_detail_progressview)
  protected ProgressBar mFragmentProgressBar;
  @BindView(R.id.fragment_order_detail_tax_title)
  protected TextView mTaxTextViewTitle;

  @FragmentArgument("order")
  protected SnkrsOrder mOrder;

  public SnkrsOrder getOrder() {
    return mOrder;
  }

  @SuppressLint("RxLeakedSubscription") @Nullable
  @Override
  public View onCreateView(LayoutInflater inflater, ViewGroup container,
      Bundle savedInstanceState) {

    Analytics.with(AnalyticsState.INBOX_ORDER_DETAILS).buildAndSend();

    final View rootView = getActivity() instanceof SnkrsActivity ?
        super.onCreateView(inflater, container, savedInstanceState) :
        inflater.inflate(R.layout.fragment_order_details, container, false);

    ButterKnife.bind(this, rootView);

    if (mOrder != null) {
      if (mOrder.getDetails() == null) {
        mRelativeLayout.setVisibility(View.GONE);
        mFragmentProgressBar.setVisibility(View.VISIBLE);
        mOrderHistoryService.getOrderHistoryDetails(mOrder)
            .subscribe(new SimpleSubscriber<SnkrsOrder>() {
              @Override
              public void onNext(SnkrsOrder order) {
                super.onNext(order);
                order.setSubmittedDate(order.getDetails().getOrder().getSubmittedDate());
                safeRunOnUiThread(() -> loadDetails(order.getDetails().getOrder()));
              }
            });
      } else {
        loadDetails(mOrder.getDetails().getOrder());
      }
    }
    return rootView;
  }

  public void loadDetails(SnkrsOrderDetails.Order detailOrder) {

    mItemTitleTextView.setText(detailOrder.getDisplayName());
    mItemStatusTextView.setText(detailOrder.getLocalizedStatusText());
    mLoadingProgressBar.setVisibility(View.VISIBLE);

    String encodedStyleColor = detailOrder.getEncodedStyleColor();
    if (!TextUtils.isEmpty(encodedStyleColor)) {
      String url = getContext().getString(R.string.style_color_url_format_transparent_bgc,
          encodedStyleColor);
      loadImage(url);
    }

    SnkrsOrderDetails.CommerceItem commerceItem = detailOrder.getCommerceItem();

    if (commerceItem != null) {
      mColorTextView.setText(commerceItem.getColorDescription());
      mSizeTextView.setText(commerceItem.getDisplaySize());
    }

    mOrderNumberTextView.setText(detailOrder.getId());
    SimpleDateFormat fromFormat = new SimpleDateFormat("yyyy-MM-dd", Locale.US);
    try {
      Date date = fromFormat.parse(mOrder.getSubmittedDate());
      mOrderDateTextView.setText(TimeFormatter.format("MM/d/yyyy", date));
    } catch (ParseException e) {
      Timber.e(e, e.getLocalizedMessage());
    }

    SnkrsOrderDetails.PriceInfo priceInfo = detailOrder.getPriceInfo();
    if (priceInfo != null) {
      mTaxTextView.setText(priceInfo.getFormattedTax());
      mShippingTextView.setText(priceInfo.getFormattedShipping());
      mTotalTextView.setText(priceInfo.getFormattedTotal());
      if (mFeedLocalizationService.getCurrentFeedLocale() != null
          && (mFeedLocalizationService.getCurrentFeedLocale().isChina() ||
          mFeedLocalizationService.getCurrentFeedLocale().isJapan())) {
        mTaxTextViewTitle.setVisibility(View.GONE);
        mTaxTextView.setVisibility(View.GONE);
      }
    }

    SnkrsAddress address = detailOrder.getShippingGroups().get(0).getShippingAddress();
    mAddressTextView.setText(address.getMultilineFormattedNameAndAddress());

    if (detailOrder.canBeCancelled()) {
      mBottomButton.setText(R.string.orders_details_cancel_order_button);
      mBottomButton.setOnClickListener(v -> cancelOrder(detailOrder));

      final Product product = threadStore.getThreadRepository()
          .getThreadPersister()
//          .getProductByStyleColor(detailOrder.getStyleColor());
      .getProductByStyleColorSuspended(detailOrder.getStyleColor());


      if (product != null) {
        final DeferredPaymentOrder deferredPaymentOrder =
            mSnkrsDatabaseHelper.getDeferredPaymentOrder(detailOrder.getId());
        if (deferredPaymentOrder != null && deferredPaymentOrder.isWaiting()) {
          mTopButton.setVisibility(View.VISIBLE);
          mTopButton.setText(R.string.cta_go_fund);
          mTopButton.setOnClickListener(
              v -> ((BaseActivity) getActivity()).fundDeferredPaymentOrder(product,
                  AnalyticsAction.ORDER_DETAILS_FUND));
        } else {
          mTopButton.setVisibility(View.GONE);
        }
      } else {
        mTopButton.setVisibility(View.GONE);
      }
    } else if (detailOrder.canTrackAndReturn()) {
      mTopButton.setText(R.string.orders_details_track_order_button);
      mBottomButton.setText(R.string.orders_details_return_order_button);
      mTopButton.setOnClickListener(v -> trackOrder(detailOrder));
      mBottomButton.setOnClickListener(v -> returnOrder(detailOrder));
    } else {
      mTopButton.setVisibility(View.GONE);
      mBottomButton.setVisibility(View.GONE);
    }

    Activity snkrsActivity = getActivity();
    if (snkrsActivity instanceof SnkrsActivity) {
      // For some reason, adding only the bottom bar's padding does not give enough padding. However, doubling it seems to work
      mRelativeLayout.setPadding(0, 0, 0, ((SnkrsActivity) snkrsActivity).getBottomBarHeight() * 2);
    }

    mFragmentProgressBar.setVisibility(View.GONE);
    mRelativeLayout.setVisibility(View.VISIBLE);
  }

  @Override
  public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);
    if (getActivity() instanceof SnkrsActivity) {
      //force hide on inbox in case bottom bar animation messes up and leaves it
      ((SnkrsActivity) getActivity()).clearInboxBadge();
      ((SnkrsActivity) getActivity()).forceBottomBarVisibility(false);
    }
  }

  @Override
  public void onDestroyView() {
    super.onDestroyView();
    if (getActivity() instanceof SnkrsActivity) {
      ((SnkrsActivity) getActivity()).forceBottomBarVisibility(true);
    }
  }

  @NonNull
  @Override
  public String getTitle() {
    return safeGetString(R.string.inbox_orders_title).toUpperCase();
  }

  @Override
  public int getLayoutId() {
    return R.layout.fragment_order_details;
  }

  private void cancelOrder(SnkrsOrderDetails.Order order) {
    Analytics.with(AnalyticsAction.ORDER_CANCEL).buildAndSend();
    String email = mPreferenceStore.getString(R.string.pref_key_email, null);
    ((BaseActivity) getActivity()).goToXxxxSiteForOrderActions(order.getId(), email);
  }

  private void trackOrder(SnkrsOrderDetails.Order order) {
    Analytics.with(AnalyticsAction.ORDER_TRACK).buildAndSend();
    String trackingUrl = order.getShippingGroups().get(0).getTrackingUrl();
    if (!TextUtils.isEmpty(trackingUrl)) {
      ((BaseActivity) getActivity()).goToSite(Uri.parse(trackingUrl));
    }
  }

  private void returnOrder(SnkrsOrderDetails.Order order) {
    Analytics.with(AnalyticsAction.ORDER_RETURN).buildAndSend();
    String email = mPreferenceStore.getString(R.string.pref_key_email, null);
    ((BaseActivity) getActivity()).goToXxxxSiteForOrderActions(order.getId(), email);
  }

  private void loadImage(String url) {

    //noinspection CodeBlock2Expr
    safeRunOnUiThread(() -> {
      ImageUtilities.INSTANCE.displayImage(mHeaderItemImageView, url,
          () -> {
            if (mLoadingProgressBar != null) {
              mLoadingProgressBar.setVisibility(View.INVISIBLE);
            }
          }
      );
    });
  }
}

这是ThreadPersister.ktclass

class ThreadPersister @Inject constructor(val snkrsDB: SnkrsDB) {
  private val config = Builder()
      .setPageSize(10)
      .setInitialLoadSizeHint(20)
      .build()
  private val dataSourceFactory = snkrsDB
      .threadDAO()
      .getPage()
      .mapByPage {
        it.mapToThreads()
            .withCardProducts(snkrsDB)
      }
  val pages = RxPagedListBuilder(
      dataSourceFactory, config
  )
      .buildObservable()

  fun insertThreads(threads: List<Thread>): ThreadDAO {
    val threadDAO = snkrsDB.threadDAO()
    snkrsDB.runInTransaction {
      threads.forEach { thread ->
        insertThread(threadDAO, thread)
      }
    }
    return threadDAO
  }

  fun insertThread(thread: Thread) {
    insertThread(snkrsDB.threadDAO(), thread)
  }

  private fun insertThread(
    threadDAO: ThreadDAO,
    thread: Thread
  ) {
    thread.productId = thread.product?.id;
    threadDAO.insert(thread)
    thread.product?.let { insertProduct(it) }
    thread.cards?.forEach { card -> insertCard(card, thread) }
    thread.relations?.let {
      it.forEach { related ->
        related.threadId = thread.threadId;snkrsDB.relatedThreadsDAO()
          .insert(related)
      }
    }
  }

  fun insertProduct(product: Product) {
    snkrsDB.productDAO()
        .insert(product)
    product.skus?.let {
      it.forEach { sku ->
        sku.productId = product.id; insertSku(sku)
      }

    }
  }

  fun insertSku(vararg sku: Sku) {
    snkrsDB.skuDAO()
        .insert(*sku)
  }

  private fun insertCard(
    card: Card,
    thread: Thread
  ) {
    card.threadId = thread.threadId
    card.images?.let {
      it.forEach { image ->
        image.cardId = card.cardId; snkrsDB.imageDAO()
          .insert(image)
      }
    }
    card.videos?.let {
      it.forEach { video ->
        video.cardId = card.cardId; snkrsDB.videoDAO()
          .insert(video)
      }
    }

    card.product?.let {
      card.productId = it.id
      insertProduct(it)
    }
    snkrsDB.cardDAO()
        .insert(card)
  }

  private fun List<Thread>.withCardProducts(snkrsDB: SnkrsDB): List<Thread> {
    val cards = this.mapNotNull { it.cards }
        .flatten()
    val ids = cards.mapNotNull { it.productId }

    val idGroups = ids.chunked(900)

    val products = idGroups.map {
      snkrsDB.productDAO()
          .getProductsById(it)
    }
        .flatten()

    cards.forEach {
      if (it.productId != null) {
        val productFull =
          products.firstOrNull { productFull -> it.productId == productFull.product?.id }
        if (productFull != null) {
          it.product = productFull.product
          it.product?.skus = productFull.skus
        }
      }
    }
    val list = this
    val filteredList = list.take(10)
        .filter {
          it.product?.skus?.any { sku -> sku.available } ?: false && it.product?.selectionEngine != "DAN" && it.product?.style != "999999" && it.product?.isOnSale() == false
        }
    return list
  }

  @Transaction
  private fun Thread.withCardProducts(snkrsDB: SnkrsDB): Thread {
    val ids = cards?.filter { it.productId != null }
        ?.map { it.productId!! }
    val products = snkrsDB.productDAO()
        .getProductsById(ids!!)


    cards?.forEach {
      if (it.productId != null) {
        val productFull =
          products.firstOrNull { productFull -> it.productId == productFull.product?.id }
        if (productFull != null) {
          it.product = productFull.product
          it.product?.skus = productFull.skus
        }
      }
    }
    return this
  }

  @Transaction
  fun getThreadsFromDB(): Observable<List<Thread>> {
    return snkrsDB.threadDAO()
        .getData()
        .toObservable()
        .map {
          it.mapToThreads()
              .map { threads -> threads.withCardProducts(snkrsDB) }
        }
  }

  @Synchronized
  @Transaction
  fun getAllPages(): Single<List<Thread>> {
    return snkrsDB.threadDAO()
        .getData()
        .doOnSuccess { it }
        .map { it.mapToThreads() }
        .map { it.withCardProducts(snkrsDB) }
  }

  @Transaction
  fun getPage(pageNumber: Int): Maybe<List<Thread>> {
    return snkrsDB.threadDAO()
        .getPage(50 * pageNumber, 50)
        .filter { it.isNotEmpty() }
        .firstOrError()
        .map { it.mapToThreads() }
        .map { it.withCardProducts(snkrsDB) }
        .toMaybe()
        .doAfterSuccess { it }
  }

  fun getFeed(): Observable<PagedList<Thread>> {
    return pages.throttlePages()
  }

  private fun Observable<PagedList<Thread>>.throttlePages() =
    this.filter { it.size > 0 }
        .sample(Observable.interval(0, 5, SECONDS), true)
        .distinctUntilChanged()

  fun getFirstThreadId(time: Long) =
    snkrsDB.threadDAO().getFirstThreadId(time).mapToThread().withCardProducts(snkrsDB)

  fun getThreadByInterestId(id: String) =
    snkrsDB.threadDAO().getThreadByInterestId(id)?.mapToThread()?.withCardProducts(snkrsDB)

  fun getThreadsByRelatedIds(ids: List<String>) =
    snkrsDB.threadDAO().getThreadsByRelatedIds(ids).mapToThreads().withCardProducts(snkrsDB)

  fun getThreadsByInterestIds(ids: List<String>): List<Thread> =
    snkrsDB.threadDAO().getThreadsByAllInterestIds(ids).mapToThreads().withCardProducts(snkrsDB)

  fun getThreadByProductId(id: String) =
    snkrsDB.threadDAO().getThreadByProductId(id).mapToThread().withCardProducts(snkrsDB)

  fun getThreadBySlug(slug: String) =
    snkrsDB.threadDAO().getThreadBySlug(slug).mapToThread().withCardProducts(snkrsDB)

  @Deprecated(message = "Use safeGetThreadByThreadId")
  fun getThreadByThreadId(id: String) =
    snkrsDB.threadDAO().getThreadByThreadId(id)!!.mapToThread().withCardProducts(snkrsDB)

  //TODO MIKE: Migrate all to safe version
  fun safeGetThreadByThreadId(id: String): Thread? {
    val thread = snkrsDB.threadDAO()
        .getThreadByThreadId(id)

    return thread?.mapToThread()
        ?.withCardProducts(snkrsDB)
  }

  fun getThreadsById(ids: List<String>) =
    snkrsDB.threadDAO().getThreadsByIDs(ids).mapToThreads().withCardProducts(snkrsDB)

  fun getThreadByStyleColor(styleColor: String): Thread {
    val split = styleColor.split('-')
    return snkrsDB.threadDAO()
        .getThreadByStyleColor(split[0], split[1])
        .mapToThread()
        .withCardProducts(snkrsDB)
  }

  suspend fun getThreadByStyleColorSuspended(styleColor: String): Thread =
    withContext(Dispatchers.IO) {
      val split = styleColor.split('-')
      snkrsDB.threadDAO()
          .getThreadByStyleColor(split[0], split[1])
          .mapToThread()
          .withCardProducts(snkrsDB)
    }

  fun getThreadsThatMatch(searchTerm: String) =
    snkrsDB.threadDAO().getThreadsForSearch(searchTerm).mapToThreads().withCardProducts(snkrsDB)

  //TODO MIKE: change to exponential dropoff
  fun getInStockThreads() =
    snkrsDB.threadDAO().getPurchasableInStockThreads()
//        .mergeWith {
//          snkrsDB.threadDAO()
//        .getDrawInStockThreads()
//        }
        .toObservable()
        .sample(Observable.interval(0, 10, SECONDS), true)
        .distinct { it.map { it.thread.lastUpdatedTime } }
        .map {
          it.distinctBy { it.thread.threadId }
              .mapToThreads()
              .withCardProducts(snkrsDB)
        }

  fun getUpcomingThreads() =
    snkrsDB.threadDAO().getUpcomingThreads()
        .toObservable()
        .sample(Observable.interval(0, 10, SECONDS), true)
        .distinct { it.map { it.thread.lastUpdatedTime } }
        .map {
          it.distinctBy { threadFull -> threadFull.thread.threadId }
              .mapToThreads()
              .withCardProducts(snkrsDB)
        }

  fun threadsByTags(searchTerm: String) = snkrsDB
      .threadDAO()
      .getThreadsByTag(searchTerm)
      .map { it.map { it.mapToThread() } }

  fun threadsByTag(searchTerm: String) = snkrsDB
      .threadDAO()
      .getThreadByTag(searchTerm)
      .map { it.mapToThread() }

  suspend fun getProductByProductIdSuspended(id: String): Product? = withContext(Dispatchers.IO) {
   getProductByProductId(id)
  }

  fun getProductByProductId(id: String) = snkrsDB.productDAO().getProductById(id)?.mapToProduct()


  suspend fun getProductByStyleColorSuspended(styleColor: String): Product? = withContext(Dispatchers.IO){
    getProductByStyleColor(styleColor)
  }

  fun getProductByStyleColor(styleColor: String?): Product? {
    if (styleColor == null) return null
    val split = styleColor.split('-')

    Timber.d("split string: %s", split)
    return snkrsDB.productDAO()
        .getProductByStyleColor(split[0], split[1])
        .mapToProduct()
  }

  @Transaction
  fun updateProductSkus(productSkus: List<Sku>) {
    productSkus.forEach { Timber.i("updating sku with productId ${it.productId} ${it.available}") }
    insertSku(*productSkus.toTypedArray())
  }
}

我试过使用 AsyncTask,但没有成功,但我想看看是否有使用 RxJava 的解决方案。还想看看我是否可以使用 Coroutines

找到解决方案

我也试过传递一个 Continuation 参数,但该方法没有返回一个 ``Product``` 对象,但即使转换它,它也会给我一个不同的错误。

我只是想使用 getProductByStyleColorSuspended(detailOrder.getStyleColor())getProductByStyleColor(detailOrders.getStyleColor())

方法从 OrderDetailsFragment.java 成功创建一个 Product 对象

你忘了 subscribeOn 和 observeOn 函数

  1. 您应该订阅 IO 线程

  2. 在主线程上观察

像那样

 mOrderHistoryService.getOrderHistoryDetails(mOrder)
        .subscribeOn(Schedulers.io())
        .observeOn (AndroidSchedulers.mainThread())
        .subscribe(new SimpleSubscriber....

在你的片段中试试这个

Single.fromCallable(new Callable<Product>() {

            @Override
            public Product call() throws Exception {
                return threadStore.getThreadRepository()
                    .getThreadPersister()
                    .getProductByStyleColorSuspended(detailOrder.getStyleColor())
            }
        })
                .subscribeOn(Schedulers.io())
                .observeOn (AndroidSchedulers.mainThread())
                .subscribe(new DisposableSingleObserver<Object>() {
                    @Override
                    public void onSuccess(Object o) {
                        if (product != null) {
                            final DeferredPaymentOrder deferredPaymentOrder =
                                mSnkrsDatabaseHelper.getDeferredPaymentOrder(detailOrder.getId());
                            if (deferredPaymentOrder != null && deferredPaymentOrder.isWaiting()) {
                              mTopButton.setVisibility(View.VISIBLE);
                              mTopButton.setText(R.string.cta_go_fund);
                              mTopButton.setOnClickListener(
                                  v -> ((BaseActivity) getActivity()).fundDeferredPaymentOrder(product,
                                      AnalyticsAction.ORDER_DETAILS_FUND));
                            } else {
                              mTopButton.setVisibility(View.GONE);
                            }
                          } else {
                            mTopButton.setVisibility(View.GONE);
                          }
                    }

                    @Override
                    public void onError(Throwable e) {

                    }
                });

对您访问数据库的任何地方应用相同的方法