16

I have a requirement to generate a bitmap out of an EditText and then perform some manipulations on it. My main concern is not to call View.buildDrawingCache() method on the UI thread and possibly block it, especially when talking about large screens (i.e. Nexus 10) since the EditText will occupy about 80% of the available screen size.

I execute Runnables inside a ThreadPoolExecutor, those will inflate dummy views on a worker thread and set all the required attributes to them, then simply call buildDrawingCache() & getDrawingCache() to generate a bitmap.

This works perfect on some devices yet recently I have encountered a few devices that crash with the following message:

java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()

I understand why this happens, as some phones must have modified implementation for EditText that creates a Handler and thus requires Looper.prepare() to be called first.

From what I've read online there is no issue with calling Looper.prepare() inside a worker thread though some stated it is highly unrecommended yet I could not find a reason for that.

Other than that, most posts related to this issue state you are not supposed to inflate views inside a background thread, probably due to the following from Android's official documentation (Processes and Threads):

"Do not access the Android UI toolkit from outside the UI thread"
  • What is the recommended approach to dealing with this problem?

  • Is there any harm in calling build/get drawingcache from the main thread? (performance-wise)

  • Will calling Looper.prepare() inside my worker thread solve this problem?

EDIT

Just to elaborate on my specific requirement, I have a user-interface consisting of an ImageView and a custom EditText on top of it, the EditText can change it's font and color according to the user selection, it can be zoomed in/out using "pinch to zoom" gesture and can also be dragged around to allow the user to reposition it on top of the image.

Eventually what I do is create a dummy view inside my worker thread using the exact same values (width, height, position) it currently has on the UI and then generate it's drawingcache, the original image's bitmap is decoded again from a local file.

Once the two bitmaps are ready I merge them into a single bitmap for future use.

So to put it simple, is there anything wrong with executing the following code (from within a background thread):

Call Looper.prepare() Create a new view with application context, call measure() & layout() manually and then build+get drawingcache from it, i.e.:

Looper.prepare();

EditText view = new EditText(appContext);

view.setText("some text");
view.setLayoutParams(layoutParams);

view.measure(
        View.MeasureSpec.makeMeasureSpec(targetWidth, View.MeasureSpec.EXACTLY),
        View.MeasureSpec.makeMeasureSpec(targetHeight, View.MeasureSpec.EXACTLY));

view.layout(0, 0, targetWidth, targetHeight);

view.buildDrawingCache();

Bitmap bitmap = view.getDrawingCache();

How does this apply to the restriction with not accessing the Android UI toolkit from outside the UI thread, what could possibly go wrong?

cdroid
  • 1,582
  • 1
  • 14
  • 22
  • what I don't understand is, why don't you use the drawingCache of your displayed EditText ? This cache exists as soon as the view is displayed. – njzk2 Feb 18 '14 at 14:31
  • Doesn't the cache get created once you call buildDrawingCache ? – cdroid Feb 18 '14 at 14:34
  • from the source, it appears you are right. apparently the cache gets not drawn unless you explicitly call for it. – njzk2 Feb 18 '14 at 14:45
  • @cdroid why not just send the view into the threadpool and execute view.buildDrawingCache();and send a callback with the drawingCache to the calling class (to a handler connected to main thread or runOnUiThread in activity)? Why recreate the view in background, I mean, when you can do the above? – Magnus Feb 18 '14 at 20:15
  • @Magnus that would leak whatever context which is associated with the view to another thread, in my case an Activity context.. – cdroid Feb 18 '14 at 20:19
  • @cdroid ...that can be cleaned upon shutdown of your app (shutdown executor etc), as soon a the drawing cache is built it's context reference is let go by that code anyway, since that runnable (callable) is done – Magnus Feb 18 '14 at 20:29
  • @Magnus Are you in favour of leaking activity context? :) – cdroid Feb 18 '14 at 20:33
  • @cdroid =) I'm in favor of simple solutions with cleanup than a complex solution. Why not do it in a AsyncTask (which is coupled to the activity and cancel / null out asynctask reference upon destroy for instance, running on the THREAD_POOL_EXECUTOR in asynctask), but I guess that's not the path you want you want to go down? Only `buildDrawingCache()` call in `doInBackground()`. – Magnus Feb 18 '14 at 20:41
  • Even if I would consider your approach, that still breaks the ground rule of not accessing the UI toolkit from outside the UI thread. – cdroid Feb 19 '14 at 10:08
  • Why the close vote? This seems like a well thought out question with lots of detail and a technical need. – Richard Le Mesurier Feb 21 '14 at 06:22
  • some devices?? are you targeting some android api versions? – Rat-a-tat-a-tat Ratatouille Feb 21 '14 at 06:26
  • hve you also tried using handlers with callbacks ? – Rat-a-tat-a-tat Ratatouille Feb 21 '14 at 06:31
  • Can I understand why are you creating a new EditText instead of accessing the main one ? – Gomino Feb 23 '14 at 12:03

6 Answers6

4

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.

Community
  • 1
  • 1
Gomino
  • 12,127
  • 4
  • 40
  • 49
1

Calling View.buildDrawingCache() calls Bitmap.nativeCreate which can be a large allocation, so yes, it can be potentially harmful to run on main thread. I don't see a problem with calling Looper.prepare() in your background thread. However, it's unclear what you are trying to achieve and there may be a better solution to your problem.

zyamys
  • 1,609
  • 1
  • 21
  • 23
1

The reason you are not supposed to the UI toolkit from other threads is that it is not written to be thread safe it is written under the assumption that only one thread runs it. This means it's really hard to tell what can go wrong, the bad effects, if any, will mostly happen in an un-repeatable due to specific timing of threads. Your description of what you are trying to do it not too clear. In your case, I would just allocate a large bitmap, and draw text into it. Why are you using the EditText in the first place ? It seems like a kind of a hack, and hacks tend to break eventually.

yoah
  • 7,180
  • 2
  • 30
  • 30
  • the effect I'm after is very simple - the user has a layout consisting of an ImageView with an EditText on top of it. He can change the image source from camera/gallery, and optionally add text on top of the image. The text can then be dragged around, it's font size can change and the font typeface/color are changeable as well. Once the user hits submit I offload this work to a background thread (this work being: 1. generating a drawingcache of a duplicate EditText instance. 2. merge that drawingcache with a bitmap from the background image, 3. save to disk). – cdroid Feb 21 '14 at 23:40
  • 2
    What I suggest you do for memory use and stability is that in your thread get the bitmap, create a canvas on it, and use this canvas to draw text (Canvas.drawText) on top of it. You can set the position, typeface, text size and color in the paint you use to draw the text. – yoah Feb 22 '14 at 11:53
  • What about text-wrapping? I want the final bitmap to look exactly like it looked on the user-interface. Wouldn't that be an overkill to start messing with Canvas and ensuring text-wrapping behaves EXACTLY like my EditText? Or perhaps StaticLayout can do that work for me? – cdroid Feb 22 '14 at 20:49
1

Why View.buildDrawingCache()? What about using View.draw(Canvas canvas) to manually render to a Canvas backed by a Bitmap? Method seems simple enough to not cause problems on background threads.

Jasoneer
  • 2,078
  • 2
  • 15
  • 20
0
EditText edit = (EditText)findViewById(R.id.edit);
edit.buildDrawingCache();
ImageView img = (ImageView)findViewById(R.id.test);
img.setImageBitmap(edit.getDrawingCache());

Lalit when you try to build the cache in the onCreate method, the drawing hasn't happened yet so the drawingCache should have nothing. Either put the buildDrawingChache method in the onClick method. Or use the following code in onCreate.

ViewTreeObserver vto = editText.getViewTreeObserver(); 
vto.addOnGlobalLayoutListener(new OnGlobalLayoutListener() { 
    @Override 
    public void onGlobalLayout() {
        editText.buildDrawingCache();
        } 
});
Digvesh Patel
  • 6,503
  • 1
  • 20
  • 34
-1

I also encountered this error a few times already:

java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()

my solution:

new Thread(new Runnable(){
  @Override
  public void run(){
    //add implementations that DOES NOT AFFECT the UI here

    new Handler(Looper.getMainLooper()).post(new Runnable() {       
      @Override
      public void run(){
         //manage your edittext and Other UIs here
      }
    });
  }
}).start();

just create a handler inside your worker thread to apply data changes to your UI

Rick Royd Aban
  • 904
  • 6
  • 33