Введение


Доброго времени суток. В этой статье вы узнаете о том, какие проблемы могут возникнуть при разработке android-приложений. На написание этой статьи меня побудили комментарии из прошлой статьи, кстати вот она: моя первая статья. Спасибо за советы! Ну, пора начинать…

Проблема 1


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

Решение


В манифесте нужно выбрать собственную тему, за это отвечает атрибут:

 android:theme="@style/AppTheme">

Заходим в values/styles.xml:



В этом файле находится тема приложения. Сначала я сделал приложение во весь экран, через наследование темы — android:Theme.Black.NoTitleBar.Fullscreen, потом изменил стиль диалоговых окон таким образом:

<item name="android:alertDialogStyle">@style/alertStyle</item>

но такой вариант не работает… За стиль диалоговых окон отвечает именно такой атрибут:


<item name="android:alertDialogTheme">@style/alertStyle</item>

Теперь уже можно определить сам стиль в том же файле:


    <style name="alertStyle" parent="android:Theme.Dialog">
        <item name="android:layout_gravity">center</item>
        <item name="android:background">#64abcdf5</item>
    </style>

Весь файл

<resources>
    <style name="AppTheme" parent="android:Theme.Black.NoTitleBar.Fullscreen">
        <item name="android:alertDialogTheme">@style/alertStyle</item>
    </style>

    <style name="alertStyle" parent="android:Theme.Dialog">
        <item name="android:layout_gravity">center</item>
        <item name="android:background">#64abcdf5</item>
    </style>
</resources>


В результате получаем:

Красивое диалоговое окно


Проблема 2


Эта проблема связана с сохранением данных. Сначала я находил решения связанные с созданием баз данных, но это для меня было чем-то страшным. Я искал вариант проще. Наконец набрёл в официальной документации на класс SharedPreferences. С помощью него очень легко сохранять данные. Рассмотрим на примере сохранение времени.

SaveObjectRating
public final class SaveObjectRating {
    private Date[] convertedTime;
    private SharedPreferences sharedPref;
    private long[] time;

    SaveObjectRating(Context context) {
        time = new long[10];
        convertedTime = new Date[10];
        sharedPref = context.getSharedPreferences("RatFile", MODE_PRIVATE);
        load();
    }

    public Date[] getConvertedTime() {
        return convertedTime;
    }

    public void setNewTimeOnIndex(long a, int index) {
        if (a < time[index] && a != 0) {
            time[index] = a;

        }
    }
      //метод сохранения данных
    public void save() {
        SharedPreferences.Editor editor = sharedPref.edit();
        for (int i = 0; i < 10; i++) {
            editor.putLong("key" + i, time[i]);
        }
        editor.apply();
    }

    //метод загрузки данных из хранилища
    private void load() {
        for (int i = 0; i < 10; i++) {
            time[i] = sharedPref.getLong("key" + i, 3599);
        }
    }
    //Метод преобразования 
    void createConvertedTime() {
        for (int i = 0; i < 10; i++) {
            convertedTime[i] = new Date();
            long min = time[i] / 60;
            long sec = time[i] % 60;
            convertedTime[i].setMinutes((int) min);
            convertedTime[i].setSeconds((int) sec);
        }
    }
}


Данный класс используется для сохранения потраченного игроком времени на прохождение уровня. При создании объектов этого класса в них будут уже загруженные данные, потому что в конструкторе класса вызывается метод load().

Новое потраченное время можно задать методом:

setNewTimeOnIndex(long a, int index)

Он изменяет данные только в том случае, если пользователь потратил на прохождение меньше времени.Чтобы сохранить данные нужно вызвать метод save().

И проблема сохранения данных решена без создания баз данных.

Проблема 3


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

Проблема 4


Наверное самая сложная проблема, потому что до сих пор каким бы решение я не пользовался приходится ставить куда-нибудь костыль, заключается в том, чтобы реализовать определение текущего фрагмента. Первое, что пришло мне в голову — это использовать статическую переменную. Как вариант сойдёт, но если фрагментов будет очень много, то создаются огромные выборки статической переменной и начитаешь путаться, какое значение отвечает за какой фрагмент. Следующий вариант — это определять на каком мы сейчас фрагменте через его тип (использование ключевого слова языка программирования Java — instanceof). На первый взгляд должно работать, но не работает. Мне до конца так и не удалось разобраться почему это работает не так как надо. (оно работает, но с ошибками...)

Итак, я попробовал ещё один вариант — проверять какой фрагмент виден на экране. Работает так как надо, если использовать его вместе с статической переменной.

MainActivity.class
@Override
    public void onBackPressed() {
        System.out.println("MainActivity:onBackPressed()");
        if (idTheCurrentFragment == 4) {
            finish();
        }
        if (start.isVisible()) {
            System.out.println("Вы нажали назад в главном меню приложения");
            idTheCurrentFragment = -1;
            finish();
        } else if (game1vs1.isVisible() || gameCompany.isVisible()) {
            confirmation(); //Метод создаёт диалоговое окно с выбором.
        } else if (idTheCurrentFragment == -1) {
            System.out.println("Вы нажали кнопку назад");
            super.onBackPressed();
        }
    }


Задача состояла в том, чтобы при нажатии системной кнопки назад игрока спрашивали уверен ли он в том, что хочет покинуть игру. Если игрок соглашается, то вызывается метод экземпляра класса FragmentManager popBackStack("..."), статическая переменная выставлялась в значение 4, при котором при нажатии на кнопку ещё раз приложение завершает работу. Такое решение меня устроило, но было бы интересно как это можно сделать ещё лучше.

Проблема 5


Проблема динамической смены фрагментов. К примеру, есть фрагмент А и при нажатии по кнопке, которая находится в размете этого фрагмента, должен открываться следующий экран, то есть фрагмент B. Я попробовал вариант — в классе активности создал обработчик событий нажатия на все кнопки в приложении. Во всей разметке я для всех кнопок указал в атрибуте android:onClick="..." созданный в активности обработчик событий. Для кнопок обязательно нужно указать id, чтобы обработчик понимал с какой кнопкой он имеет дело.

Код обработчика
public void onClick(View v) {
        int id = v.getId();
        switch (id) {
            case R.id.exit: {
                onBackPressed();
                break;
            }
            case R.id.info: {
                fm.beginTransaction().replace(R.id.fragment_container, infoFragment).addToBackStack(null).commit();
                break;
            }
            case R.id.about_game: {
                fm.beginTransaction().replace(R.id.fragment_container, aboutGameFragment).addToBackStack(null).commit();
                break;
            }
            case R.id.about_authors: {
                fm.beginTransaction().replace(R.id.fragment_container, aboutAuthorsFragment).addToBackStack(null).commit();
                break;
            }
        }


Сами кнопки не создаются в java коде, только в xml. Да, да это работает. В активности есть экземпляр класса FragmentManager, чтобы выполнить транзакцию, то есть поменять текущий фрагмент.

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

                SendingActivity message = (SendingActivity) getActivity();
                message.send(''сообщение для активности'');

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

@Override
    public void send(int a) {
        switch (a) {
            case 0: {
                //Открываем фрагмент первого режима игры
                break;
            }
            case 1: {
                //Открываем фрагмент второго режима игры
                break;
            }
            case 2: {
                //Открываем фрагмент третьего режима игры
                break;
            }
            case 3: {
                //Открываем фрагмент четвёртого режима игр
                break;
            }
            case 4: {
                //Закрываем приложение(Кнопка выход)
                finish();
                break;
            }
        }

Проблема 6


Это проблема осталась так не решённой. Я надеюсь найдутся какие-нибудь опытные разработчик, которые смогут поделиться своим опытом в комментариях. Дело в том, что часто бывает нужно импортировать свою картинки svg-формата в среду разработки, но делать по одной картинки очень долго, даже если натренироваться… История была такой, я мучился вставлял 80 картинок в среду разработки, а потом оказалось, что их нужно переделывать…

Заключение


Молодцы те, кто дочитал до конца. Итак, я проделал длинный путь обхода этих проблем, чтобы на моих ошибках научились другие и поделился своим опытом с начинающими android-разработчиками. Для примеров я использовал написанных мною приложения. Вот ссылки на них:
play.google.com/store/apps/details?id=com.thekingames.memorize
play.google.com/store/apps/details?id=com.thekingames.warrior
Я надеюсь, что моя статья была для вас полезна. Всем удачи!
Поделиться с друзьями
-->

Комментарии (32)


  1. AllexIn
    17.02.2017 20:37
    +3

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

    Откройте для себя правило: никаких магический чисел в коде. И проблема уйдет сама собой.


  1. trilodi
    17.02.2017 20:54

    Проблема 5

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

    Ну тут обычно все наоборот, все обычно начинают с SharedPreferences, а потом уходят в сторону БД, как бы логично от простого к сложному. Просто нужно понимать разницы, SharedPreferences предназначен больше сохранения состояний/настроек, можно конечно его и под все подряд использовать, но это не есть правильно.
    Даже если быть самоучкой то первый поиск в гугле приводит к SharedPreferences https://www.google.se/webhp?sourceid=chrome-instant&ion=1&espv=2&ie=UTF-8#q=%D1%81%D0%BE%D1%85%D1%80%D0%B0%D0%BD%D0%B5%D0%BD%D0%B8%D0%B5+%D0%B2+android+java


    1. ureneva1999
      18.02.2017 16:49
      -3

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

      Именно это я и описывал дальше.
      Ещё один вариант, предлагаемый документацией Google — это через объект интерфейса, то есть фрагмент обрабатывает нажатие на свои кнопки и при нажатии создаётся объект интерфейса, который реализует активность
      … Умейте дочитать до конца.


      1. trilodi
        18.02.2017 18:10
        +1

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


        1. trilodi
          18.02.2017 18:23

          К слову дочитал статью до конца, некоторые банальные моменты даже перечитал


  1. shakagamii
    17.02.2017 21:30

    А что вы отображаете во фрагментах можно узнать? Просто судя по вашему предыдущему посту, у вас приложения на 3-4 активити без какой-то сложной графики. Не могу понять, что там может тормозить.

    System.out.println(«MainActivity:onBackPressed()»);

    Ну не так же логи выводятся в андроиде то) Вот, почитайте.
    if (idTheCurrentFragment == 4) {

    Используйте константы же. В этих цифрах запутаться легче легкого.

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


    1. ureneva1999
      17.02.2017 21:36
      -1

      приложения на 3-4 активити без какой-то сложной графики. Не могу понять, что там может тормозить.

      Тут наверное дело не в мощности, а в версии андроид. На телефоне под андроид 4.1 видно как создаётся ещё одно окно. И ещё я сравнивал по ОЗУ. Фрагменты экономнее.


      1. trilodi
        17.02.2017 21:54

        И ещё я сравнивал по ОЗУ. Фрагменты экономнее.

        Исключительно на момент старта. Если архитектура правильная то разницы вы абсолютно никакой не увидите. К примеру переделывал свой, довольно большой проект (более 30 уникальных активити) на фрагменты, переделывал исключительно из-за навигации. Так вот по производительности особую разницу я не заметил. Если у вас ощутимо тормозит запуск новой активности, то стоит обратить внимание где именно происходит утечка, видимо что то в основном потоке нагрузочное стартует


        1. trilodi
          17.02.2017 22:04

          К слову, фрагмент на то и называется фрагментом, что он заменяет часть GUI, за новое окно отвечает именно Activity. Нужно понимать разницу и разделять эти понятия, каждый метод предназначен для своей задачи


      1. shakagamii
        17.02.2017 22:38
        +1

        На телефоне под андроид 4.1 видно как создаётся ещё одно окно.

        До андроида 3.0 вообще жили на одних активити, и ничего, делали плавные приложения)
        И ещё я сравнивал по ОЗУ. Фрагменты экономнее.

        Пользователи себе телефоны с 3+гб оперативы покупают, чтобы мы им по 5-10мб экономили чтоли?)

        Можно увидеть Методы onCreate и onStart одного из ваших фрагментов? Просто интересно что вы там загружаете.
        К примеру, если вы там каждый раз дергаете SharedPref подобным образом
        sharedPref = context.getSharedPreferences(«RatFile», MODE_PRIVATE);

        это будет давать лаг.
        SharedPref лучше вообще инициализировать один раз при запуске, через контекст приложения, а потом обращаться к уже созданному объекту.


        1. trilodi
          18.02.2017 05:55

          Я думаю SharedPref тут меньше из зол, он даже не даст настолько ощутимого эффекта о котором говорит автор, там что-то по серьезднее запускается. Еще подобный косяк кстати был замечен на сдк >=20, там в анимации активити при переходах иногда бывают затупы, ощущение что они искуственные чтобы перевести все на фрагменты


          1. shakagamii
            18.02.2017 18:44

            Ну а чо, когда 3 андроид вышел, все и так кинулись использовать эти фрагменты. Казалось, что делать 1 активити и 150 фрагментов в нем, это спасение. Потом правда попустило)


            1. trilodi
              18.02.2017 18:54

              Если мне память не изменяет, то 3 андроид только для планшетов был. И там гугл сам чуть ли не настаивал на использовании фрагментов. Просто там дизайн в приложениях был таков что менялись части активити, а не вся активити. То есть фрагменты изначально и были созданы для того что бы изменить часть активити не прибегая к полной ее перезагрузки (заменить ее фрагмент), а так же фрагменты были и есть предназначены для повторного использования кода. Например вам нужно показать один и тот же участок с логиков в разных окнах, тут на помощь как раз и приходят фрагменты. Или что бы в 1 активити не плодить тонну заменяемых объектов, которые могут замедлить работу приложения. Но фрагменты никак не панацея. Нужно уметь разделять понятие фрагментов и активити. Активити это окно грубо говоря, а фрагмент — это фрагмент этого окна.

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

              Увы такая мода если новое что то выходит это используют повсеместно, потом начинают осознавать что это не есть хорошо


        1. ureneva1999
          18.02.2017 16:33
          -1

          Объект рейтинга я создаю только один раз. Лагов ни каких нету. Просто анимация создания активити очень некрасива. Я от неё избавился через фрагменты.


          1. shakagamii
            18.02.2017 18:40
            +1

            Изменили бы ее, хотя бы так для начала)

            ....
            startActivity(intent); //стартуем активити
            overridePendingTransition(R.anim.fadein, R.anim.fadeout); //меняем анимацию запуска/закрытия активити
            


            1. ureneva1999
              19.02.2017 16:54
              -1

              Проверил ваше решение и нашёл только единственный минус — при каждой транзакции нужно вызывать:
              overridePendingTransition(R.anim.animation,R.anim.animation);
              Файл R.anim.animation пустой, чтобы не было никакой анимации.Мне нравиться ваш способ решения проблемы.


              1. trilodi
                19.02.2017 21:48

                Вообще это общепринятый способ решения, который описан в документации


  1. AlexandrHrybuk
    18.02.2017 16:25

    По поводу 4 проблемы. Я использую конструкцию
    if(currentFragment.getClass().equals(FragmentName.class){...}
    Я в разработке только недавно, может эта конструкция и не есть правильной, но работает)


    1. ureneva1999
      18.02.2017 16:31
      -1

      Спасибо за ваш комментарий.Мне нравиться ваш способ и в дальнейшем буду его использовать


      1. trilodi
        18.02.2017 17:55

        Так вы же вроде описали что делали сравнение класса и этот метод ведет себя не адекватно


        1. ureneva1999
          18.02.2017 18:04

          Я использовал ключевое слово instanceof, а не сравнение классов.


  1. vvgladoun
    18.02.2017 16:26

    Явно что-то делается не так, раз в простом приложении активити тормозит на старте так, что это видно. Фрагменты использовать можно и нужно, но лепить все приложение в одной активити — явно другая крайность. Опять же, если хотите, чтобы у каждого фрагмента был свой идентификатор в стэке — используйте тэги (в качестве тэгов можно использовать статические переменные с понятным названием в каждом фрагменте: addToBackStack(YOUR_FRAGMENT_TAG))

    N2. Запихивать все подряд в shared prefernces — не самая лучшая идея. Тяжело с SQLite, структура данных простая и объемы данных небольшие, либо нет большого количества связей таблиц? — попробуйте realm.

    N5. По поводу кнопок — кнопки можно создавать и в XML и программно и как вашей душе угодно, а есть еще такая замечательная вещь как data binding… Если надо действительно менять фрагмент в той же активити по кнопке — реализуйте callback в активити. Если это принципиально новое нечто — создавайте интент под новое активити.

    N6. Про картинки — если речь идет о добавлении как VectorDrawable, то можно используя third party конвертеры перевести все ваши SVG в XML и потом скопировать при закрытой AS в нужную папку (res\drawable).

    Сам весьма «молодой» в мобильной разработке. Из личного опыта могу посоветовать послушать последние google IO, все, что касается технических выступлений + codelabs. Разобраться с архитектурой приложений — MVC и MVVM, и как они могут быть реализованы. Идеальное активити — пустое активити, выносите логику отдельные классы — это поможет и с тестированием и с масштабированием в дальнейшем. Ну и крайне рекомендую разобраться со всеми средствами профилирования и анализа производительности — посмотрите видео из серии performance patterns на официальном канале Google Developers в youtube, пройдите курс от гугла на Udacity — многое откроется по проблеме N3. Курсы из Nano degree на Udacity вообще очень неплохи для начинающих — они записаны самими гугловцами и доступны бесплатно, если вам не нужна красивая бумажка об окончании ND. Удачи!


    1. trilodi
      18.02.2017 18:03

      попробуйте realm.

      Если данные типа настроек, или сохранение состояния какой либо переменной, то тут лучше использовать SharedPreferences. Реалм очень большой, если для проекта 6-7 мб лишних не проблема то реалм идеальный вариант, если проект весит меньши библиотеки то реалм зло. Я в своем проекте из-за этого использовал Sugar ORM, она не такая шустрая, но если правильно распределить потоки, то особо не заметно, но принцип работы и синтаксис аналогичен реалму, и вес прибавляется всего пара сотен килобайт. Просто пользователи ругались на размер, а у конкурентов был размер на много ниже, из-за этого пришлось попрощаться с реалмом, и визуально в скорости не проиграл


    1. shakagamii
      18.02.2017 19:00

      Тяжело с SQLite...

      ORM же для этого придумали. Практически любая библиотека дает примерно такое же удобство в использовании, а разница в скорости для большинства проектов будет не существенна.

      N5. По поводу кнопок — кнопки можно создавать и в XML и программно и как вашей душе угодно, а есть еще такая замечательная вещь как data binding…

      Воу, это же мрак, особенно для новичка. Сколько не общался с людьми, все только и плюются от этой «фичи».
      Лучше уж рассказать про тот же Butter Knife, который делает примерно тоже самое. Но в разы удобнее и понятнее.


      1. vvgladoun
        18.02.2017 23:58

        Не так страшен databinding (далее db) как его малюют, если никакую логику в XML не городить. Создать на каждый стандартный для вашего приложения кусок UI по вью и по модели, привязанной через db. Далее в xml фрагмента (или активити) как в конструкторе накидываем созданные вью и фрагмент байндим на ViewLogic класс. В классе фрагмента (или активити) только инициализируем наш VL класс (еще лучше — инжектим Dagger-ом). А во VL выполняем всю логику и настраиваем/меняем модели наших «кусков» как душе угодно. При разработке достаточно большого приложения получаем весьма удобный инструментарий, легко масштабируемый и очень легко покрываемый юнит тестами. (P.S. ButterKnife хорош, его использовали до того, как перешли на db)


        1. shakagamii
          19.02.2017 01:20
          +1

          Ну, к MVVM душа лежит не у всех)
          Все равно выглядит слишком сложно, а все ради чего, чтобы уйти от findbyid, onclicklistener`or да нескольких кастомных атрибутов? Ну не знаю. Реально интересная вещь, это слежение за данными и автоматическое изменение состояния view, но опять же, без данной библиотеки это просто еще 1 строчка в коде. Про KISS тоже забывать не нужно)

          p.s. Хотя честно говоря, мое знакомство с databinding было уже довольно давно, может они там чего нового придумали, но я увы не в курсе. А все выше сказанное, так, мысли в слух, не более.


  1. Fast_IQ
    18.02.2017 16:26

    Для проблемы 6 можно легко нагуглить решение SVG to XML (SVG2andrid) вот например можно сразу несколько файлов обрабатывать.


    1. ureneva1999
      18.02.2017 20:14
      -1

      Спасибо за ваш комментарий. Вы облегчили мне жизнь раз в 20.


  1. jawaharlalnehru
    18.02.2017 16:34

    Проблема 5:

    если оба фрагмента принадлежат одной активити, то создаёте в этой активити такой метод:

        public void startFragment(Fragment fragment, boolean addToBackStack) {
            FragmentManager fragmentManager = getSupportFragmentManager();
            FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
            fragmentTransaction.replace(R.id.container, fragment);
    
            if(addToBackStack) {
                fragmentTransaction.addToBackStack("frame");
            }
            else {
                fragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
            }
    
            fragmentTransaction.commit();
        }
    


    потом во фрагменте А создаёте поле вашей активити:

    MainActivity activity;
    


    в методе onCreateView фрагмента А инициализируете её:

    activity = (MainActivity) getActivity();
    


    а в конце в обработчике нажатия кнопки во фрагменте А:

            buttonToFragmentB.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    FragmentB fragment = new FragmentB();
                    activity.startFragment(fragment, true);
                }
            });
    


    вуа, как говорится, ля. Бонусом параметром boolean addToBackStack определяете возможность возврата из фрагмента Б во фрагмент А по нажатию кнопки «Back».


  1. Valle
    18.02.2017 19:43
    -1

    Для проблемы 6 используйте поддержку VectorDrawable в студии — это генерация картинок в билдтайм для старых андроидов и использование svg практически напрямую для api >=21. https://android-developers.googleblog.com/2016/02/android-support-library-232.html


    1. ureneva1999
      18.02.2017 20:13
      -1

      Спасибо за ваш комментарий, разобрался.


  1. andreich
    22.02.2017 10:52

    что касается импорта пачки svg, то можно воспользоваться вот этим плагином, он ковертнет svg в Vector drawable