LINUX.ORG.RU

Проблема в потоке android

 ,


0

1

Выполняется такой поток.


        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    wifi = (WifiManager) getActivity().getApplicationContext().getSystemService(Context.WIFI_SERVICE);
                    di = wifi.getDhcpInfo();
                    gateway = IntToStr(di.gateway);
                } catch (NullPointerException e) {
                    e.printStackTrace();

                }

                gateway_label.post(new Runnable() {
                    @Override
                    public void run() {
                        gateway_label.setText(gateway);
                    }
                });
            }

        }).start();
По идее делая .post выполнение считается потоко-безопасным. Но текст не отображается, то есть, дальнейшее использование gateway работает правильно. Но не отображается как надо в gateway_label. Из-за чего может быть, как исправить?

Ответ на: комментарий от kazufukurou

А что ещё может выбросить? Там же ошибка будет, если не все catch впишу, так получается что больше ничего не надо получать.

gateway volatile?

А этот вопрос мне не понятен.

u0atgKIRznY5
() автор топика
Ответ на: комментарий от kazufukurou

ошибка в IntToStr?

Не может быть. Там точно всё нормально, если пришло число адреса. Потом в другой функции используется gateway, и если бы gateway был ошибочен, то ничего бы не выполнилось, а так другое выполняется нормально, используя gateway как строку ip адресом.

u0atgKIRznY5
() автор топика
Ответ на: комментарий от u0atgKIRznY5

В твоем коде не видно как объявлен gateway, ты пишешь и читаешь эту переменную в разных потоках, java не гарантирует что соседний поток увидит изменения другого потока, с volatile гарантирует.

kazufukurou
()

Во-первых ты здесь канонически налажал с памятью. Ты удерживаешь сильную ссылку на активити, она в свою очередь держит ссылки на вьюшки, вьюшки на контекст и поехали... вниз по цепочке вплоть до самых недр. Во-вторых, в голую с потоками никто не работает, это очень херовая практика. Тебе надо:
1) Вынести тяжелую работу в другой поток
2) Быть уверенным в том, что все вызовы обновления UI дергаются из основного потока, иначе ты получишь CalledFromWrongThreadException.
В ведре есть AsyncTask, выглядит он, конечно, уродски, но свою работу выполняет. Поковыряй в его сторону. Можно еще заюзать Executor из Java + runOnUiThread, но это более велосипедно, кмк.

Jefail ★★★★
()
Ответ на: комментарий от kazufukurou

Попробую volatile, но используя gateway_label.post <- это как раз то, чтобы написать в главном потоке из другого потока.

u0atgKIRznY5
() автор топика

делая .post выполнение считается потоко-безопасным.

Ни разу, вообще и никакой связи. Потокобезопасность - это обмазывание блокировками и контроль доступа к данным (чтение / запись) в многопоточной среде. Post - это сугубо ведроидная штука. У тебя есть Looper, который привязан к основному потоку, looper обрабатывает сообщения из т.н. message queue. Когда ты делаешь .post - ты просто добавляешь message в эту самую очередь, который потом будет обработан looper'ом. Но это никаких гарантий потокобезопасности тебе не даёт.

Jefail ★★★★
()
Ответ на: комментарий от u0atgKIRznY5
try {
    final WifiManagar wifi = (WifiManager) getActivity().getApplicationContext().getSystemService(Context.WIFI_SERVICE);
    final DhcpInfo di = wifi.getDhcpInfo();
    final int gateway = IntToStr(di.gateway);
    gateway_label.post(new Runnable() {
         @Override
         public void run() {
             gateway_label.setText("wtf.ok[" + gateway + "]");
         }
    });
} catch (Exception e) {
     gateway_label.post(new Runnable() {
         @Override
         public void run() {
             gateway_label.setText("wtf.fail[" + e.getMessage() + "]");
         }
    });
}

что лейбл так покажет? (мб не скомпилится)

kazufukurou
()
Последнее исправление: kazufukurou (всего исправлений: 1)
Ответ на: комментарий от kazufukurou

Ничего не покажет, а gateway это String. Я же говорю, что gateway работает потом нормально, то есть в других функциях он используется и выполняется правильно.

u0atgKIRznY5
() автор топика
Ответ на: комментарий от u0atgKIRznY5

Учи матчасть. Из документации

public void onClick(View v) {
    new Thread(new Runnable() {
        public void run() {
            final Bitmap bitmap = loadImageFromNetwork("http://example.com/image.png");
            mImageView.post(new Runnable() {
                public void run() {
                    mImageView.setImageBitmap(bitmap);
                }
            });
        }
    }).start();
}
Bitmap - сильная финальная ссылка с локальной областью видимости. Кроме как из анонимного класса этого потока её больше никто не тронет. Это потокобезопасно (хоть и говнокод).

У тебя судя по написанию

wifi = (WifiManager) getActivity().getApplicationContext().getSystemService(Context.WIFI_SERVICE);
di = wifi.getDhcpInfo();
gateway = IntToStr(di.gateway);
Все 3 переменные объявлены с глобальной областью видимости, соответственно, если ты запустишь 2 потока одновременно, то значение каждой из них будет неопределенным, ибо возникнет race condition. В документации под потокобезопасностью имелся ввиду другой сценарий. Там несколько потоков могут захотеть отрисовать что-то в UI, но рисовать из другого потока нельзя, потому в качестве решения предлагается положить runnable в очередь, а looper уже эту очередь синхронно обработает. Одну комманду за другой. Вот что там имелось ввиду, я надеюсь ты понял свою ошибку.

Jefail ★★★★
()
Ответ на: комментарий от u0atgKIRznY5

Обычная ссылка в Java считается сильной. Это тебе тоже стоит изучить. Есть 4 типа: Strong, Weak, Soft, Phantom. Тип ссылки влияет на стратегию её сбора GC. В Android это особенно актуальная проблема.

public void onClick(View v) {
    new Thread(new Runnable() {
        public void run() {
            final Bitmap bitmap = loadImageFromNetwork("http://example.com/image.png");
            mImageView.post(new Runnable() {
                public void run() {
                    mImageView.setImageBitmap(bitmap);
                }
            });
        }
    }).start();
}
Вот, например здесь, внутри анонимного класса дергается глобальный филд mImageView. Предположим, что у тебя загрузка картинки из сети занимает секунд 15 (ну херовое у нас соединение, что поделать). Если за это время ты пару раз повернешь телефон - у тебя старое активити уничтожится и создастся новое (man activity lifecycle). Однако, из-за того, что анонимный класс держит неявную сильную ссылку на внешний класс и из него дергает ImageView, GC её не соберет и будет утечка памяти. Причем утечка будет иерархическая, потому что один сраный ImageView по цепочке потащит за собой всё, активити, контекст и так далее. Вариантов решения данной проблемы несколько. Но советую для начала действительно понять, что происходит под капотом и почему оно протекает.
https://www.youtube.com/watch?v=BkbHeFHn8JY

Jefail ★★★★
()
Последнее исправление: Jefail (всего исправлений: 1)
Ответ на: комментарий от u0atgKIRznY5

Разницы нет. К утечке приводит связка сильной ссылки и того, что Android уничтожает Activity со всеми потрохами при повороте. Можно, конечно, задать в манифесте ignore config changes, тогда при повороте уничтожения происходить не будет, но это повлечет за собой другие проблемы. Я обычно делаю конструкцию, когда обработчик асинхронного запроса удерживается как слабая ссылка (WeakReference). Результат асихронного запроса будет потрачен (но это так и так произойдет), но зато ты не протечёшь по памяти.

Jefail ★★★★
()
Ответ на: комментарий от u0atgKIRznY5

Что именно подробнее? Про механизмы работы - это тебе про GC надо читать. Я могу только чуть позже пример накидать. Если есть телега - напиши мне. @v_sulimov

Jefail ★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.