异步任务阻塞 UI 线程

Posted

技术标签:

【中文标题】异步任务阻塞 UI 线程【英文标题】:Async Task blocking UI thread 【发布时间】:2017-07-07 16:02:43 【问题描述】:

更新

感谢@EmanuelSeibold,我能够将问题定位到recyclerview 的更新。 AsyncTask 在后台运行得很好,只有 recyclerview 的适配器更新会冻结 UI。

更新2

我发现这确实是我的布局设置。我忘了删除 RecyclerView 周围的nestedScrollView。这似乎导致了渲染冲突。

我通过这里的答案和博客文章挖掘了自己的方式,但似乎无法找到解决方案。

我对 android 开发相当陌生,并试图了解多线程。

场景:我有一个应用程序,其中包含一个包含课程数据的 SQLite 数据库,并实现了一个查询该数据库的搜索功能。这会阻塞 UI 线程大约 3 秒。 因此,我实现了一个 AsyncTask 以保持 UI 响应,但在搜索过程中我的 UI 仍然被阻止。

提前致谢!

代码如下:

搜索活动

public class Activity_Search extends Activity_Base 

  private RecyclerView rv;


  @Override
  protected void onCreate(Bundle savedInstanceState) 
      super.onCreate(savedInstanceState);
      setContentView(R.layout.activity_search);
      setActionBarTitle(R.string.title_search);

      findViewById(R.id.searchCourseTitle).setOnKeyListener(new View.OnKeyListener() 
          @Override
          public boolean onKey(View v, int keyCode, KeyEvent event) 
              switch(keyCode) 
                  case KeyEvent.KEYCODE_ENTER:
                      startSearch();
                      break;
                  default:
                      return false;
              
              return true;
          
      );


      rv = (RecyclerView) findViewById(R.id.searchRecycler);

      Adapter_Search adapter = new Adapter_Search(this, null);
      rv.setAdapter(adapter);
      rv.setLayoutManager(new LinearLayoutManager(this));

      FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.search_FAB);
      fab.setOnClickListener(new View.OnClickListener() 
          @Override
          public void onClick(View view) 
              startSearch();
          
      );
  

  private void startSearch() 
      findViewById(R.id.searchCourseTitle).clearFocus();
      if (this.getCurrentFocus() != null) 
          InputMethodManager imm =
                  (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
          imm.hideSoftInputFromWindow(this.getCurrentFocus().getWindowToken(), 0);
      

      getting input

      String[] columns = ...;


      String selection = formatting input;

      Async_Search search = new Async_Search(this);
      search.execute(columns, new String[]selection);
  

  public void onSearchCompleted(Cursor results) 
      ((Adapter_Search) rv.getAdapter()).changeCursor(results);
  

异步任务

public class Async_Search extends AsyncTask<String[], Void, Cursor> 

  private Activity activity;

  public Async_Search (Activity activity) 
      this.activity = activity;
  

  @Override
  protected Cursor doInBackground(String[]... params) 
      SQLiteDatabase db = SQL_Database.getInstance(activity).getWritableDatabase();

      String[] columns = params[0];
      String selection = params[1][0];

      return db.query(...)
  

  @Override
  protected void onPostExecute(Cursor results) 
      ((Activity_Search) activity).onSearchCompleted(results);
  

回收站适配器

public class Adapter_Search extends RecyclerCursorAdapter<Adapter_Search.ViewHolder>

public static class ViewHolder extends RecyclerView.ViewHolder 
    public TextView name, ects, studipCode, termYear, lecturers, fields;

    public ViewHolder (View view) 
        super(view);
        id lookups 
    


public Adapter_Search(Context context, Cursor cursor) 
    super(context, cursor);


@Override
public ViewHolder onCreateViewHolder (ViewGroup parent, int ViewType) 
    View view = LayoutInflater.from(parent.getContext())
            .inflate(R.layout.search_list_entry, parent, false);
    ViewHolder viewHolder = new ViewHolder(view);
    return viewHolder;


@Override
public void onBindViewHolder (ViewHolder viewHolder, Cursor cursor) 

    TextView name, ects, studipCode, termYear, lecturers, fields;
    name = viewHolder.name;
    ects = viewHolder.ects;
    studipCode = viewHolder.studipCode;
    termYear = viewHolder.termYear;
    lecturers = viewHolder.lecturers;
    fields = viewHolder.fields;

    String termandyear = cursor.getString(cursor.getColumnIndexOrThrow(SQL_Database.COURSE_COLUMN_TERM)) +
            String.format("%.2s",cursor.getString(cursor.getColumnIndexOrThrow(SQL_Database.COURSE_COLUMN_YEAR)));

    name.setText(cursor.getString(cursor.getColumnIndexOrThrow(SQL_Database.COURSE_COLUMN_COURSE)));
    String credits = cursor.getString(cursor.getColumnIndexOrThrow(SQL_Database.COURSE_COLUMN_ECTS)) + " ECTS";
    ects.setText(credits);
    and so on 

基础适配器类

public abstract class RecyclerCursorAdapter <ViewHolder extends RecyclerView.ViewHolder> extends RecyclerView.Adapter<ViewHolder> 

private Context mContext;

private Cursor mCursor;

private boolean mDataValid;

private int mRowIdColumn;

private DataSetObserver mDataSetObserver;

public RecyclerCursorAdapter(Context context, Cursor cursor) 
    mContext = context;
    mCursor = cursor;
    mDataValid = cursor != null;
    mRowIdColumn = mDataValid ? mCursor.getColumnIndex("_id") : -1;
    mDataSetObserver = new NotifyingDataSetObserver();
    if (mCursor != null) 
        mCursor.registerDataSetObserver(mDataSetObserver);
    


public Cursor getCursor() 
    return mCursor;


@Override
public int getItemCount() 
    if (mDataValid && mCursor != null) 
        return mCursor.getCount();
    
    return 0;


@Override
public long getItemId(int position) 
    if (mDataValid && mCursor != null && mCursor.moveToPosition(position)) 
        return mCursor.getLong(mRowIdColumn);
    
    return 0;


@Override
public void setHasStableIds(boolean hasStableIds) 
    super.setHasStableIds(true);


public abstract void onBindViewHolder(ViewHolder viewHolder, Cursor cursor);

@Override
public void onBindViewHolder(ViewHolder viewHolder, int position) 
    if (!mDataValid) 
        throw new IllegalStateException("this should only be called when the cursor is valid");
    
    if (!mCursor.moveToPosition(position)) 
        throw new IllegalStateException("couldn't move cursor to position " + position);
    
    onBindViewHolder(viewHolder, mCursor);


/**
 * Change the underlying cursor to a new cursor. If there is an existing cursor it will be
 * closed.
 */
public void changeCursor(Cursor cursor) 
    Cursor old = swapCursor(cursor);
    if (old != null) 
        old.close();
    


/**
 * Swap in a new Cursor, returning the old Cursor.  Unlike
 * @link #changeCursor(Cursor), the returned old Cursor is <em>not</em>
 * closed.
 */
public Cursor swapCursor(Cursor newCursor) 
    if (newCursor == mCursor) 
        return null;
    
    final Cursor oldCursor = mCursor;
    if (oldCursor != null && mDataSetObserver != null) 
        oldCursor.unregisterDataSetObserver(mDataSetObserver);
    
    mCursor = newCursor;
    if (mCursor != null) 
        if (mDataSetObserver != null) 
            mCursor.registerDataSetObserver(mDataSetObserver);
        
        mRowIdColumn = newCursor.getColumnIndexOrThrow("_id");
        mDataValid = true;
        notifyDataSetChanged();
     else 
        mRowIdColumn = -1;
        mDataValid = false;
        notifyDataSetChanged();
        //There is no notifyDataSetInvalidated() method in RecyclerView.Adapter
    
    return oldCursor;


private class NotifyingDataSetObserver extends DataSetObserver 
    @Override
    public void onChanged() 
        super.onChanged();
        mDataValid = true;
        notifyDataSetChanged();
    

    @Override
    public void onInvalidated() 
        super.onInvalidated();
        mDataValid = false;
        notifyDataSetChanged();
        //There is no notifyDataSetInvalidated() method in RecyclerView.Adapter
    

【问题讨论】:

看起来您的 RecycleView 需要时间来更新数据。它不是 AsyncTask。您可以发布您的 AdapterSearch 吗? @EmanuelSeibold 我编辑了它。是因为我必须循环浏览整个光标(最多可容纳 400 行)?如果是,我该如何绕过它?很抱歉代码太长了,我现在只在移动设备上,格式化代码在这里真的很难。 Cursor 是惰性的,所以即使它有 400 行,它和 recyclerview 的组合也不应该挂掉 UI。你确定它没有被阻止,而是很慢? @cricket_007 它冻结了,android 监视器告诉我帧被跳过了,并且花了很多秒才跟进触摸交互 【参考方案1】:

也许尝试从异步搜索中删除这些行。

private Activity activity;

  public Async_Search (Activity activity) 
      this.activity = activity;
  

改为这样调用:

  Async_Search search = new Async_Search();
  search.execute(columns, new String[]selection);

我不知道这是否可行,但我的 Asynctask 不使用你的活动。

【讨论】:

那我如何获得一个可读的数据库呢?它需要构造函数的上下文。以及如何在 onPostExecute 中与 UI 交互? @Mipsh ***.com/questions/12575068/…

以上是关于异步任务阻塞 UI 线程的主要内容,如果未能解决你的问题,请参考以下文章

异步任务(AsyncTask)

Android异步任务AsyncTask

Android_AsyncTask异步任务机制

C# 理解阻塞 UI 和异步/等待与 Task.Run 的问题?

同步/异步 异步回调 协成 线程队列

一篇搞定(Js异步事件循环与消息队列微任务与宏任务)