КОТ++
Прикладное программирование
Системное программирование
Программирование микроконтроллеров

Большинство хороших программистов делают свою работу не потому, что ожидают оплаты или признания, а потому что получают удовольствие от программирования. (c) Линус Торвальдс

Поиск по тегам

Опубликовано
Комментарии 0

Недавно достаточное количество времени промучался с собиранием кокосовского проекта под android. И так небольшое количество туториалов, статей и видео зарубежных коллег, да и эти не точны, а вероятно относятся к старым версиям, поэтому и не работают. А на русском инструкций практически нет, а которые есть в основном бесполезны. Так как проблему сборки я решил, то хочу поделиться решением. Для особо внимательных – да да я снова занимаюсь разработкой игры.

И так, я предполагаю, что проект cocos2d-x у вас уже создан и вы уже посмотрели как выглядит этот проект на windows, к слову сборка именно под windows очень простая, достаточно открыть win32 проект в Visual Studio и скомпилить. Под android не так просто, нам понадобится Android Studio. Устанавливаем через Android Studio одну из актуальных Android SDK, и ещё ставим NDK, все эти действия легко делаются в Tools->Android->SDK Manager. Добавляем в переменную среды Path путь до cmake, cmake находится в папке с Android SDK %LocalAppData%\Android\sdk\cmake\3.10.2.4988404\bin (название папки с версией вероятно у вас может быть другое, сами посмотрите какое у вас). JDK нам устанавливать не нужно, в пакете Android Studio оно уже есть.

Выбираем NDK (если возникнут какие-то проблемы, то вероятно нужны ещё другие компоненты что на скрине, но у меня они уже были установлены)

Ставим путь до cmake

Теперь нам надо исправить следующие файлы: CMakeLists.txt (лежит в корне кокос проекта) и Android.mk (лежит в proj.android/app/jni). Если у вас HelloWorld проект и больше не добавляли никаких классов, то можно пропустить этот шаг. Суть в том, что нужно добавить недостающие пути к исходникам в эти файлы. В CMakeLists.txt нужно добавить и .cpp и .h файлы. В Android.mk добавить только все .cpp файлы, а также прописать все директории в которых есть .h файлы.

Пример исправлений в файле CMakeLists.txt:

<...> - тут остальная часть файла, которую менять не надо
list(APPEND GAME_SOURCE
     Classes/AbstractGameObject.cpp     
     Classes/AppDelegate.cpp     
     Classes/ChoiceMenu.cpp     
     Classes/Contestant.cpp     
     Classes/dragonBones/animation/Animation.cpp     
     Classes/dragonBones/animation/AnimationState.cpp     
     Classes/dragonBones/animation/BaseTimelineState.cpp     
     Classes/dragonBones/animation/TimelineState.cpp     
     Classes/dragonBones/animation/WorldClock.cpp     
     Classes/dragonBones/armature/Armature.cpp     
     Classes/dragonBones/armature/Bone.cpp     
     Classes/dragonBones/armature/Constraint.cpp     
     Classes/dragonBones/armature/DeformVertices.cpp     
     Classes/dragonBones/armature/Slot.cpp     
     Classes/dragonBones/armature/TransformObject.cpp  
     <...> - тут ещё куча файлов .cpp
     )
list(APPEND GAME_HEADER
     Classes/AbstractGameObject.h
     Classes/AppDelegate.h
     Classes/ChoiceMenu.h
     Classes/Contestant.h
     Classes/dragonBones/animation/Animation.h
     Classes/dragonBones/animation/AnimationState.h
     Classes/dragonBones/animation/BaseTimelineState.h
     Classes/dragonBones/animation/IAnimatable.h
     Classes/dragonBones/animation/TimelineState.h
     Classes/dragonBones/animation/WorldClock.h
     Classes/dragonBones/armature/Armature.h
     Classes/dragonBones/armature/Bone.h
     Classes/dragonBones/armature/Constraint.h
     Classes/dragonBones/armature/DeformVertices.h
     Classes/dragonBones/armature/IArmatureProxy.h
     Classes/dragonBones/armature/Slot.h
     Classes/dragonBones/armature/TransformObject.h
     <...> - тут ещё куча файлов .h
     )
<...> - остальная часть файла, которую менять не надо

Пример исправлений в файле Android.mk:

<…> – тут остальная часть файла, которую менять не надо
LOCAL_SRC_FILES := hellocpp/main.cpp \ ../../../Classes/AbstractGameObject.cpp \ ../../../Classes/AppDelegate.cpp \ ../../../Classes/ChoiceMenu.cpp \ ../../../Classes/Contestant.cpp \ ../../../Classes/dragonBones/animation/Animation.cpp \ ../../../Classes/dragonBones/animation/AnimationState.cpp \ ../../../Classes/dragonBones/animation/BaseTimelineState.cpp \ ../../../Classes/dragonBones/animation/TimelineState.cpp \ ../../../Classes/dragonBones/animation/WorldClock.cpp \ ../../../Classes/dragonBones/armature/Armature.cpp \ ../../../Classes/dragonBones/armature/Bone.cpp \ ../../../Classes/dragonBones/armature/Constraint.cpp \ ../../../Classes/dragonBones/armature/DeformVertices.cpp \ ../../../Classes/dragonBones/armature/Slot.cpp \ ../../../Classes/dragonBones/armature/TransformObject.cpp \ <…> – тут ещё куча файлов .cpp

LOCAL_C_INCLUDES := $(LOCAL_PATH)/../../../Classes \ $(LOCAL_PATH)/../../../dragonBones/ \ $(LOCAL_PATH)/../../../dragonBones/animation \ $(LOCAL_PATH)/../../../dragonBones/armature \ $(LOCAL_PATH)/../../../dragonBones/core \ $(LOCAL_PATH)/../../../dragonBones/events \ $(LOCAL_PATH)/../../../dragonBones/factories \ $(LOCAL_PATH)/../../../dragonBones/geom \ $(LOCAL_PATH)/../../../dragonBones/model \ $(LOCAL_PATH)/../../../dragonBones/parsers \ $(LOCAL_PATH)/../../../dragonBones/cocos2dx \ $(LOCAL_PATH)/../../../dragonBones/textures \ $(LOCAL_PATH)/../../../Classes/rapidjson \ $(LOCAL_PATH)/../../../Classes/rapidjson/error \ $(LOCAL_PATH)/../../../Classes/rapidjson/internal \ $(LOCAL_PATH)/../../../Classes/rapidjson/msinttypes \ <…> – тут ещё остальные директории
<…> – остальная часть файла, которую менять не надо

Лайфхак как добавить исходные файлы если у вас их очень много. Зайдите в cmd в папку с исходниками, наберите:

dir /B /s | find ".cpp"

Выведет список всех .cpp в проекте, осталось только подкорректировать текст автозаменой и добавить пути в CMakeLists.txt и в Android.mk.

Почти готово, теперь открываем папку proj.android в Android Studio как проект. Главное иметь достаточно места на диске, сборка съест около 2-3 гигов. Нажимаем Build->Build APK в Android Studio, ждём минут 15, после можем радоваться собранному APK. В итоге всё достаточно просто если есть инструкция.

Теги , , , ,
Автор

Опубликовано
Комментарии 0

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

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

context.getDataDir()
– этот метод вернёт скрытую директорию приложения, то есть файлы потом будут доступны только приложению, которое их записало. Хорошо, безопасность и всё такое, но, а что если нужно записать в галерею или документы, или просто в какое-то общее хранилище? Тогда мы должны писать во внешнюю помять и просить разрешения WRITE_EXTERNAL_STORAGE. В некоторых туториалах пишут, что это разрешение для записи на sd карту, но если следовать здравому смыслу, то под external понимается память, которая не internal, а internal – это память, которая доступна только одному приложению, которое и пишет эти файлы. То есть external – это не то внешнее что на sd карте, а то внешнее что доступно всем, то есть общее хранилище, которое нам и нужно. К слову, в Android Q уже не нужно это разрешение и там «гении» из гугла придумали что-то новое для разработчиков.

Добавляем разрешение, пишем код для записи в файл, но не работает. Ах да, с Android 6.0 у нас же появились runtime разрешения, и для записи во внешнее хранилище нужно запросить разрешение прямо из кода. Пишем:

//проверяем принято ли данное разрешение
if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED)
{   
        //запрашиваем разрешение у пользователя
        ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 0);
}

Ага круто, теперь то мы действительно можем записывать файлы. Пишем:
File path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS);
File file = new File(path, FILE_NAME);
FileWriter fileWriter = new FileWriter(file, true);
fileWriter.append(data);
fileWriter.flush();
fileWriter.close();

Проверяем на телефоне, файл есть, так теперь подключаем телефон к компьютеру и ищем наш файл. Файла нет, класс! Windows не видит файл или android не даёт увидеть файл? Ну, конечно, виноват android, кто же ещё. Смысл в том, что android должен просканить media, найти новый файл и тогда каким-то чудом он покажет файл винде. Делается это так:
MediaScannerConnection.scanFile(context, new String[]{filePath}, null, null);

Таким образом мы заставляем андроид просканить медиа и показать файл в windows. К слову при перезаписи файла, надо снова выполнять метод scanFile, иначе windows не видит изменений. Но, конечно, вышесказанное справедливо только для windows 7, на windows 10 я не проверял, а linux вероятно всего и так всё увидит, но линуксом не пользуются рядовые пользователи, поэтому обсуждать тут это не буду.

На этом всё, я думаю в статью ещё есть что добавить, поэтому если есть неясные моменты на данную тему, то спрашивайте в комментариях.

Теги , , ,
Автор

Опубликовано
Комментарии 0

Котострофа

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

Однажды код перестал работать и у заказчика, в итоге я предположил, что на андроиде с этим gps ужасная котострофа. Решили сделать функцию геолокации для пользователя на выбор, а не принудительной, чтобы те у кого gps не работает могли использовать обычный ввод своего адреса. Но сегодня я понял в чем проблема. Ненавистный gps у меня снова заработал после того как я откатил телефон назад к заводским настройкам. Смартфон стал пошустрее работать, и в том числе заработало gps.

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

Теги , , ,
Автор

Опубликовано
Комментарии 0

Не так давно передо мной стояла серьёзная задача парсинга данных с некоторых сайтов. Проблема была в том, что обычный HTTP запрос не поможет, т.к. нужно, чтобы на странице отработал javascript и построил данные на странице, чтобы мы потом их спарсили. С помощью обычного HTTP запроса возвращается почти пустая страница. Выход очевиден - делать запрос через браузер, ждать пока отработают скрипты на странице и передавать исходный код уже сформированной страницы парсеру. Долго я гулял по гуглу прежде чем найти рабочее решение поставленной проблемы. Поэтому хочу поделиться с вами тем, что в итоге у меня получилось. Ниже привожу фрагменты кода с ключевыми моментами.
webView.getSettings().setJavaScriptEnabled(true);
webView.addJavascriptInterface(new MyJavaScriptInterface(), "HTMLOUT");
//нужно, чтобы загружалась полная версия сайта, а не мобильная
webView.getSettings().setUserAgentString("Mozilla/5.0 (Windows NT 10.0; WOW64; rv:47.0) Gecko/20100101 Firefox/47.0"); 
webView.setWebViewClient(new WebViewClient()    
{
    @Override
    public void onPageFinished(WebView view, String url)
    {
        webView.loadUrl("javascript:window.HTMLOUT.processHTML('<html>'+"
        + "document.getElementsByTagName('html')[0].innerHTML+'</html>');");
    }

    @Override
    public void onReceivedError(WebView view, int errorCode, String description, String failingUrl)
    {
        super.onReceivedError(view, errorCode, description, failingUrl);
    }

	@Override
    public boolean shouldOverrideUrlLoading(WebView view, String url)
    {
        view.loadUrl(url);
        return true;
    }
});

private class MyJavaScriptInterface
{
    @JavascriptInterface
    public void processHTML(String html)
    {
        onPostExecute(html); 
        webView.stopLoading();
    }
}

public void execute()
{
    webView.loadUrl(url + urlQuery);
}
Методы execute и onPostExecute названы так, чтобы с получившимся классом можно было работать также как и с AsyncTask. Логика кода выше такая: браузер сообщает нам, что закончил свои дела по загрузке в событии onPageFinished, но теперь нам надо забрать исходный код страницы из браузера. Для этого прикручиваем JavaScriptInterface также как в коде выше. На stackoverflow.com находились различные вариации реализации этой хитрости, но почти ни один пример не работал. В каких-то тайных уголках гугла удалось найти решение и реализовать его, вот теперь делюсь хитрым приёмом с вами)

Теги , , ,
Автор

Опубликовано
Комментарии 0

Туки-Тук – это удобный сервис для поиска и предоставления доступного жилья в короткие сроки.

Функционал:
- регистрация;
- верификация аккаунта по email и телефону;
- создание объявления о сдаче жилья в аренду;
- поиск объявлений по различным параметрам;
- бронирование объявлений;
- возможность выбор наилучшего кандидата для снятия жилья;
- рейтинги и отзывы.

Ссылка на сайт: http://tuki-tuk.com
Ссылка google play: https://play.google.com/store/apps/details?id=com.varvar.tuki_tuk

Теги ,
Автор

Опубликовано
Комментарии 1

Используете много картинок в своём андроид приложении? И при этом бывает вылетает ошибка OutOfMemory связанная с показом картинок? В таком случае этот пост для вас.

Сам недавно столкнулся с такой проблемой, поэтому в этой статье могу предложить некоторое решение. В моём приложении картинки загружались с сервера и отображались сразу в большом количестве на экране смартфона. И когда хип переполнялся из-за большого количества картинок в памяти приложение падало, падение можно обойти, но картинки всё равно уже не отображаются там где надо. Самое очевидное решение это подгружать только те картинки, которые пользователь может просматривать в данный момент. Но определить какие из ImageView сейчас находятся в видимости пользователя, а какие нет тоже не простая задача. А ведь охото сделать простое и быстрое решение.

На stackoverflow.com предлагали ужимать картинки, а потом выводить на экран. В принципе неплохое решение, но чтоб отобразить сразу много картинок нужно применить 7/10 шакалов ко всем картинкам.

Также видел совет по увеличению хипа для приложения, прописать в манифесте:

android:largeHeap=«true»

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

Остаётся заюзать какую-нибудь библиотеку для подгрузки изображений из инета. На слуху у меня две таких либы: Picasso и Android-Universal-Image-Loader. Мне по некоторым причинам больше приглянулась вторая либа. Её то и попробуем использовать. Подключим в свой проект и пропишем код инициализации. Мне наиболее оптимальным кажется такой вариант (некоторые параметры были найден уже не вспомню на каком форуме):

        Executor downloadExecutor = Executors.newFixedThreadPool(5);
        Executor cachedExecutor = Executors.newSingleThreadExecutor();

ActivityManager am = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE); int memClass = am.getMemoryClass(); final int memoryCacheSize = 1024 * 1024 * memClass / 8; options = new DisplayImageOptions.Builder() .showImageOnLoading(R.drawable.ic_stub_image) .bitmapConfig(Bitmap.Config.RGB_565) .imageScaleType(ImageScaleType.IN_SAMPLE_INT) .cacheInMemory(true) .cacheOnDisc(true) .build(); File cacheDir = StorageUtils.getCacheDirectory(this); ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(getApplicationContext()) .taskExecutor(downloadExecutor) .taskExecutorForCachedImages(cachedExecutor) .memoryCache(new UsingFreqLimitedMemoryCache(memoryCacheSize)) // 2 Mb .diskCache(new LimitedAgeDiskCache(cacheDir, 52428800)) .imageDownloader(new BaseImageDownloader(this, 5 * 1000, 30 * 1000)) .defaultDisplayImageOptions(options) .build(); ImageLoader.getInstance().init(config);

Далее скачиваем и показываем картинку:

ImageLoader.getInstance().displayImage(imageUrl, imageView, MainActivity.options);

Но позже выясняется, что такой вариант особо не спасает. В логах у нас также будет OutOfMemory. А что если грузить сначала превьюшки для изображений (т.е. сжатые маленькие картинки), а если юзер просматривает картинку полностью, то в этот момент подгружать уже оригинал? Попробуем (код для подгрузки превьюхи):

ImageLoader.getInstance().displayImage(imageUrl, imageView, new ImageSize(80, 80));

Для подгрузки оригиналов воспользуйтесь кодом чуть выше. Проверяем, работает!

Данное решение не оптимизирует работу приложения так, как хотелось бы одержимому перфекционисту, но вполне побеждает OutOfMemory!

Теги , ,
Автор

Опубликовано
Комментарии 0

Разрабатывая одно приложение наткнулся на ошибку в переводе времени из одного часового пояса в другой.
Требовалось написать функцию перевода из текущей тайм зоны в тайм зону Москвы.

Получилась такая функция:

public Date getMskDateTime(Date d)
{ SimpleDateFormat dateFormatGmt = new SimpleDateFormat(«yyyy-MMM-dd HH:mm:ss»); dateFormatGmt.setTimeZone(TimeZone.getTimeZone(«GMT+3»));

SimpleDateFormat dateFormatLocal = new SimpleDateFormat(«yyyy-MMM-dd HH:mm:ss»); TimeZone tz = TimeZone.getDefault(); dateFormatLocal.setTimeZone(tz); try { return dateFormatLocal.parse(dateFormatGmt.format(d)); } catch (ParseException e) { return new Date(); } }

После были замечены баги, время переводится не всегда верно.
Проверяю у себя (моё локальное GMT+5) всё отлично работает, у другого же человека, у которого локальное GMT+6, время переводится неверно.
Перевожу у себя на GMT+6 и выявляю ту же ошибку. Перевожу на GMT+7 и опять всё верно переводит.
Т.е. при переводе на GMT+3: GMT+5 переводит на 2 часа, GMT+6 переводит опять на 2 часа (должен на 3), GMT+7 переводит на 4 часа как и должен.
Чудеса!
Ставлю у себя GMT+6. Вывожу то, что хранится в TimeZone.getDefault(). Вижу GMT+6.
Принудительно ставлю GMT+6 вместо default:

TimeZone.getTimeZone(«GMT+6»);

И всё снова верно работает. МАГИЯ!
Думаю, что это баг андроида. Тестил на своём 4.4.

Решил прикрутить небольшой костыль, чтобы избавиться от проблемы.
В итоге решение выглядит так:

public Date getMskDateTime(Date d)
{ SimpleDateFormat dateFormatGmt = new SimpleDateFormat(«yyyy-MMM-dd HH:mm:ss»); dateFormatGmt.setTimeZone(TimeZone.getTimeZone(«GMT+3»));

SimpleDateFormat dateFormatLocal = new SimpleDateFormat(«yyyy-MMM-dd HH:mm:ss»); TimeZone tz = TimeZone.getDefault(); long hours = TimeUnit.MILLISECONDS.toHours(tz.getRawOffset()); long minutes = TimeUnit.MILLISECONDS.toMinutes(tz.getRawOffset()) – TimeUnit.HOURS.toMinutes(hours); minutes = Math.abs(minutes); tz = TimeZone.getTimeZone(String.format(«GMT+%d:%02d», hours, minutes)); dateFormatLocal.setTimeZone(tz); try { return dateFormatLocal.parse(dateFormatGmt.format(d)); } catch (ParseException e) { return new Date(); } }

Теги , , ,
Автор

Опубликовано
Комментарии 0

Андроид приложение для водителей такси, позволяет выбрать интересующие заказы и взять на выполнение. Приложение является частью системы BasketTaxi.
Для использования приложение нужно сначала зарегистрироваться в системе как водитель такси. После авторизоваться под своим аккаунтом и можно начинать работу. Неавторизованным пользователям предоставляется возможность просмотреть все заказы, но без взятия на выполнение.
Имеется возможность выбрать интересующий город и категорию заказов (Новые, Горящие, Другие).

Приложение продолжает развиваться, провожу поддержку приложения и периодически добавляю новый функционал.

Ссылка: https://play.google.com/store/apps/details?id=basket.client

Теги ,
Автор

Опубликовано
Комментарии 0

Андроид приложение для заказа такси в городах: Абакан, Барнаул, Бердск, Бийск, Братск, Иркутск, Кемерово, Томск, Новосибирск, Томск, Улан-Удэ.

Главные функции программы:
- Точная стоимость поездки.
- СМС уведомления.
- Удобная форма для заказа такси.

Приложение продолжает развиваться, провожу поддержку приложения и периодически добавляю новый функционал.

Ссылка: https://play.google.com/store/apps/details?id=basket.calltaxi

Теги ,
Автор