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

Для некоторых людей программирование является такой же внутренней потребностью, подобно тому, как коровы дают молоко, или писатели стремятся писать. (с) Николай Безруков

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

Опубликовано
Комментарии 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

Теги ,
Автор