Создание представлений внутри рабочего потока

У меня есть требование создать растровое изображение из EditText, а затем выполнить некоторые манипуляции с ним. Моя главная задача - не называть метод View.buildDrawingCache() в потоке пользовательского интерфейса и, возможно, блокировать его, особенно когда речь идет о больших экранах (например, Nexus 10), поскольку EditText будет занимать около 80% доступного размера экрана.

Я выполняю Runnable внутри ThreadPoolExecutor, они будут раздувать фиктивные представления рабочего потока и установить для них все необходимые атрибуты, а затем просто вызовите buildDrawingCache() и getDrawingCache() для создания растрового изображения.

Это отлично работает на некоторых устройствах, но в последнее время я столкнулся с несколькими устройствами, которые сбой со следующим сообщением:

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

Я понимаю, почему это происходит, поскольку некоторые телефоны должны иметь модифицированную реализацию для EditText, которая создает Handler и, следовательно, требуется сначала вызвать Looper.prepare().

Из того, что я читал в Интернете, нет проблемы с вызовом Looper.prepare() внутри рабочего потока, хотя некоторые заявили, что он очень не рекомендуется, но я не мог найти причину этого.

Кроме того, в большинстве сообщений, связанных с этой проблемой, говорится, что вы не должны раздувать представления в фоновом потоке, возможно, из-за следующих из официальной документации Android (процессы и потоки):

"Do not access the Android UI toolkit from outside the UI thread"
  • Каков рекомендуемый подход к решению этой проблемы?

  • Есть ли вред в вызове build/get drawingcache из основного потока? (С точки зрения производительности)

  • Будет ли вызов Looper.prepare() внутри рабочего потока решить эту проблему?

ИЗМЕНИТЬ

Чтобы подробно остановиться на моем конкретном требовании, у меня есть пользовательский интерфейс, состоящий из ImageView и пользовательского EditText поверх него, EditText может изменить его шрифт и цвет в соответствии с выбором пользователя, его можно увеличить/используя жестов "щепотка для увеличения", а также можно перетаскивать, чтобы пользователь мог переместить его поверх изображения.

В конечном итоге то, что я делаю, создает фиктивный вид внутри моего рабочего потока, используя те же самые значения (ширина, высота, положение), которые он имеет в настоящее время в пользовательском интерфейсе, а затем сгенерирует его рисование, исходное растровое изображение изображения снова декодируется из локального файл.

Как только два растровых изображения готовы, я объединю их в одно растровое изображение для будущего использования.

Итак, если это просто, есть ли что-то неправильное в выполнении следующего кода (из фона):

Вызов Looper.prepare() Создайте новое представление с контекстом приложения, вызовите measure() и layout() вручную, а затем создайте + getcachecache из него, то есть:

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();

Как это относится к ограничению без доступа к инструментарию Android UI из-за пределов пользовательского интерфейса, что может пойти не так?

Ответы

Ответ 1

В вашем случае вы можете сделать это, конечно, но будьте осторожны, только считывая значения из данных пользовательского интерфейса, чтобы избежать ошибок синхронизации.

Также вы не должны воссоздавать EditText из фонового потока, более эффективно напрямую обращаться к уже существующему:

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

Если у вас есть вопрос: почему это не рекомендуется рекомендациями Android, здесь, это хороший ответ SO на ваш вопрос.

Ответ 2

Вызов View.buildDrawingCache() вызывает Bitmap.nativeCreate, который может быть большим распределением, поэтому да, это может быть потенциально опасно для запуска в основном потоке. Я не вижу проблемы с вызовом Looper.prepare() в фоновом потоке. Однако неясно, чего вы пытаетесь достичь, и может быть лучшее решение вашей проблемы.

Ответ 3

Причина, по которой вы не должны использовать инструментарий UI из других потоков, заключается в том, что он не написан для потокобезопасности, он написан в предположении, что выполняется только один поток. Это означает, что очень сложно сказать, что может пойти не так, плохие эффекты, если таковые имеются, будут в основном происходить в не повторяющихся из-за определенного времени потоков. Ваше описание того, что вы пытаетесь сделать, не слишком понятно. В вашем случае я бы просто выделил большое растровое изображение и вложил в него текст. Почему вы используете EditText в первую очередь? Похоже, это своего рода хак, и хаки, как правило, ломаются в конце концов.

Ответ 4

Почему View.buildDrawingCache()? Как насчет использования View.draw(холст-холст) для рендеринга вручную в Canvas с поддержкой Bitmap? Метод кажется достаточно простым, чтобы не вызывать проблем с фоновыми потоками.

Ответ 5

EditText edit = (EditText)findViewById(R.id.edit);
edit.buildDrawingCache();
ImageView img = (ImageView)findViewById(R.id.test);
img.setImageBitmap(edit.getDrawingCache());

Lalit, когда вы пытаетесь создать кеш в методе onCreate, чертеж еще не произошел, поэтому в файле чертежа не должно быть ничего. Либо поместите метод buildDrawingChache в метод onClick. Или используйте следующий код в onCreate.

ViewTreeObserver vto = editText.getViewTreeObserver(); 
vto.addOnGlobalLayoutListener(new OnGlobalLayoutListener() { 
    @Override 
    public void onGlobalLayout() {
        editText.buildDrawingCache();
        } 
});

Ответ 6

Я также столкнулся с этой ошибкой несколько раз:

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

мое решение:

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();

просто создайте обработчик внутри рабочего потока, чтобы применить изменения данных к пользовательскому интерфейсу