Inflate a View in a Background Thread

Inflate a view in a background thread

The LayoutInflater does not make any assumptions about what thread it runs on. And nothing is mentioned about this in its documentation. Also its code seams to be thread-agnostic.

On the other hand, Views that are created by LayoutInflater might instantiate Handlers in their constructors. Well, they probably shouldn't do that, but there is no requirement for them to not create/use Handlers in their constructors.

My guess is that Samsung Galaxy S had some modifications in its EditText that somehow triggers creation of Handler (according to crash log from your other question instance of GestureDetector was instantiated which in turn created new Handler). While default implementation doesn't do this.


Overall, I'd say that because there is no explicit requirement for Views to not use Handlers and Loopers in their constructors you can't assume inflating Views from non-UI thread is safe.

You can actually create HandlerThread and try inflating Views inside it. But I'd say this is very risky, as in Samsung Galaxy S example the view assumes that this thread will be alive during View lifetime and will process all messages using its Looper. Which might result in crash later on.

Creating views inside a worker thread

In your case, you can do it of course, but be carefull only reading values from UI data, to avoid synchronizations bug.

Also you should not recreate the EditText from the background thread, it will be more efficient to directly access the already existant one instead:

Looper.prepare();
myEditText.setDrawingCacheEnabled(true);
Bitmap bitmap = myEditText.getDrawingCache();

If your question is : why it is not recommanded by android guidelines, here is a good SO answer to your question.

android: inflate views into a layout

I checked your code by creating a project. I am using Android Studio. I did not find any of that problem that you mentioned. Just have a look at my code and try to change yours accordingly.

MainActivity:

public class MainActivity extends Activity {

private LinearLayout mContainer;
private View view;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

mContainer = (LinearLayout)findViewById(R.id.inflate_container);
findViewById(R.id.add).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
view = LayoutInflater.from(MainActivity.this).inflate(R.layout.expanded_record,mContainer,false);
mContainer.addView(view,mContainer.getChildCount());
}
});
findViewById(R.id.change_color).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
view.setBackgroundColor(Color.BLUE);

}
});

}

Main XML:

 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".MainActivity">

<LinearLayout
android:id="@+id/inflate_container"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical">

</LinearLayout>

<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_margin="20dp">

<TextView

android:text="add" android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/add"
android:clickable="true"/>

<TextView
android:id="@+id/change_color"
android:clickable="true"
android:text="change_color" android:layout_width="wrap_content"
android:layout_height="wrap_content" />

</LinearLayout>

</RelativeLayout>

and the XML that I am inflating

<?xml version="1.0" encoding="utf-8"?>
<View xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="10dp"
android:background="#000000"
android:layout_gravity="center">

</View>

This is the result I got

See that I can add black views and change them to blue and then again black

http://postimg.org/image/ggeotcz91/

android addView in background thread

Instead of adding view on a background thread you can parcel out the work by posting several Runnables on the UI thread. The code below is a highly simplified version of that technique but it's similar to how it was done in Android's Launcher app:

private void createAndAddViews(int count) {
for (int i = 0; i < count; i++) {
// create new views and add them
}
}

Runnable r = new Runnable() {
public void run() {
createAndAddViews(4); // add 4 views
if (mMoreViewsToAdd) mTopLevelView.post(this);
}
};

mTopLevelView.post(r);

Interesting thread behavior when Layout Inflater inflates EditText

Does anyone have any idea why this is happening

TextView has references to AsyncTask in its locale handling (at least in its Android 5.1 version; you would need to check for the specific version of Android that you are running).

AsyncTask will initialize a thread pool when the AsyncTask class is loaded as part of setting up the rest of the static data members. That pool has a minimum pool size of the number of CPU cores plus one. So, on an emulator or single-core hardware, two Thread objects will be created once something references the AsyncTask class. They will exist for the duration of your process. Note that on multi-core CPUs, I would expect more threads to be in this pool in the steady state, and there can be even more threads (up to double the number of cores, plus one) if a lot of tasks are active.

how I can prevent it?

Don't use TextView or anything that inherits from it. Also, do not use any other framework classes that reference AsyncTask.

Since those threads are not running (as you can see from your screenshot), I fail to see what your issue is. If anything, we need more stuff in the framework using background threads, as there are some places where Android's framework code runs afoul of StrictMode threading checks.

what can I do to... close them afterwards?

They are closed, insofar as they are not running. As you see in your screenshot, they are blocked ("Wait"). They will remain in that state until something needs to run a task. This is how thread pools work.

Efficiently Inflating a lot of Views within several Horizontal LinearLayout with Adapters

I have figured it out :)

@TheOriginalAndroid answer is an excellent idea and response! Thank you so much for your time and help. I actually had already started implementing an AsyncTask Manager and finished it yesterday morning.

I solved this by creating a class called AsycnGridManager that will manage the group of asyncTasks responsible for painting the view. It is quite a bit of code, but I went into great detail in the comments. This is not the actual code but a shell to show an overview of how it works. I have not compiled it so please don't take it as a diamond. This class should be created and started from the main thread within your main activity or fragment that is responsible for it.

/**
* This class will manage a view and load it asynchronously.
* In particular, this view will manage a linearLayout in
* 2D space. IE. One verticle linear layout with a horizontal
* linearLayout at each row.
* @author Evan Boucher
*/
public class AsyncGridManager {

/**
* This is the core number of Threads in the pool.
* You should probably consider checking the
* system for the number of cores the device has.
* I currently use 4 as it fits my needs.
*/
private static final int NUM_OF_THREADS_IN_POOL = 4;

/**
* The max number of threads that can exist in the pool at one time.
*/
private static final int MAX_NUM_OF_THREADS_IN_POOL = 10;

/**
* The max number of tasks that the queue can hold for the
* pool
*/
private static final int MAX_NUM_OF_TASKS_IN_QUEUE = 150;

/**
* The max keep alive time for a thread task in the pool.
* This should be longer than your longest task. If you have
* a long UI task in each thread (you are probably doing
* to much to begin with!) then the task may get stopped
* before it finishes.
*/
private static final int THREAD_KEEP_ALIVE_TIME = 4000;

/**
* The minimum time to wait to paint a single EPG item.
* This means that a block will never be painted any faster
* than this number in Milliseconds.
*/
private final int MIN_WAIT_TIME_TO_PAINT = 100;

/**
* The max time an async task will sleep before painting on the
* UI thread.
*/
private final int MAX_WAIT_TIME_TO_PAINT = 1000;

/**
* The thread pool that the async tasks within this class will
* pull from. This is defined by the above varaibles.
*/
private ThreadPoolExecutor mThreadPool;

/**
* The queue of tasks that the thread pool will pull from.
* The size is fairly large as I don't much care about memory
* usage right now. Once the queue fills up it will not add
* anymore tasks. Be aware of that! So tasks can be lost or
* cause a thread to block (if you add the tasks on the main
* thread).
*/
private BlockingQueue taskQueue;

/**
* The thread that this manager will run on as to not block the main thread.
*/
public Thread mGridManagerThread;

/**
* The Grid map object that is the underlying data for this grid.
* Each key is a row and each value is a list for the columns in that
* row.
*/
private Map<String,List<CustomObject>> mGridMap;
//Number of rows in the table (size of the mGridMap)
private int mNumOfRows;
//Get the rootView that is already inflated. This is what we will add to.
private LinearLayout mRootGridView;
//The Android activity context that this special async manager is attached to.
private Context mContext;

/**
* Creates and initializes this class.
*
*/
public AsyncGridManager(Context context, LinearLayout rootView, Map<String,List<CustomObject>> gridMap) {

//Create a new taskqueue for the EPGblocks.
taskQueue = new ArrayBlockingQueue<CreateEPGTableRowTask>(MAX_NUM_OF_TASKS_IN_QUEUE);

//Create a new threadpool for the tasks.
poolExecutor = new ThreadPoolExecutor(NUM_OF_THREADS_IN_POOL, MAX_NUM_OF_THREADS_IN_POOL, THREAD_KEEP_ALIVE_TIME, TimeUnit.MILLISECONDS, taskQueue);
this.mGridMap = gridMap;
/*
* We can only get the number of rows as that is predefined
* by this datastructure (won't know how many columns until we get to the row).
*/
this.mNumOfRows = mGridMap.size();

this.mContext = context;
/*
* The RootView should be a LinearLayout in my case and already be inflated!
*/
this.mRootGridView = rootView
}
/**
* Tell the async manager to start loading the tasks into the queue.
* It loads on a seperate thread to make this completely async.
*/
public void startAsyncLoading() {
/*
* It is important here to note that we should inflate the mRootGridView
* This way adding views to it will be async on the UI thread.
*/
mGridManagerThread = new Thread(new AsyncGridLoaderRunnable());
mGridManagerThread.start();
}

/**
* The runnable for this manager to generate
*/
public class AsyncGridLoaderRunnable extends Runnable {

@Override
public void run() {
//A for loop to go through the size of the rows
for (int i = 0; i < mNumOfRows; i++) {
//For each row, lets make a AsyncTask to generate and paint that row. You need to make a new one everytime.
CreateRowAsyncTask rowAsyncTask = new CreateRowAsyncTask(i);
/*
* I pass i in here so that you could also get the rowIndex as a parameter too if we want.
* This adds the task to the taskQueue for this pool to execute.
*/
rowAsyncTask.executeOnExecutor(poolExecutor, i);
}
}
}
/**
* Async Task that will create and print a row
* from the map.
*/
public class CreateRowAsyncTask extends AsyncTask {
//Random generator to force tasks to sleep for random periods.
private Random mRandomGenerator;
//The row index that this task is responsible for painting and managing.
private int rowIndex;
//The horizontal linearlayou that represents this row. Might want to add it to a list so we can reference it later.
private LinearLayout singleRowLayout;

//The local reference to the list of columns for this row.
private List<CustomObject> columnList;

public CreateRowAsyncTask(int rowIndex) {
this.mRandomGenerator = new Random();
this.rowIndex = rowIndex;
//Create the linearlayout for the row.
singleRowLayout = new LinearLayout(mContext);
//Set it to horisontal to be a row.
singleRowLayout.setOrientation(LinearLayout.HORIZONTAL);
//Get a reference to this rows list of columns.
columnList = mGridMap.get(rowIndex);
}
@Override
protected Object doInBackground(Object... arg0) {
/*
* Here you could do some background stuff to setup objects /views.
* I am going to assume you have some method to generate the view
* from our CustomObject (the items within the list for the rows).
*/
//Lets tell the UI thread to add our row real quickly (remember the root view was already inflated)
mRootGridView.addView(singleRowLayout);

/*
* Due to the Async nature we need to draw each row together.
* If we dont, EPG blocks will be out of order (not guaranteed order).
* Uses onProgressUpdate() to paint each block in the row.
*/
CustomObject columnObject;
for (int i = 0; i < columnList.size(); i++) {
//Lets save a reference to the object we want to add to the row we are on
columnObject = columnList.get(i);

/*
* The customView we are adding. This assumes that the columnObject createView() method
* will create a new LinearLayout (or View of some type) which we will add to this row.
* You could put the createView() call directly in the publishProgress() method for
* ease, but I left it out to show the custom view creation.
* Be sure that the createView() does not handle any inflated views (these must be
* accessed on the UI thread).
*/
CustomView newViewToAddAsColumn = columnObject.createView();
//Create each row and use ProgressUpdate to paint it.
publishProgress(newViewToAddAsColumn);
try {
/*
* Sleep the task for a random period of time, this way the view is not loading all at once.
* This is one strategy, there are plenty of other Async Loading strategies
*/
Thread.sleep(mRandomGenerator.nextInt(MAX_WAIT_TIME_TO_PAINT - MIN_WAIT_TIME_TO_PAINT) + MIN_WAIT_TIME_TO_PAINT);

} catch (InterruptedException e) {
Log.e(TAG, "ERROR! AsyncTask failed to wait!!!");
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}

@Override
protected void onProgressUpdate(Object... values) {
//Get the customView and add it to the row.
CustomView customViewToAdd = (EpgEventView) values[0];
//Add the customView to the row. We assume that the params for the view are within the customView.
singleRowLayout.addView(customViewToAdd, customViewToAdd.getParams());
}

}

}

I have not run this code specifically so use it more as an example than a perfect solution. This code will add views asynchronously to the rootView without blocking the UI experience. :)

Enjoy,

-Evan



Related Topics



Leave a reply



Submit