Журнал LinuxFormat - перейти на главную

LXF157:ARM и Android:Про­грам­ми­ро­ва­ние

Материал из Linuxformat
Перейти к: навигация, поиск


Ваши программные наработки не пропадут даром

ARM и Android: Про­грам­ми­ро­ва­ние

Часть 3: Ан­д­рей Бо­ров­ский счи­ты­ва­ет дан­ные из ви­део­бу­фе­ра Android, уг­луб­ля­ясь в ме­ха­низм взаи­мо­дей­ст­вия при­ло­же­ний Linux и сис­те­мы Android.

Наш эксперт

Ан­д­рей Бо­ров­ский В 14 лет сломал школьную локальную сеть. И это оказалось только началом.

Нау­чив­шись счи­ты­вать дан­ные из ви­део­бу­фе­ра Android и вы­во­дить гра­фи­ку на эк­ран уст­рой­ст­ва, вы са­ми смо­же­те на­пи­сать про­грам­му, ко­то­рую на Google Play про­да­ют за день­ги. Для это­го в на­ше блю­до, при­го­тов­лен­ное на C, при­дет­ся до­ба­вить ще­пот­ку Java.

Сто­ит от­ме­тить, что от ре­ли­за к ре­ли­зу в со­ста­ве средств раз­ра­бот­ки для Android по­яв­ля­ет­ся все боль­ше воз­мож­но­стей взаи­мо­дей­ст­вия с сис­те­мой, пред­на­зна­чен­ных для С и С++. Ве­ро­ят­но, раз­ра­бот­чи­ки Android уже по­ня­ли, что они несколь­ко по­го­ря­чи­лись, оста­вив воз­мож­ность раз­ра­бот­ки для сво­ей плат­фор­мы толь­ко на Java. Уже сей­час под Android мож­но на­пи­сать пол­но­цен­ное при­ло­жение на С и С++, не со­дер­жа­щее ни од­ной строч­ки ко­да на Java. Прав­да, код Java все рав­но бу­дет до­бав­лен в это при­ло­жение (ком­по­нов­щи­ком) для то­го, что­бы обес­пе­чить его взаи­мо­дей­ст­вие с осталь­ны­ми ком­понен­та­ми сис­те­мы. В бу­ду­щем, воз­мож­но, мы уви­дим ин­тер­фейс про­грам­ми­ро­вания, пред­на­зна­чен­ный ис­клю­чи­тель­но для язы­ков, ком­пи­ли­руе­мых в ма­шин­ные ко­ды.

Про­грам­ми­ро­вание для Android при по­мо­щи стан­дарт­ных средств взаи­мо­дей­ст­вия С++ и Java под­роб­но опи­са­но в до­ку­мен­та­ции и со­про­во­ж­да­ет­ся при­ме­ра­ми, так что оста­нав­ли­вать­ся на нем мы не бу­дем. Вме­сто это­го мы рас­смот­рим неко­то­рые аль­тер­на­тив­ные ме­то­ды и сред­ст­ва раз­ра­бот­ки, ко­то­рые хо­тя и не яв­ля­ют­ся ча­стью Android API, но, тем не менее, по­зво­ля­ют ре­шать мно­гие за­да­чи, возникаю­щие пе­ред про­грам­ми­ста­ми.

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

На ри­сун­ке по­ка­за­на при­бли­зи­тель­ная схе­ма взаи­мо­дей­ст­вия ком­понен­тов гра­фи­че­­ской сис­те­мы Android ме­ж­ду со­бой. Эту схе­му мож­но раз­де­лить на три уров­ня. На са­мом верхнем уровне рас­по­ла­га­ют­ся при­ло­жения Android, на­пи­сан­ные на Java (воз­мож­но, с ис­поль­зо­ванием биб­лио­тек, на­пи­сан­ных на C и C++ и вы­зы­вае­мых с по­мо­щью JNI). Ни­же на схе­ме по­ка­зан ин­тер­фейс, пред­на­зна­чен­ный для ор­ганиза­ции взаи­мо­дей­ст­вия гра­фи­че­­ской сис­те­мы Java и яд­ра Linux. Этот ин­тер­фейс со­сто­ит из несколь­ких ком­понен­тов, ка­ж­дый из ко­то­рых реа­ли­зо­ван в соб­ст­вен­ной биб­лио­те­ке, на­пи­сан­ной на C или C++. Дан­ные биб­лио­те­ки яв­ля­ют­ся обыч­ны­ми раз­де­ляе­мы­ми биб­лио­те­ка­ми Linux, и на­ши про­грам­мы мо­гут по­лу­чить пря­мой доступ к лю­бой из них, хо­тя поль­зы от это­го бу­дет, ве­ро­ят­но, немно­го.

Об­ра­ти­те внимание на ком­понен­ты SurfaeFlinger и PixelFlinger. По ка­кой-то при­чине сло­во «flinger» по­лю­би­лось раз­ра­бот­чи­кам Android, и они ши­ро­ко его ис­поль­зу­ют. На русский язык точнее все­го бы­ло бы пе­ре­вес­ти эти на­звания как «жонг­лер по­верх­но­стя­ми» (име­ют­ся в ви­ду вир­ту­аль­ные по­верх­но­сти, на ко­то­рые при­ло­жения вы­во­дят гра­фи­ку) и «жонг­лер пик­се­ля­ми». По­ми­мо про­че­го, ком­понен­ты про­ме­жу­точ­но­го слоя вы­пол­ня­ют про­грамм­ную эму­ля­цию от­сут­ст­вую­щих ком­понен­тов гра­фи­че­­ско­­го обо­ру­до­вания. На­при­мер, PixelFlinger мо­жет эму­ли­ро­вать под­держ­ку трех­мер­ной гра­фи­ки, ес­ли в сис­те­ме от­сут­ст­ву­ет ап­па­рат­ная под­держ­ка 3D. К это­му же уров­ню я отнес фай­лы-уст­рой­ст­ва, пре­достав­ляю­щие непо­сред­ст­вен­ный доступ к ви­део­бу­фе­ру сис­те­мы, хо­тя, воз­мож­но, их сле­до­ва­ло бы по­мес­тить уровнем ниже.

Как прой­ти в биб­лио­те­ку?

Ес­ли вы планируе­те под­клю­чать к при­ло­жению на­пря­мую та­кие биб­лио­те­ки, как libflinger.so или libsufrfaceflinger.so, то имей­те в ви­ду, что ском­по­но­вать при­ло­жение с эти­ми биб­лио­те­ка­ми на эта­пе сбор­ки не уда­ст­ся. Android NDK про­сто ниче­го о них не зна­ет.

Един­ст­вен­ный под­хо­дя­щий спо­соб – восполь­зо­вать­ся ме­то­дом яв­ной за­груз­ки биб­лио­тек во вре­мя вы­полнения про­грам­мы, с по­мо­щью функ­ций dlopen(), dlsym() и dlclose(). При этом вам все рав­но по­на­до­бят­ся за­го­ло­воч­ные фай­лы со­от­вет­ст­вую­щих биб­лио­тек. Вы най­де­те их в со­ста­ве NDK, в раз­де­ле ис­ход­ных тек­стов Android.

В со­от­вет­ст­вии с по­ка­зан­ной трех­уровневой сис­те­мой, у раз­ра­бот­чи­ка при­ло­жений на С и С++ есть три пу­ти взаи­мо­дей­ст­вия с гра­фи­че­­ски­­ми ком­понен­та­ми Android.

(thumbnail)
Схе­ма взаи­мо­дей­ст­вия при­ло­же­ния Android и гра­фи­че­ских ком­по­нен­тов.

Пер­вый путь за­клю­чат­ся в том, что­бы ис­поль­зо­вать код, на­пи­сан­ный на C и C++ внут­ри при­ло­жения Java, иг­раю­ще­го роль оберт­ки. Этот путь хо­рош тем, что при­ло­жение ве­дет се­бя по пра­ви­лам, уста­нов­лен­ным сис­те­мой, а зна­чит, в про­цес­се его ра­бо­ты мы мо­жем из­бе­жать непри­ят­ных сюр­при­зов.

Вто­рой путь за­клю­ча­ет­ся в по­лу­чении досту­па к биб­лио­те­кам, реа­ли­зую­щим ин­тер­фейс ме­ж­ду Java и ядром сис­те­мы. Как уже от­ме­ча­лось, эти биб­лио­те­ки вполне доступ­ны на­шим про­грам­мам. Не­доста­ток данно­го под­хо­да за­клю­ча­ет­ся в том, что про­грам­мы, взаи­мо­дей­ст­вую­щие с нижними уров­ня­ми аб­ст­рак­ции, не учи­ты­ва­ют со­бы­тия, про­ис­хо­дя­щие на бо­лее вы­со­ких уров­нях, что мо­жет при­вес­ти к кон­флик­там и непредвиден­но­му по­ве­дению этих про­грамм. И это не го­во­ря о том, что сам про­ме­жу­точ­ный уро­вень, не яв­ляю­щий­ся ча­стью офи­ци­аль­но­го API Android, мо­жет быть под­вер­жен неожи­дан­ным и пло­хо до­ку­мен­ти­ро­ван­ным из­менениям.

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

Из пе­ре­чис­лен­ных вы­ше трех под­хо­дов к ра­бо­те с гра­фи­кой мы рас­смот­рим два наи­бо­лее ра­цио­наль­ных: пря­мое взаи­мо­дей­ст­вие с ви­део­бу­фе­ром и ис­поль­зо­вание тон­кой обо­лоч­ки Java во­круг ко­да про­грам­мы, на­пи­сан­но­го на C. За­од­но мы рас­смот­рим дру­гой, бо­лее про­стой спо­соб на­пи­сания Make-фай­лов для сбор­ки при­ло­жений Linux под Android. В от­ли­чие от рас­смот­рен­но­го в пре­ды­ду­щей ста­тье, этот спо­соб скры­ва­ет от нас мно­гие ин­те­рес­ные де­та­ли и ра­бо­та­ет толь­ко с NDK, за­то по­зво­ля­ет соз­да­вать бо­лее ком­пакт­ные Make-фай­лы.

Счи­ты­вание дан­ных из ви­део­бу­фе­ра

Уст­рой­ст­во ви­део­бу­фе­ра /dev/graphics/fb0 по­зво­ля­ет по­лу­чить пря­мой доступ к то­му, что про­ис­хо­дит на эк­ране уст­рой­ст­ва Android. Дан­ные, ко­то­рые мож­но счи­тать из фай­ла /dev/graphics/fb0, пред­став­ля­ют со­бой по­ток зна­чений пик­се­лей, где пер­вым идет верхний ле­вый пик­сель, а по­следним – нижний пра­вый. К это­му на­до до­ба­вить, что, по­сколь­ку уст­рой­ст­ва Android ис­поль­зу­ют двой­ную бу­фе­ри­за­цию, из фай­ла /dev/graphis/fb0 мож­но про­чи­тать со­дер­жи­мое сра­зу двух кад­ров ви­део­бу­фе­ра. Что­бы пре­об­ра­зо­вать этот по­ток «сы­рой» ин­фор­ма­ции в на­гляд­ный гра­фи­че­­ский фор­мат, необ­хо­ди­мо знать по крайней ме­ре два па­ра­мет­ра ви­део­сис­те­мы уст­рой­ст­ва: фор­мат хранения пик­се­лей, ко­то­рый тес­но свя­зан с ко­ли­че­­ст­вом цве­тов, ото­бра­жае­мых уст­рой­ст­вом, и раз­ре­шение эк­ра­на уст­рой­ст­ва по вер­ти­ка­ли и по го­ри­зон­та­ли. Пик­се­ли на эк­ра­нах Android мо­гут быть пред­став­ле­ны 16-ю, 24-мя и 32-мя би­та­ми. Ко­ли­че­­ст­во пик­се­лей на эк­ране ко­леб­лет­ся от 240 × 320 до 800 × 1280.

Рас­смот­рим фраг­мент про­грам­мы fboutput, ко­то­рая де­ла­ет снимок эк­ра­на уст­рой­ст­ва и со­хра­ня­ет его на дис­ке в фор­ма­те BMP. Весь текст про­грам­мы вы най­де­те на при­ла­гаю­щем­ся дис­ке, в то вре­мя как фраг­мент, пред­став­лен­ный ниже, оп­ре­де­ля­ет па­ра­мет­ры ви­део­бу­фе­ра.

int fd;

fb_fix_screeninfo fi;

fb_var_screeninfo vi;

fd = open(“/dev/graphics/fb0”, O_RDWR);

ioctl(fd, FBIOGET_FSCREENINFO, &fi);

ioctl(fd, FBIOGET_VSCREENINFO, &vi);

Мы от­кры­ва­ем файл /dev/graphics/fb0 как обыч­ный файл Linux, а за­тем де­ла­ем два вы­зо­ва ioctl(). Пер­вый вы­зов по­зво­лит по­лу­чить фик­си­ро­ван­ные ха­рак­те­ри­сти­ки эк­ра­на, вто­рой – пе­ре­мен­ные ха­рак­те­ри­сти­ки. Струк­ту­ры, в ко­то­рые вы­зо­вы за­пи­сы­ва­ют дан­ные (пе­ре­мен­ные fi и vi со­от­вет­ст­вен­но), име­ют тип fb_fix_screeninfo и fb_var_screeninfo. Оба ти­па объ­яв­ле­ны в за­го­ло­воч­ном фай­ле /include/linux/fb.h. Мы не бу­дем опи­сы­вать все по­ля этих струк­тур; ска­жем толь­ко, что по­сле вы­полнения при­ве­ден­ных вы­ше вы­зо­вов ioctl() пе­ре­мен­ная vi.xres бу­дет одер­жать чис­ло пик­сель­ных столб­цов в дан­ном ре­жи­ме ра­бо­ты эк­ра­на уст­рой­ст­ва, vi.yres бу­дет одер­жать чис­ло строк, а пе­ре­мен­ная vi.bits_per_pixel – чис­ло би­тов на один пик­сель. Пе­ре­мен­ная fi.smem_len бу­дет со­дер­жать раз­мер ви­део­бу­фе­ра в бай­тах (в дан­ном слу­чае – объ­ем па­мя­ти, необ­хо­ди­мый для хранения двух кад­ров бу­фе­ра).

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

void * bits = mmap(0, fi.smem_len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);

В ре­зуль­та­те ука­за­тель bits ука­зы­ва­ет на на­ча­ло об­лас­ти дан­ных бу­фе­ра. Объ­ем па­мя­ти, занимае­мый одним ка­дром, мож­но по­счи­тать по фор­му­ле

vi.xres*vi.yres*(vi.bits_per_pixel << 3).

Для по­лу­чения скрин­шо­та нам оста­лось счи­тать дан­ные од­но­го кад­ра из бу­фе­ра и кон­вер­ти­ро­вать их в фор­мат BMP. Все эти опе­ра­ции про­де­лы­ва­ет про­грам­ма fboutput, и оста­нав­ли­вать­ся на них под­роб­но мы не бу­дем.

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

Мы мог­ли бы со­брать про­грам­му fboutput, ис­поль­зуя в ка­че­­ст­ве шаб­ло­на Make-файл, вве­ден­ный в пре­ды­ду­щей ста­тье, но по­сту­пим ина­че – восполь­зу­ем­ся сис­те­мой сбор­ки про­грамм NDK/SDK, ко­то­рая так­же при­го­дит­ся нам в сле­дую­щем при­ме­ре. Пре­ж­де все­го, стан­дарт­ная сис­те­ма сбор­ки Android пред­по­ла­га­ет, что со­би­рае­мые фай­лы рас­по­ло­же­ны в оп­ре­де­лен­ных ди­рек­то­ри­ях. В слу­чае про­грам­мы fboutput, ко­то­рая все же не яв­ля­ет­ся стан­дарт­ным при­ло­жением Android, струк­ту­ра про­ек­та вы­гля­дит до­воль­но про­сто. В ди­рек­то­рии про­ек­та рас­по­ло­же­на под­ди­рек­то­рия jni (она на­зы­ва­ет­ся так по­то­му, что обыч­но со­дер­жит код, к ко­то­ро­му осталь­ные час­ти при­ло­жения об­ра­ща­ют­ся по­мо­щью од­но­имен­но­го ме­ханиз­ма), где со­дер­жит­ся файл ис­ход­ных тек­стов на­шей про­грам­мы, а так­же два фай­ла сце­на­ри­ев сбор­ки – Android.mk и Appliation.mk. Со­дер­жи­мое фай­ла Appliation.mk вы­гля­дит не про­сто, а очень про­сто:

APP_PLATFORM := android-9

NDK_APP_OUT := ./

APP_ABI := armeabi

Не­труд­но до­га­дать­ся, что в этом фай­ле мы ука­зы­ва­ем вер­сию API (в на­шем слу­чае – android-9), корневую ди­рек­то­рию для ре­зуль­та­тов сбор­ки и фор­мат дво­ич­но­го ко­да, ко­то­рый мы хо­тим по­лу­чить (armeabi).

Файл Android.mk не­сколь­ко слож­нее:

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE := fbout

LOCAL_SRC_FILES := capturescr.c

include $(BUILD_EXECUTABLE)

Пе­ре­мен­ная LOCAL_MODULE со­дер­жит имя, ко­то­рое бу­дет при­свое­но дво­ич­но­му фай­лу, по­лу­чен­но­му в ре­зуль­та­те сбор­ки. Пе­ре­мен­ной LOCAL_SRC_FILES сле­ду­ет при­сво­ить спи­сок фай­лов ис­ход­ных тек­стов на­шей про­грам­мы. От­дель­но­го внимания за­слу­жи­ва­ет по­след­няя строч­ка, ко­то­рая оп­ре­де­ля­ет, чем имен­но дол­жен быть ре­зуль­тат сбор­ки. По­сколь­ку обыч­но код C и C++ ис­поль­зу­ет­ся в при­ло­жениях Android в фор­ме раз­де­ляе­мых биб­лио­тек, в боль­шин­ст­ве слу­ча­ев эта стро­ка бу­дет вы­гля­деть как

include $(BUILD_SHARED_LIBRARY)

но мы хо­тим по­лу­чить на вы­хо­де файл са­мо­стоя­тель­ной про­грам­мы и по­это­му ис­поль­зу­ем кон­стан­ту BUILD_EXECUTABLE.

Все, что нам те­перь оста­лось сде­лать – пе­рей­ти в корневую ди­рек­то­рию на­ше­го про­ек­та и вы­звать в ней скрипт ndk-build, вхо­дя­щий в со­став NDK. Ес­ли сбор­ка про­шла успеш­но, то в под­ди­рек­то­рии корневой ди­рек­то­рии про­ек­та libs/armeabi поя­вит­ся ис­пол­няе­мый файл на­шей про­грам­мы. Не удив­ляй­тесь, что файл поя­вил­ся в ди­рек­то­рии libs: все-та­ки основ­ное пред­на­зна­чение NDK – сбор­ка раз­де­ляе­мых биб­лио­тек. Тем не менее, NDK зна­ет о том, что со­би­ра­ет ис­пол­няе­мый файл про­грам­мы. В про­цес­се сбор­ки про­грам­ма ав­то­ма­ти­че­­ски ком­по­ну­ет­ся с фраг­мен­том ко­да, со­дер­жа­щим функ­цию _start(), ко­то­рая вы­зы­ва­ет на­шу функ­цию main(). Ском­пи­ли­ро­ван­ную про­грам­му нуж­но за­гру­зить в уст­рой­ст­во Android (или его эму­ля­тор) и в окне кон­со­ли Android ко­ман­до­вать нечто на­по­до­бие

fboutoutput screenshot.bmp.

В ре­зуль­та­те бу­дет соз­дан файл screenshot.bmp, со­дер­жа­щий снимок эк­ра­на уст­рой­ст­ва.

Ра­бо­та с гра­фи­кой с по­мо­щью SDL

Как мы уже ви­де­ли, на­шим про­грам­мам доступ­ны все сред­ст­ва вы­во­да гра­фи­ки Android, на­чи­ная с OpenGL ES и за­кан­чи­вая ви­део­бу­фе­ром. Ос­та­лось толь­ко встро­ить гра­фи­че­­ский вы­вод на­ше­го при­ло­жения в об­щую ме­ханику ра­бо­ты опе­ра­ци­он­ной сис­те­мы. Са­мый про­стой спо­соб сде­лать это – на­пи­сать оберт­ку на Java, ко­то­рая бу­дет вы­зы­вать по­лез­ный код на­шей про­грам­мы с по­мо­щью JNI. По­сколь­ку од­на из за­дач, ко­то­рую мы ста­вим пе­ред со­бой, за­клю­ча­ет­ся в уп­ро­щении пе­ре­но­са на Android про­грамм, пред­на­зна­чен­ных для на­столь­ных вер­сий Linux, возника­ет во­прос, на­сколь­ко дол­жен из­менить­ся код про­грам­мы, на­пи­сан­ной на C или C++. От­вет на этот во­прос за­ви­сит от то­го, на­сколь­ко пе­ре­но­си­мая про­грам­ма по сво­ему по­ве­дению долж­на быть по­хо­жа на стан­дарт­ное при­ло­жение Android.
Android и мно­го­за­дач­ность

Хо­тя Android – мно­го­за­дач­ная сис­те­ма, эта мно­го­за­дач­ность ра­бо­та­ет не со­всем так, как в ОС для ПК. Пред­по­ла­га­ет­ся, что в ка­ж­дый мо­мент вре­мени поль­зо­ва­тель ра­бо­та­ет толь­ко с одним при­ло­жением (ок­но ко­то­ро­го занима­ет весь эк­ран уст­рой­ст­ва). Дру­гие про­цес­сы, ок­на ко­то­рых не вид­ны, рас­смат­ри­ва­ют­ся сис­те­мой как про­цес­сы заднего пла­на (воз­мож­ны еще про­цес­сы, у ко­то­рых во­об­ще нет окон, но мы на них оста­нав­ли­вать­ся не бу­дем). Про­цес­сы заднего пла­на мо­гут про­дол­жать ра­бо­ту, но в слу­чае, ес­ли сис­те­ме не бу­дет хва­тать па­мя­ти, лю­бой из этих про­цес­сов мо­жет быть ав­то­ма­ти­че­­ски за­вер­шен и вы­гру­жен. При этом сис­те­ма не толь­ко не спра­ши­ва­ет поль­зо­ва­те­ля о том, со­гла­сен ли он на вы­груз­ку при­ло­жения, но и не пре­ду­пре­ж­да­ет его об этом (са­ма про­грам­ма, ес­те­ст­вен­но, по­лу­ча­ет от сис­те­мы со­от­вет­ст­вую­щие сиг­на­лы).

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

В са­мом про­стом слу­чае код про­грам­мы, на­пи­сан­ной на C или C++, не при­дет­ся ме­нять во­об­ще. Но по­лу­чен­ная в ре­зуль­та­те про­грам­ма бу­дет вес­ти се­бя не со­всем так, как обыч­ные при­ло­жения Android. Бо­лее глу­бо­кая «ан­д­рои­ди­за­ция» про­грамм по­тре­бу­ет внесения из­менений в их ар­хи­тек­ту­ру в со­от­вет­ст­вии с тре­бо­вания­ми ОС Android.

В ка­че­­ст­ве при­ме­ра про­грам­мы, ра­бо­таю­щей с гра­фи­кой, ко­то­рую мож­но с минималь­ны­ми из­менения­ми пе­ренести на плат­фор­му Android, мы рас­смот­рим про­стую про­грам­му, ис­поль­зую­щую биб­лио­те­ку SDL (www.libsdl.org). Вы­бор SDL не слу­ча­ен. Эта биб­лио­те­ка уже дав­но ис­поль­зу­ет­ся про­грам­ми­ста­ми-умель­ца­ми в про­цес­се пе­ре­но­са игр на мо­биль­ные плат­фор­мы (доста­точ­но на­з­вать иг­ру OpenTTD, в ко­то­рую мож­но по­иг­рать и на плат­фор­ме Android). Кро­ме то­го, на­чи­ная с но­вей­шей вер­сии SDL – SDL 2, плат­фор­ма Android под­дер­жи­ва­ет­ся биб­лио­те­кой офи­ци­аль­но. Итак, до­пустим, мы хо­тим пор­ти­ро­вать на Android про­стую про­грам­му, на­пи­сан­ную с ис­поль­зо­ванием SDL:

#include “SDL.h”

int SDL_main(int argc, char *argv[]) {

SDL_Window *win = NULL;

SDL_Renderer *renderer = NULL;

SDL_Texture *bitmapTex = NULL;

SDL_Surface *bitmapSurface = NULL;

SDL_Rect rect;

int running = 1;

SDL_Init(SDL_INIT_VIDEO);

win = SDL_CreateWindow(“SDL for Android Hello World”, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 800, 480, SDL_WINDOW_SHOWN);

renderer = SDL_CreateRenderer(win, -1, SDL_RENDERER_ACCELERATED);

bitmapSurface = SDL_LoadBMP(“data/android.bmp”);

bitmapTex = SDL_CreateTextureFromSurface(renderer, bitmapSurface);

SDL_FreeSurface(bitmapSurface);

while (1) {

SDL_Event e;

if (SDL_PollEvent(&e)) {

if (e.type == SDL_QUIT) {

break;

}

}

SDL_RenderClear(renderer);

SDL_RenderCopy(renderer, bitmapTex, NULL, NULL);

SDL_RenderPresent(renderer);

}

SDL_DestroyTexture(bitmapTex);

SDL_DestroyRenderer(renderer);

SDL_DestroyWindow(win);

return 0;

}

Про­грам­ма це­ли­ком по­за­им­ст­во­ва­на из до­ку­мен­та­ции SDL. Един­ст­вен­ное, что она де­ла­ет – вы­во­дит на эк­ран кар­тин­ку, за­гру­жен­ную из фай­ла data/android.bmp. По­сколь­ку на этот раз ре­зуль­та­том сбор­ки станет про­грам­ма с рас­ши­рением APK, нам при­дет­ся вы­полнить все фор­маль­но­сти, свя­зан­ные с соз­данием про­ек­та при­ло­жения Android: ор­ганизо­вать струк­ту­ру ди­рек­то­рий, как то­го тре­бу­ет сис­те­ма сбор­ки Android, и до­ба­вить необ­хо­ди­мые фай­лы, опи­сы­ваю­щие про­ект. Все это вы най­де­те на дис­ке (про­ект на­зы­ва­ет­ся sdl-android). Все фай­лы на­хо­дят­ся на сво­их мес­тах, за ис­клю­чением ис­ход­ных тек­стов биб­лио­те­ки SDL. Я не стал вклю­чать их по­то­му, что SDL 2.0 все еще пребывает в со­стоянии раз­ра­бот­ки и к мо­мен­ту вы­хо­да жур­на­ла под­держ­ка Android в этой биб­лио­те­ке, ско­рее все­го, бу­дет рас­ши­ре­на и улуч­ше­на.

Итак, рас­смот­рим бег­ло струк­ту­ру про­ек­та при­ло­жения SDL-Android. Ис­ход­ный текст са­мой про­грам­мы на­хо­дит­ся в ди­рек­то­рии jni/src. В ди­рек­то­рии jni/SDL нуж­но раз­мес­тить ис­ход­ные тек­сты биб­лио­те­ки SDL, про­из­ве­дя в них неболь­шие из­менения (опи­сан­ные в фай­ле README для Android, вхо­дя­щем в со­став ди­ст­ри­бу­ти­ва биб­лио­те­ки). В ди­рек­то­рии src на­хо­дит­ся файл ис­ход­ных тек­стов на язы­ке Java. Этот файл реа­ли­зу­ет ту са­мую «оберт­ку», о ко­то­рой го­во­ри­лось вы­ше. В ди­рек­то­рии assets/data на­хо­дит­ся файл BMP, из ко­то­ро­го про­грам­ма за­гру­жа­ет изо­бра­жение. Во вре­мя вы­полнения со­б­ран­ной про­грам­мы этот файл бу­дет за­гру­жать­ся из сис­те­мы ре­сур­сов при­ло­жения.

При­сту­пим к сбор­ке. Сна­ча­ла, с по­мо­щью зна­ко­мо­го нам скрип­та ndk-build, мы со­бе­рем ту часть ко­да, ко­то­рая на­пи­са­на на C. В ре­зуль­та­те у нас поя­вят­ся две раз­де­ляе­мые биб­лио­те­ки: libSDL2.so и libmain.so. Вто­рая биб­лио­те­ка со­дер­жит код на­шей про­грам­мы, то есть функ­цию SDL_main().

Об­ра­ти­те, кста­ти, внимание на но­вые ди­рек­ти­вы в фай­ле jni/src/Android.mk:

LOCAL_SHARED_LIBRARIES := SDL2

LOCAL_LDLIBS := -lGLESv1_CM –llog

Пер­вая ди­рек­ти­ва ука­зы­ва­ет на то, что биб­лио­те­ку libmain.so сле­ду­ет свя­зать с биб­лио­те­кой libSDL2.so, со­б­ран­ной в рам­ках это­го же про­ек­та. Вто­рая ди­рек­ти­ва ука­зы­ва­ет на необ­хо­ди­мость под­клю­чения биб­лио­тек libGLESv1_CM.so и liblog.so, ко­то­рые яв­ля­ют­ся ча­стью Android.

Те­перь нам при­дет­ся восполь­зо­вать­ся ин­ст­ру­мен­том, ко­то­рым мы пре­ж­де не поль­зо­ва­лись – про­грам­мой ant. Эта про­грам­ма пред­на­зна­че­на для сбор­ки про­ек­тов Java; она же соз­даст нам файл с рас­ши­рением APK. В корневой ди­рек­то­рии про­ек­та ко­ман­ду­ем

ant -debug

или

ant -release

И по­лу­ча­ем файл APK, ко­то­рый мож­но уста­но­вить на уст­рой­ст­во Android так же, как и лю­бое дру­гое при­ло­жение.

На­пи­сание про­грамм для Android на C тре­бу­ет­ся не толь­ко ха­ке­рам и тем, кто хо­чет пе­ренести на мо­биль­ную плат­фор­му на­пи­сан­ный ранее код. Толь­ко ис­поль­зуя C и C++, мы мо­жем сде­лать то, что час­то тре­бу­ет­ся в вы­со­ко­про­из­во­ди­тель­ных про­грам­мах: об­ра­тить­ся на­пря­мую к воз­мож­но­стям про­цес­со­ра ARM. Об этом – в сле­дую­щей час­ти.

Персональные инструменты
купить
подписаться
Яндекс.Метрика