В дополнение к этому ответу вам нужно создать канал уведомления, прежде чем он сможет быть использован.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
/* Create or update. */
NotificationChannel channel = new NotificationChannel("my_channel_01",
"Channel human readable title",
NotificationManager.IMPORTANCE_DEFAULT);
mNotificationManager.createNotificationChannel(channel);
}
Также вам нужно использовать каналы только в том случае, если ваша targetSdkVersion 26 или выше.
Если вы используете NotificationCompat.Builder, вам также необходимо обновить бета-версию библиотеки поддержки: https://developer.android.com/topic/libraries/ support-library / revisions.html # 26-0-0-beta2 (чтобы иметь возможность вызвать setChannelId
в компоновщике сопоставления.)
Будьте осторожны, так как это обновление библиотеки повышает minSdkLevel до 14.
Я нашел отчет об ошибке очень похожего случая ( JDK-8143380 ), который был закрыт как «Не проблема» Стюарта Маркса:
Это тупик инициализации класса. Основной поток тестовой программы выполняет статический инициализатор класса, который устанавливает флаг для выполнения инициализации для класса; этот флаг остается установленным до завершения статического инициализатора. Статический инициализатор выполняет параллельный поток, который заставляет лямбда-выражения оцениваться в других потоках. Эти потоки блокируют ожидание завершения класса для инициализации. Однако основной поток блокируется, ожидая завершения параллельных задач, что приводит к тупиковой ситуации.
Тестовая программа должна быть изменена для перемещения логики параллельного потока вне статического инициализатора класса. Закрытие как не проблема.
blockquote>
Я смог найти еще один отчет об ошибке ( JDK-8136753 ), также закрытый как «Не проблема» Стюарта Маркса:
Это тупик, который возникает из-за того, что статический инициализатор Fruit enum плохо взаимодействует с инициализацией класса.
См. Спецификацию языка Java, раздел 12.4.2 для получения подробных сведений о классе инициализация.
http://docs.oracle.com/javase/specs/jls/se8/html/jls-12.html#jls-12.4.2
Вкратце, что происходит:
- Основной поток ссылается на класс Fruit и запускает процесс инициализации. Это устанавливает флаг инициализации в прогресс и запускает статический инициализатор в основном потоке.
- Статический инициализатор запускает некоторый код в другом потоке и ждет его завершения. В этом примере используются параллельные потоки, но это не имеет ничего общего с потоками.
- Код в другом потоке ссылается на класс Fruit, который проверяет флаг инициализации в процессе выполнения. Это заставляет другой поток блокироваться до тех пор, пока флаг не будет очищен. (См. Шаг 2 в JLS 12.4.2.)
- Основной поток заблокирован, ожидая завершения другого потока, поэтому статический инициализатор никогда не завершается. Поскольку флаг инициализации в процессе не очищается до тех пор, пока не завершится статический инициализатор, потоки зашли в тупик.
Чтобы избежать этой проблемы, убедитесь, что статическая инициализация класса завершена быстро, без заставляя другие потоки выполнять код, который требует, чтобы этот класс завершил инициализацию.
Закрытие как не проблема.
blockquote>
Обратите внимание, что FindBugs имеет открытую проблему для добавления предупреждения для этой ситуации.
Для тех, кто задается вопросом, где другие потоки, ссылающиеся на класс Deadlock
, Java lambdas ведут себя так, как вы написали это:
public class Deadlock {
public static int lambda1(int i) {
return i;
}
static {
IntStream.range(0, 10000).parallel().map(new IntUnaryOperator() {
@Override
public int applyAsInt(int operand) {
return lambda1(operand);
}
}).count();
System.out.println("done");
}
public static void main(final String[] args) {}
}
С обычными анонимными классами нет взаимоблокировки:
public class Deadlock {
static {
IntStream.range(0, 10000).parallel().map(new IntUnaryOperator() {
@Override
public int applyAsInt(int operand) {
return operand;
}
}).count();
System.out.println("done");
}
public static void main(final String[] args) {}
}
Существует прекрасное объяснение этой проблемы Andrei Pangin , датированное 07 апреля 2015 года. Здесь доступно , но оно написано на русском языке (я предлагаю просмотрите образцы кода в любом случае - они являются международными). Общая проблема - это блокировка во время инициализации класса.
Вот некоторые цитаты из статьи:
Согласно JLS , каждый класс имеет уникальную блокировку инициализации который фиксируется во время инициализации. Когда другой поток пытается получить доступ к этому классу во время инициализации, он будет заблокирован в блокировке до завершения инициализации. Когда классы инициализируются одновременно, можно получить тупик.
Я написал простую программу, которая вычисляет сумму целых чисел, что она должна печатать?
public class StreamSum {
static final int SUM = IntStream.range(0, 100).parallel().reduce((n, m) -> n + m).getAsInt();
public static void main(String[] args) {
System.out.println(SUM);
}
}
Теперь удалите parallel()
или заменить лямбда на вызов Integer::sum
- что изменится?
Здесь мы снова увидим тупик [были некоторые примеры блокировок в инициализаторах класса, ранее описанных в этой статье]. Из-за операций потока parallel()
выполняется в отдельном пуле потоков. Эти потоки пытаются выполнить тело лямбда, которое записывается в байт-код как метод private static
внутри класса StreamSum
. Но этот метод не может быть выполнен до завершения статического инициализатора класса, который ждет результатов завершения потока.
Что более разумно: этот код работает по-разному в разных средах. Он будет корректно работать на одном процессоре и, скорее всего, будет работать на многопроцессорной машине. Это различие связано с реализацией пула Fork-Join. Вы можете сами убедиться, что вы изменяете параметр -Djava.util.concurrent.ForkJoinPool.common.parallelism=N