异步任务阻塞 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 线程的主要内容,如果未能解决你的问题,请参考以下文章