Android Studio Screen Capture
I would like to take screen shot of my app and I would like to share that screen shot in mail,blue tooth,social medias etc.. I don’t know how to perform screen shot and to share it how it can be done?
4,621 10 10 gold badges 38 38 silver badges 53 53 bronze badges
asked Aug 1, 2015 at 10:20
vimal kumar vimal kumar
325 6 6 silver badges 22 22 bronze badges
wich emulator r u using?
Aug 1, 2015 at 10:22
running in lollipop device
Aug 1, 2015 at 10:26
so u need to screen capture through adb?
Aug 1, 2015 at 10:27
if you r using a mobile device as emulator u can take screenshot using the inbuild function fo the device. it is available in most of the devices. if you r using any other emulator, u might need to do screen capture thru adb. if u r using a mobile device give me its brand and model number.
Aug 1, 2015 at 10:30
I to knew that.I would like to include the screen shot option in my app for example am having a button like screen shot while clicking the screen shot button it should be attached to our mail.
Aug 1, 2015 at 10:33
2 Answers 2
to include screenshot function your app, you need to grand superuser permission. In other words, third party apps can only take screenshot on rooted device, oru your app should be installed as a system app.
answered Aug 1, 2015 at 10:36
Rishad Appat Rishad Appat
1,796 1 1 gold badge 15 15 silver badges 31 31 bronze badges
am having all those permission to perform changes in my app kindly tell me the way to do it
Aug 1, 2015 at 10:46
1.Start the app in Debug Mode.
2.click Android to open the Android DDMS tool window.
3. Click Screen Capture on the left side of the Android DDMS tool window.
4. Optional: To add a device frame around your screenshot, enable the Frame and
take the screenshot option.
and Save It
You can even try printscreen or snipping tool in windows.. and edit the view as
you want
The newly manufactures Android smartphones are already receiving very easy methods to take screenshot on them. So, you do not require any third party application or software for this purpose rather you will have to use the easy and reliable method to do so, which is also the time saving feature in this time-valued world.
So, I will share the easy method to take screenshot on Samsung Galaxy trend,
Method – Take Screenshot using Hardware Combination Method
STEP 1 – You will notice Power Key on the right side of the phone and Volume keys on the left hand side.
STEP 2 – Now press and hold the two keys for about 2 seconds.
Your screenshot will be taken and stored in the phone’s memory.
Сборка бинарных файлов Android с помощью исходников и Android NDK. Прокачиваем утилиту screencap
Я занимаюсь автоматизацией Android устройств и часто SDK или ОС Android не имеют нужного функционала или его работа выполняется медленно/очень медленно.
Используя возможности Native Development Kit (NDK) мы можем написать функционал, который будет выполняться быстрей, чем тот же функционал на Java. За счет данного кита мы можем добавлять в свое приложение код, написанный на C/C++ или создавать свои бинарные файлы под мобильные Android устройства.
В данной статье я расскажу каким образом мы можем настроить компиляцию бинарного файла под ОС Android, а так же покажу процесс, как мы можем дополнить функционал уже существующих бинарных файлов в этой ОС.
Для примера я «прокачаю» screencap, который сможет не только получать скриншот всего экрана или выдавать «сырые данные», но и возвращать цвет пикселя по указанной точки или получать изображение только нужной области. Итак, поехали!
Установка NDK
Скачиваем Android NDK и распаковываем архив или устанавливаем через SDK Manager.
Если еще нет, то можете добавить доп. инструменты:
sudo apt-get install android-tools-adb android-tools-fastboot
И создаем «проект» под архитектуру вашего мобильного устройства:
./android-ndk-r12b/build/tools/make_standalone_toolchain.py --arch --install-dir ~/arm
Так как у моего HOMTOM HT16 архитектура armeabi-v7a то я буду использовать команду:
./android-ndk-r12b/build/tools/make_standalone_toolchain.py --arch arm --install-dir ~/arm
И ждем, пока скрипт создаст все необходимые файлы (до 5 мин. примерно).
Проверка на работоспособонсть
Создадим файл hello_world.c с простым кодом:
#include int main ()
И попробуем его скомпилировать:
~/arm/bin/clang -pie hello_world.c -o hello_world
С помощью атрибута -o указываем имя файла, а с помощью ключа -pie мы указываем, что бинарный файл PIE и все его зависимости загружаются в случайные расположения в виртуальной памяти каждый раз, когда приложение выполняется.
Если компиляция прошла успешно, то заливаем файл на телефон:
adb push ./hello_world /data/local/tmp
И попробуем бинарник:
adb shell /data/local/tmp/hello_world
Если вы увидели вывод фразы «hello world» — значит вы всё сделали правильно!
Если у вас все таки появились ошибки, возможно вы выбрали не правильную архитектуру, тогда просто удалите данную директорию и заново создайте с нужной.
Для определения архитектуры можете выполнить команду
adb shell cat /proc/cpuinfo
Исходники и библиотеки
Так как анализ скомпилированного бинарного файла очень затратно по времени, а Android является открытой системой, то почему бы не воспользоваться этим качеством!
Заходим сюда и ищем нужную версию Android. Ну а так уже — скачиваем архив и ищем нужный исходник бинарного файла. Хотя, конечно же есть вариант — Google и «правильный запрос».
В моем случае нужный мне файл находится по этой ссылке.
screencap.cpp
#include #include #include #include #include #include #include #include #include #include #include #include #include #include // TODO: Fix Skia. #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wunused-parameter" #include #include #pragma GCC diagnostic pop using namespace android; static uint32_t DEFAULT_DISPLAY_ID = ISurfaceComposer::eDisplayIdMain; static void usage(const char* pname) < fprintf(stderr, "usage: %s [-hp] [-d display-id] [FILENAME]\n" " -h: this message\n" " -p: save the file as a png.\n" " -d: specify the display id to capture, default %d.\n" "If FILENAME ends with .png it will be saved as a png.\n" "If FILENAME is not given, the results will be printed to stdout.\n", pname, DEFAULT_DISPLAY_ID ); >static SkColorType flinger2skia(PixelFormat f) < switch (f) < case PIXEL_FORMAT_RGB_565: return kRGB_565_SkColorType; default: return kN32_SkColorType; >> static status_t vinfoToPixelFormat(const fb_var_screeninfo& vinfo, uint32_t* bytespp, uint32_t* f) < switch (vinfo.bits_per_pixel) < case 16: *f = PIXEL_FORMAT_RGB_565; *bytespp = 2; break; case 24: *f = PIXEL_FORMAT_RGB_888; *bytespp = 3; break; case 32: // TODO: do better decoding of vinfo here *f = PIXEL_FORMAT_RGBX_8888; *bytespp = 4; break; default: return BAD_VALUE; >return NO_ERROR; > static status_t notifyMediaScanner(const char* fileName) < String8 cmd("am broadcast -a android.intent.action.MEDIA_SCANNER_SCAN_FILE -d file://"); String8 fileUrl("\""); fileUrl.append(fileName); fileUrl.append("\""); cmd.append(fileName); cmd.append(" >/dev/null"); int result = system(cmd.string()); if (result < 0) < fprintf(stderr, "Unable to broadcast intent for media scanner.\n"); return UNKNOWN_ERROR; >return NO_ERROR; > int main(int argc, char** argv) < ProcessState::self()->startThreadPool(); const char* pname = argv[0]; bool png = false; int32_t displayId = DEFAULT_DISPLAY_ID; int c; while ((c = getopt(argc, argv, "phd:")) != -1) < switch (c) < case 'p': png = true; break; case 'd': displayId = atoi(optarg); break; case '?': case 'h': usage(pname); return 1; >> argc -= optind; argv += optind; int fd = -1; const char* fn = NULL; if (argc == 0) < fd = dup(STDOUT_FILENO); >else if (argc == 1) < fn = argv[0]; fd = open(fn, O_WRONLY | O_CREAT | O_TRUNC, 0664); if (fd == -1) < fprintf(stderr, "Error opening file: %s (%s)\n", fn, strerror(errno)); return 1; >const int len = strlen(fn); if (len >= 4 && 0 == strcmp(fn+len-4, ".png")) < png = true; >> if (fd == -1) < usage(pname); return 1; >void const* mapbase = MAP_FAILED; ssize_t mapsize = -1; void const* base = NULL; uint32_t w, s, h, f; size_t size = 0; // Maps orientations from DisplayInfo to ISurfaceComposer static const uint32_t ORIENTATION_MAP[] = < ISurfaceComposer::eRotateNone, // 0 == DISPLAY_ORIENTATION_0 ISurfaceComposer::eRotate270, // 1 == DISPLAY_ORIENTATION_90 ISurfaceComposer::eRotate180, // 2 == DISPLAY_ORIENTATION_180 ISurfaceComposer::eRotate90, // 3 == DISPLAY_ORIENTATION_270 >; ScreenshotClient screenshot; sp display = SurfaceComposerClient::getBuiltInDisplay(displayId); if (display == NULL) < fprintf(stderr, "Unable to get handle for display %d\n", displayId); return 1; >Vector configs; SurfaceComposerClient::getDisplayConfigs(display, &configs); int activeConfig = SurfaceComposerClient::getActiveConfig(display); if (static_cast(activeConfig) >= configs.size()) < fprintf(stderr, "Active config %d not inside configs (size %zu)\n", activeConfig, configs.size()); return 1; >uint8_t displayOrientation = configs[activeConfig].orientation; uint32_t captureOrientation = ORIENTATION_MAP[displayOrientation]; status_t result = screenshot.update(display, Rect(), 0, 0, 0, -1U, false, captureOrientation); if (result == NO_ERROR) < base = screenshot.getPixels(); w = screenshot.getWidth(); h = screenshot.getHeight(); s = screenshot.getStride(); f = screenshot.getFormat(); size = screenshot.getSize(); >else < const char* fbpath = "/dev/graphics/fb0"; int fb = open(fbpath, O_RDONLY); if (fb >= 0) < struct fb_var_screeninfo vinfo; if (ioctl(fb, FBIOGET_VSCREENINFO, &vinfo) == 0) < uint32_t bytespp; if (vinfoToPixelFormat(vinfo, &bytespp, &f) == NO_ERROR) < size_t offset = (vinfo.xoffset + vinfo.yoffset*vinfo.xres) * bytespp; w = vinfo.xres; h = vinfo.yres; s = vinfo.xres; size = w*h*bytespp; mapsize = offset + size; mapbase = mmap(0, mapsize, PROT_READ, MAP_PRIVATE, fb, 0); if (mapbase != MAP_FAILED) < base = (void const *)((char const *)mapbase + offset); >> > close(fb); > > if (base != NULL) < if (png) < const SkImageInfo info = SkImageInfo::Make(w, h, flinger2skia(f), kPremul_SkAlphaType); SkAutoTUnrefdata(SkImageEncoder::EncodeData(info, base, s*bytesPerPixel(f), SkImageEncoder::kPNG_Type, SkImageEncoder::kDefaultQuality)); if (data.get()) < write(fd, data->data(), data->size()); > if (fn != NULL) < notifyMediaScanner(fn); >> else < write(fd, &w, 4); write(fd, &h, 4); write(fd, &f, 4); size_t Bpp = bytesPerPixel(f); for (size_t y=0 ; y> > close(fd); if (mapbase != MAP_FAILED) < munmap((void *)mapbase, mapsize); >return 0; >
Частично разберем данный код.
DEFAULT_DISPLAY_ID — идентификатор дисплея, с которого необходимо получить скриншот. В нашем случае 0.
flinger2skia и vinfoToPixelFormat — отвечает за определение, в каком формате должно быть изображение.
notifyMediaScanner — после того, как изображение будет создано в файловой системе необходимо послать broadcast, чтобы файл смог корректно отображаться. Если не вызывать данный broadcast, то не все приложении смогут увидеть созданный файл.
Функция main не является сложной для «чтения», поэтому разберем только важные моменты, которые непосредственно отвечают за получение данных о изображении.
/dev/graphics/fb0 — это так называемый framebuffer. Framebuffer — это область видеопамяти для кратковременного хранения одного или нескольких видеокадров. Исходя из кода main видно, что существуют версии Android устройств, которые хранят изображение экрана в этом файле. Таким образом, если вы «счастливчик», то узнав vinfo.xoffset и vinfo.yoffset (в большинстве случаев они будут равны 0) и используя консоль вы с легкостью сможете получить информацию о цвете:
dd if=/dev/graphics/fb0 bs= count=1 skip= 2>/dev/null | hd
В моём случае оказалось все не так просто и данный файл не содержать какую либо информацию о изображении.
screenshot.update — это второй способ, как можно получить изображение. Данная функция имеет несколько перегруженных методов с которыми можно ознакомиться тут.
Рассмотрим описание функции с самым большим количеством параметров:
ScreenshotClient::update(const sp& display, Rect sourceCrop, uint32_t reqWidth, uint32_t reqHeight, uint32_t minLayerZ, uint32_t maxLayerZ, bool useIdentityTransform, uint32_t rotation)
display — ссылка на необходимый display.
sourceCrop — кроп выбранной области изображения. Может содержать координаты верхней левой точки и нижней правой (всего 4 параметра xLeft, yTop, xRight, yBottom). Начальная точка координат — верхний левый угол.
reqWidth — ширина возвращаемого изображения
reqHeight — высота возвращаемого изображения
minLayerZ и maxLayerZ — как именно работают данные параметры не удалось понять. Перебор значений выдавал иногда черный кран
useIdentityTransform — Если true, то отключает слой наложения поверх приложений, т.е. тех Activity, которые используют ACTION_MANAGE_OVERLAY_PERMISSION
rotation — поворот изображения.
Таким образом, чтобы нам получить цвет пикселя, нам необходимо задать xLeft и yTop, сдвинув их на 1, т.к. отсчет будет идти с 0, а указанные координаты установить в xRight, yBottom. В reqWidth и reqHeight установить значение равным 1. Изменяя параметры данной функции мы сможем определять границы нужной для нас области.
Компиляция базового screencap.cpp
На самом деле это самая сложная часть, которая может занять несколько дней или целую неделю. К сожалению мне не удалось найти каких-то быстрых решений сборки новой версии screencap в сети, поэтому пришлось конкретно помучаться с clang’ом и его параметрами.
Если вы сразу же попробуете скомпилировать данный код, компилятор будет постоянно ругаться, что нет какого-то файла, а иногда может и сообщить, что файл то есть, но вот нет нужного конструктора.
Поэтому, первоначально я добавил все файлы, которые отсутствовали в библиотеке NDK. Ошибки дают представления, где примерно должен находиться тот или иной файл. Для этого, вам необходимо добавить недостающие файлы, а чтобы узнать где находится sysroot (директория, где ищет clang), можно воспользоваться следующей «фичей»:
echo "#include int main()<>" > t.c; ~/arm/bin/clang -v t.c; rm t.c
В ошибке будет виден путь до данной директории. Если clang у вас находится в другом месте — измените путь до него.
Android (4751641 based on r328903) clang version 7.0.2 (https://android.googlesource.com/toolchain/clang 003100370607242ddd5815e4a043907ea9004281) (https://android.googlesource.com/toolchain/llvm 1d739ffb0366421d383e04ff80ec2ee591315116) (based on LLVM 7.0.2svn) Target: armv7a-none-linux-android16 Thread model: posix InstalledDir: /Users/macbookair/arm/bin Found candidate GCC installation: /Users/macbookair/arm/bin/../lib/gcc/arm-linux-androideabi/4.9.x Selected GCC installation: /Users/macbookair/arm/bin/../lib/gcc/arm-linux-androideabi/4.9.x Candidate multilib: thumb;@mthumb Candidate multilib: armv7-a;@march=armv7-a Candidate multilib: armv7-a/thumb;@march=armv7-a@mthumb Candidate multilib: .; Selected multilib: armv7-a;@march=armv7-a "/Users/macbookair/arm/bin/clang70" -cc1 -triple armv7-none-linux-android16 -emit-obj -mrelax-all -disable-free -disable-llvm-verifier -discard-value-names -main-file-name t.c -mrelocation-model pic -pic-level 2 -pic-is-pie -mthread-model posix -mdisable-fp-elim -fmath-errno -masm-verbose -mconstructor-aliases -fuse-init-array -target-cpu generic -target-feature +soft-float-abi -target-abi aapcs-linux -mfloat-abi soft -fallow-half-arguments-and-returns -dwarf-column-info -debugger-tuning=gdb -target-linker-version 241.9 -v -resource-dir /Users/macbookair/arm/lib64/clang/7.0.2 -isysroot /Users/macbookair/arm/bin/../sysroot -internal-isystem /Users/macbookair/arm/bin/../sysroot/usr/local/include -internal-isystem /Users/macbookair/arm/lib64/clang/7.0.2/include -internal-externc-isystem /Users/macbookair/arm/bin/../sysroot/include -internal-externc-isystem /Users/macbookair/arm/bin/../sysroot/usr/include -fdebug-compilation-dir /Users/macbookair/Downloads/sji-android-screen-capture-master/src/android/capture -ferror-limit 19 -fmessage-length 80 -fno-signed-char -fobjc-runtime=gcc -fdiagnostics-show-option -fcolor-diagnostics -o /var/folders/_4/7d51802j1hz2rnmql8kngx5w0000gn/T/t-9f5ef7.o -x c t.c clang -cc1 version 7.0.2 based upon LLVM 7.0.2svn default target x86_64-apple-darwin18.0.0 ignoring nonexistent directory "/Users/macbookair/arm/bin/../sysroot/include" #include ". " search starts here: #include search starts here: /Users/macbookair/arm/bin/../sysroot/usr/local/include /Users/macbookair/arm/lib64/clang/7.0.2/include /Users/macbookair/arm/bin/../sysroot/usr/include End of search list. t.c:1:10: error: expected "FILENAME" or #include int main()<> ^ 1 error generated.
Обращаемся к Google и ищем все необходимые файлы. В моем случае пришлось добавить следующие директории и файлы:
Директории и файлы
//Добавлены директории ~/arm/sysroot/usr/include/binder ~/arm/sysroot/usr/include/utils ~/arm/sysroot/usr/include/cutils ~/arm/sysroot/usr/include/log ~/arm/sysroot/usr/include/system ~/arm/sysroot/usr/include/gui ~/arm/sysroot/usr/include/ui ~/arm/sysroot/usr/include/hardware ~/arm/sysroot/usr/ports //Добавлены файлы ~/arm/sysroot/usr/include/SkAnnotation.h ~/arm/sysroot/usr/include/SkAtomics.h ~/arm/sysroot/usr/include/SkBBHFactory.h ~/arm/sysroot/usr/include/SkBitmap.h ~/arm/sysroot/usr/include/SkBitmapDevice.h ~/arm/sysroot/usr/include/SkBlitRow.h ~/arm/sysroot/usr/include/SkBlurTypes.h ~/arm/sysroot/usr/include/SkCanvas.h ~/arm/sysroot/usr/include/SkChunkAlloc.h ~/arm/sysroot/usr/include/SkClipStack.h ~/arm/sysroot/usr/include/SkColor.h ~/arm/sysroot/usr/include/SkColorFilter.h ~/arm/sysroot/usr/include/SkColorPriv.h ~/arm/sysroot/usr/include/SkColorTable.h ~/arm/sysroot/usr/include/SkComposeShader.h ~/arm/sysroot/usr/include/SkData.h ~/arm/sysroot/usr/include/SkDataTable.h ~/arm/sysroot/usr/include/SkDeque.h ~/arm/sysroot/usr/include/SkDevice.h ~/arm/sysroot/usr/include/SkDither.h ~/arm/sysroot/usr/include/SkDocument.h ~/arm/sysroot/usr/include/SkDraw.h ~/arm/sysroot/usr/include/SkDrawable.h ~/arm/sysroot/usr/include/SkDrawFilter.h ~/arm/sysroot/usr/include/SkDrawLooper.h ~/arm/sysroot/usr/include/SkDrawPictureCallback.h ~/arm/sysroot/usr/include/SkEndian.h ~/arm/sysroot/usr/include/SkError.h ~/arm/sysroot/usr/include/SkFilterQuality.h ~/arm/sysroot/usr/include/SkFixed.h ~/arm/sysroot/usr/include/SkFlattenable.h ~/arm/sysroot/usr/include/SkFlattenableSerialization.h ~/arm/sysroot/usr/include/SkFloatBits.h ~/arm/sysroot/usr/include/SkFloatingPoint.h ~/arm/sysroot/usr/include/SkFont.h ~/arm/sysroot/usr/include/SkFontHost.h ~/arm/sysroot/usr/include/SkFontLCDConfig.h ~/arm/sysroot/usr/include/SkFontStyle.h ~/arm/sysroot/usr/include/SkGraphics.h ~/arm/sysroot/usr/include/SkImage.h ~/arm/sysroot/usr/include/SkImageDecoder.h ~/arm/sysroot/usr/include/SkImageEncoder.h ~/arm/sysroot/usr/include/SkImageFilter.h ~/arm/sysroot/usr/include/SkImageGenerator.h ~/arm/sysroot/usr/include/SkImageInfo.h ~/arm/sysroot/usr/include/SkInstCnt.h ~/arm/sysroot/usr/include/SkLazyPtr.h ~/arm/sysroot/usr/include/SkMallocPixelRef.h ~/arm/sysroot/usr/include/SkMask.h ~/arm/sysroot/usr/include/SkMaskFilter.h ~/arm/sysroot/usr/include/SkMath.h ~/arm/sysroot/usr/include/SkMatrix.h ~/arm/sysroot/usr/include/SkMetaData.h ~/arm/sysroot/usr/include/SkMultiPictureDraw.h ~/arm/sysroot/usr/include/SkMutex.h ~/arm/sysroot/usr/include/SkOnce.h ~/arm/sysroot/usr/include/SkOSFile.h ~/arm/sysroot/usr/include/SkPackBits.h ~/arm/sysroot/usr/include/SkPaint.h ~/arm/sysroot/usr/include/SkPath.h ~/arm/sysroot/usr/include/SkPathEffect.h ~/arm/sysroot/usr/include/SkPathMeasure.h ~/arm/sysroot/usr/include/SkPathRef.h ~/arm/sysroot/usr/include/SkPicture.h ~/arm/sysroot/usr/include/SkPictureRecorder.h ~/arm/sysroot/usr/include/SkPixelRef.h ~/arm/sysroot/usr/include/SkPixelSerializer.h ~/arm/sysroot/usr/include/SkPoint.h ~/arm/sysroot/usr/include/SkPostConfig.h ~/arm/sysroot/usr/include/SkPreConfig.h ~/arm/sysroot/usr/include/SkRasterizer.h ~/arm/sysroot/usr/include/SkRect.h ~/arm/sysroot/usr/include/SkRefCnt.h ~/arm/sysroot/usr/include/SkRegion.h ~/arm/sysroot/usr/include/SkRRect.h ~/arm/sysroot/usr/include/SkScalar.h ~/arm/sysroot/usr/include/SkShader.h ~/arm/sysroot/usr/include/SkSize.h ~/arm/sysroot/usr/include/SkSpinlock.h ~/arm/sysroot/usr/include/SkStream.h ~/arm/sysroot/usr/include/SkString.h ~/arm/sysroot/usr/include/SkStrokeRec.h ~/arm/sysroot/usr/include/SkSurface.h ~/arm/sysroot/usr/include/SkSurfaceProps.h ~/arm/sysroot/usr/include/SkTArray.h ~/arm/sysroot/usr/include/SkTDArray.h ~/arm/sysroot/usr/include/SkTDict.h ~/arm/sysroot/usr/include/SkTDStack.h ~/arm/sysroot/usr/include/SkTemplates.h ~/arm/sysroot/usr/include/SkTextBlob.h ~/arm/sysroot/usr/include/SkThread.h ~/arm/sysroot/usr/include/SkTime.h ~/arm/sysroot/usr/include/SkTInternalLList.h ~/arm/sysroot/usr/include/SkTLazy.h ~/arm/sysroot/usr/include/SkTRegistry.h ~/arm/sysroot/usr/include/SkTSearch.h ~/arm/sysroot/usr/include/SkTypeface.h ~/arm/sysroot/usr/include/SkTypes.h ~/arm/sysroot/usr/include/SkUnPreMultiply.h ~/arm/sysroot/usr/include/SkUserConfig.h ~/arm/sysroot/usr/include/SkUtils.h ~/arm/sysroot/usr/include/SkWeakRefCnt.h ~/arm/sysroot/usr/include/SkWriteBuffer.h ~/arm/sysroot/usr/include/SkWriter32.h ~/arm/sysroot/usr/include/SkXfermode.h //И небольшая правка кода ~/arm/sysroot/usr/include/android/log.h
И казалось бы, что вот он успех, все что надо добавлено. Выполняем компиляцию
~/arm/bin/clang -pie screencap.cpp -o ./screencap
и получаем ужасный результат:
Слабонервным не смотреть
/var/folders/_4/7d51802j1hz2rnmql8kngx5w0000gn/T/screencap-175810.o:screencap.cpp:function main: error: undefined reference to 'android::ProcessState::self()' /var/folders/_4/7d51802j1hz2rnmql8kngx5w0000gn/T/screencap-175810.o:screencap.cpp:function main: error: undefined reference to 'android::ProcessState::startThreadPool()' /var/folders/_4/7d51802j1hz2rnmql8kngx5w0000gn/T/screencap-175810.o:screencap.cpp:function main: error: undefined reference to 'android::ScreenshotClient::ScreenshotClient()' /var/folders/_4/7d51802j1hz2rnmql8kngx5w0000gn/T/screencap-175810.o:screencap.cpp:function main: error: undefined reference to 'android::SurfaceComposerClient::getBuiltInDisplay(int)' /var/folders/_4/7d51802j1hz2rnmql8kngx5w0000gn/T/screencap-175810.o:screencap.cpp:function main: error: undefined reference to 'android::SurfaceComposerClient::getDisplayConfigs(android::sp const&, android::Vector*)' /var/folders/_4/7d51802j1hz2rnmql8kngx5w0000gn/T/screencap-175810.o:screencap.cpp:function main: error: undefined reference to 'android::SurfaceComposerClient::getActiveConfig(android::sp const&)' /var/folders/_4/7d51802j1hz2rnmql8kngx5w0000gn/T/screencap-175810.o:screencap.cpp:function main: error: undefined reference to 'android::ScreenshotClient::update(android::sp const&, android::Rect, unsigned int, unsigned int, unsigned int, unsigned int, bool, unsigned int)' /var/folders/_4/7d51802j1hz2rnmql8kngx5w0000gn/T/screencap-175810.o:screencap.cpp:function main: error: undefined reference to 'android::ScreenshotClient::getPixels() const' /var/folders/_4/7d51802j1hz2rnmql8kngx5w0000gn/T/screencap-175810.o:screencap.cpp:function main: error: undefined reference to 'android::ScreenshotClient::getWidth() const' /var/folders/_4/7d51802j1hz2rnmql8kngx5w0000gn/T/screencap-175810.o:screencap.cpp:function main: error: undefined reference to 'android::ScreenshotClient::getHeight() const' /var/folders/_4/7d51802j1hz2rnmql8kngx5w0000gn/T/screencap-175810.o:screencap.cpp:function main: error: undefined reference to 'android::ScreenshotClient::getStride() const' /var/folders/_4/7d51802j1hz2rnmql8kngx5w0000gn/T/screencap-175810.o:screencap.cpp:function main: error: undefined reference to 'android::ScreenshotClient::getFormat() const' /var/folders/_4/7d51802j1hz2rnmql8kngx5w0000gn/T/screencap-175810.o:screencap.cpp:function main: error: undefined reference to 'android::ScreenshotClient::getSize() const' /var/folders/_4/7d51802j1hz2rnmql8kngx5w0000gn/T/screencap-175810.o:screencap.cpp:function main: error: undefined reference to 'android::bytesPerPixel(int)' /var/folders/_4/7d51802j1hz2rnmql8kngx5w0000gn/T/screencap-175810.o:screencap.cpp:function main: error: undefined reference to 'SkImageEncoder::EncodeData(SkImageInfo const&, void const*, unsigned int, SkImageEncoder::Type, int)' /var/folders/_4/7d51802j1hz2rnmql8kngx5w0000gn/T/screencap-175810.o:screencap.cpp:function main: error: undefined reference to 'android::bytesPerPixel(int)' /var/folders/_4/7d51802j1hz2rnmql8kngx5w0000gn/T/screencap-175810.o:screencap.cpp:function main: error: undefined reference to 'android::ScreenshotClient::~ScreenshotClient()' /var/folders/_4/7d51802j1hz2rnmql8kngx5w0000gn/T/screencap-175810.o:screencap.cpp:function main: error: undefined reference to 'android::ScreenshotClient::~ScreenshotClient()' /var/folders/_4/7d51802j1hz2rnmql8kngx5w0000gn/T/screencap-175810.o:screencap.cpp:function main: error: undefined reference to 'stderr' /var/folders/_4/7d51802j1hz2rnmql8kngx5w0000gn/T/screencap-175810.o:screencap.cpp:function main: error: undefined reference to 'stderr' /var/folders/_4/7d51802j1hz2rnmql8kngx5w0000gn/T/screencap-175810.o:screencap.cpp:function main: error: undefined reference to 'stderr' /var/folders/_4/7d51802j1hz2rnmql8kngx5w0000gn/T/screencap-175810.o:screencap.cpp:function usage(char const*): error: undefined reference to 'stderr' /var/folders/_4/7d51802j1hz2rnmql8kngx5w0000gn/T/screencap-175810.o:screencap.cpp:function notifyMediaScanner(char const*): error: undefined reference to 'android::String8::String8(char const*)' /var/folders/_4/7d51802j1hz2rnmql8kngx5w0000gn/T/screencap-175810.o:screencap.cpp:function notifyMediaScanner(char const*): error: undefined reference to 'android::String8::String8(char const*)' /var/folders/_4/7d51802j1hz2rnmql8kngx5w0000gn/T/screencap-175810.o:screencap.cpp:function notifyMediaScanner(char const*): error: undefined reference to 'android::String8::append(char const*)' /var/folders/_4/7d51802j1hz2rnmql8kngx5w0000gn/T/screencap-175810.o:screencap.cpp:function notifyMediaScanner(char const*): error: undefined reference to 'android::String8::append(char const*)' /var/folders/_4/7d51802j1hz2rnmql8kngx5w0000gn/T/screencap-175810.o:screencap.cpp:function notifyMediaScanner(char const*): error: undefined reference to 'android::String8::append(char const*)' /var/folders/_4/7d51802j1hz2rnmql8kngx5w0000gn/T/screencap-175810.o:screencap.cpp:function notifyMediaScanner(char const*): error: undefined reference to 'android::String8::append(char const*)' /var/folders/_4/7d51802j1hz2rnmql8kngx5w0000gn/T/screencap-175810.o:screencap.cpp:function notifyMediaScanner(char const*): error: undefined reference to 'android::String8::~String8()' /var/folders/_4/7d51802j1hz2rnmql8kngx5w0000gn/T/screencap-175810.o:screencap.cpp:function notifyMediaScanner(char const*): error: undefined reference to 'android::String8::~String8()' /var/folders/_4/7d51802j1hz2rnmql8kngx5w0000gn/T/screencap-175810.o:screencap.cpp:function notifyMediaScanner(char const*): error: undefined reference to 'android::String8::~String8()' /var/folders/_4/7d51802j1hz2rnmql8kngx5w0000gn/T/screencap-175810.o:screencap.cpp:function notifyMediaScanner(char const*): error: undefined reference to 'android::String8::~String8()' /var/folders/_4/7d51802j1hz2rnmql8kngx5w0000gn/T/screencap-175810.o(.ARM.extab+0x0): error: undefined reference to '__gxx_personality_v0' /var/folders/_4/7d51802j1hz2rnmql8kngx5w0000gn/T/screencap-175810.o(.ARM.extab+0x48): error: undefined reference to '__gxx_personality_v0' /var/folders/_4/7d51802j1hz2rnmql8kngx5w0000gn/T/screencap-175810.o:screencap.cpp:function android::sp::~sp(): error: undefined reference to 'android::RefBase::decStrong(void const*) const' /var/folders/_4/7d51802j1hz2rnmql8kngx5w0000gn/T/screencap-175810.o(.ARM.extab.text._ZN7android2spINS_12ProcessStateEED2Ev+0x0): error: undefined reference to '__gxx_personality_v0' /var/folders/_4/7d51802j1hz2rnmql8kngx5w0000gn/T/screencap-175810.o:screencap.cpp:function android::Vector::Vector(): error: undefined reference to 'android::VectorImpl::VectorImpl(unsigned int, unsigned int)' /var/folders/_4/7d51802j1hz2rnmql8kngx5w0000gn/T/screencap-175810.o:screencap.cpp:function android::Vector::operator[](unsigned int) const: error: undefined reference to '__android_log_assert' /var/folders/_4/7d51802j1hz2rnmql8kngx5w0000gn/T/screencap-175810.o(.ARM.extab.text._ZN12SkAutoTUnrefI6SkDataED2Ev+0x0): error: undefined reference to '__gxx_personality_v0' /var/folders/_4/7d51802j1hz2rnmql8kngx5w0000gn/T/screencap-175810.o:screencap.cpp:function android::Vector::~Vector(): error: undefined reference to 'android::VectorImpl::finish_vector()' /var/folders/_4/7d51802j1hz2rnmql8kngx5w0000gn/T/screencap-175810.o:screencap.cpp:function android::Vector::~Vector(): error: undefined reference to 'android::VectorImpl::~VectorImpl()' /var/folders/_4/7d51802j1hz2rnmql8kngx5w0000gn/T/screencap-175810.o:screencap.cpp:function android::Vector::~Vector(): error: undefined reference to 'android::VectorImpl::~VectorImpl()' /var/folders/_4/7d51802j1hz2rnmql8kngx5w0000gn/T/screencap-175810.o:screencap.cpp:function android::sp::~sp(): error: undefined reference to 'android::RefBase::decStrong(void const*) const' /var/folders/_4/7d51802j1hz2rnmql8kngx5w0000gn/T/screencap-175810.o:screencap.cpp:function __clang_call_terminate: error: undefined reference to '__cxa_begin_catch' /var/folders/_4/7d51802j1hz2rnmql8kngx5w0000gn/T/screencap-175810.o:screencap.cpp:function __clang_call_terminate: error: undefined reference to 'std::terminate()' /var/folders/_4/7d51802j1hz2rnmql8kngx5w0000gn/T/screencap-175810.o:screencap.cpp:function android::Vector::~Vector(): error: undefined reference to 'operator delete(void*)' /var/folders/_4/7d51802j1hz2rnmql8kngx5w0000gn/T/screencap-175810.o:screencap.cpp:function SkRefCntBase::unref() const: error: undefined reference to 'SkDebugf(char const*, . )' /var/folders/_4/7d51802j1hz2rnmql8kngx5w0000gn/T/screencap-175810.o:screencap.cpp:typeinfo for android::Vector: error: undefined reference to 'vtable for __cxxabiv1::__vmi_class_type_info' /Users/macbookair/Documents/test/bin/../lib/gcc/arm-linux-androideabi/4.9.x/../../../../arm-linux-androideabi/bin/ld: the vtable symbol may be undefined because the class is missing its key function /var/folders/_4/7d51802j1hz2rnmql8kngx5w0000gn/T/screencap-175810.o:screencap.cpp:typeinfo for android::Vector: error: undefined reference to 'typeinfo for android::VectorImpl' clang70: error: linker command failed with exit code 1 (use -v to see invocation)
По мне, это был реальный ад, т.к. множество файлов имело ошибки, чего по идее быть не должно, ведь содержимое данных файлов не менялось. На данном этапе я на долго «присел» и начал активно гуглить ошибки, но нужного ответа не нашел. Каким образом я решил поизучать туториал по clang я уже не помню, но это решение меня спасло!
Как оказалось, у clang есть параметр (а точнее у ld) —unresolved-symbols, который отвечает за работу с неразрешенными (unresolved) символами. Хотя по самой ошибки и не скажешь, что дело в этом. Добавляем параметр и выполняем компиляцию снова:
~/arm/bin/clang -pie screencap.cpp -o ./screencap -Wl,--unresolved-symbols=ignore-all -s
Наконец-то компиляция прошла успешно! На самом деле, я уже думал, что большая часть проблем ушла, но не тут то было. Начали появляться ошибки, наподобие следующей:
CANNOT LINK EXECUTABLE: cannot locate symbol "_ZN7android12ProcessState4selfEv"
Как оказалось, компилятору было достаточно наших добавленных файлов, но на Android устройстве все эти файлы хранятся в .so библиотеках. Позже разобравший более менее хорошо в этом вопросе, я нашел относительно простой способ поиска необходимых библиотек. Для этого нужно открыть бинарных файл screencap, который находится на Android-устройстве (/system/bin/screencap) в текстовом редакторе и посмотреть все названия .so библиотек, которые используются в данном файле. В моем случае вот эта часть:
Часть с названиями используемых библиотек
. libdl.so__libc_initlibc.so__cxa_atexit__register_atfork__gnu_Unwind_Find_exidxLIBC_PRIVATE_ZNK7android6VectorINS_11DisplayInfoEE12do_constructEPvj__snprintf_chk__aeabi_memset__aeabi_memcpy_ZNK7android6VectorINS_11DisplayInfoEE10do_destroyEPvj_ZNK7android6VectorINS_11DisplayInfoEE7do_copyEPvPKvj_ZNK7android6VectorINS_11DisplayInfoEE8do_splatEPvPKvj_ZNK7android6VectorINS_11DisplayInfoEE15do_move_forwardEPvPKvj_ZNK7android6VectorINS_11DisplayInfoEE16do_move_backwardEPvPKvj_ZN7android10VectorImpl13finish_vectorEv_ZN7android10VectorImplD2Ev_ZTVN7android6VectorINS_11DisplayInfoEEE_ZdlPvsignal__android_log_print_ZN7android12ProcessState4selfEv_ZN7android12ProcessState15startThreadPoolEv_ZNK7android7RefBase9decStrongEPKvgetoptatoidupopen__errnostrerrorfprintfstrlenstrcmp_ZN7android16ScreenshotClientC1Ev_ZN7android21SurfaceComposerClient17getBuiltInDisplayEi_ZN7android10VectorImplC2Ejj_ZN7android21SurfaceComposerClient17getDisplayConfigsERKNS_2spINS_7IBinderEEEPNS_6VectorINS_11DisplayInfoEEE_ZN7android21SurfaceComposerClient15getActiveConfigERKNS_2spINS_7IBinderEEE_ZN7android16ScreenshotClient6updateERKNS_2spINS_7IBinderEEENS_4RectEjjjjbj_ZNK7android16ScreenshotClient9getPixelsEv_ZNK7android16ScreenshotClient8getWidthEv_ZNK7android16ScreenshotClient9getHeightEv_ZNK7android16ScreenshotClient9getStrideEv_ZNK7android16ScreenshotClient9getFormatEv_ZNK7android16ScreenshotClient7getSizeEvioctlclose_ZN7android13bytesPerPixelEi_ZN14SkImageEncoder10EncodeDataERK11SkImageInfoPKvjNS_4TypeEiwrite_ZN7android7String8C1EPKc_ZN7android7String86appendEPKcsystemfputs_ZN7android7String8D1Evmunmap_ZN7android16ScreenshotClientD1Evmmapoptargstderroptindabort_edata__bss_start_endlibcutils.solibutils.solibbinder.solibskia.solibui.solibgui.solibc++.solibm.so.
Вы можете сравнить данную часть с вашим скомпилированным файлом и найти, каких же библиотек вам не хватает. В данном случае ими оказались: libgui.so, libui.so, libcutils.so, libutils.so, libbinder.so, libskia.so. Ищем их расположение (на самом деле, они находятся в одном и том же месте):
find -name 'libskia.so' 2>/dev/null /system/lib/libskia.so
Выполняем копирование на sdcard/libs всех библиотек (пример для libskia.so):
./system/lib/libskia.so /sdcard/
Используя adb pull копируем файлы с моб. устройства на компьютер:
adb pull /sdcard/libs ~/arm
Располагаем их в нужной для вас директории и выполняем компиляцию, но уже с новым параметром *.so, который указывает на то, что нужно так же использовать все .so библиотеки при сборке, а точнее — указать ссылки на них в файле.
~/arm/bin/clang -pie screencap.cpp *.so -o ./screencap -Wl,--unresolved-symbols=ignore-all -s
Теперь и компиляция пройдет успешно и запуск бинарного файла на моб. устройстве будет без ошибок.
Заключение и результат
Как вы могли заметить, добавление своего функционала «в чужой код» является не очень простым занятием.
Измененный код файла screencap приведен ниже. Сборка его происходит таким же образом, как и оригинального. Добавленные изменения думаю будут понятны большинству читателей, поэтому решил его не комментировать.
Улучшенный screencap.cpp
#include #include #include #include #include #include #include #include #include #include #include #include #include #include // TODO: Fix Skia. #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wunused-parameter" #include #include #pragma GCC diagnostic pop using namespace android; static uint32_t DEFAULT_DISPLAY_ID = ISurfaceComposer::eDisplayIdMain; static void usage(const char* pname) < fprintf(stderr, "usage: %s [-hpcr] [-d display-id] [FILENAME] [x1 y1] [x2 y2]\n" " -h: this message\n" " -p: save the file as a png.\n" " -d: specify the display id to capture, default %d.\n" " -c: print pixel color [x y].\n" " -r: get rectangle area [x1 - left y1 - top x2 - right y2 - bottom].\n" "If FILENAME ends with .png it will be saved as a png.\n" "If FILENAME is not given, the results will be printed to stdout.\n", pname, DEFAULT_DISPLAY_ID ); >static SkColorType flinger2skia(PixelFormat f) < switch (f) < case PIXEL_FORMAT_RGB_565: return kRGB_565_SkColorType; default: return kN32_SkColorType; >> static status_t vinfoToPixelFormat(const fb_var_screeninfo& vinfo, uint32_t* bytespp, uint32_t* f) < switch (vinfo.bits_per_pixel) < case 16: *f = PIXEL_FORMAT_RGB_565; *bytespp = 2; break; case 24: *f = PIXEL_FORMAT_RGB_888; *bytespp = 3; break; case 32: // TODO: do better decoding of vinfo here *f = PIXEL_FORMAT_RGBX_8888; *bytespp = 4; break; default: return BAD_VALUE; >return NO_ERROR; > static status_t notifyMediaScanner(const char* fileName) < String8 cmd("am broadcast -a android.intent.action.MEDIA_SCANNER_SCAN_FILE -d file://"); String8 fileUrl("\""); fileUrl.append(fileName); fileUrl.append("\""); cmd.append(fileName); cmd.append(" >/dev/null"); int result = system(cmd.string()); if (result < 0) < fprintf(stderr, "Unable to broadcast intent for media scanner.\n"); return UNKNOWN_ERROR; >return NO_ERROR; > int main(int argc, char** argv) < ProcessState::self()->startThreadPool(); const char* pname = argv[0]; int x1,x2,y1,y2; bool png = false; bool color = false; bool rectangle = false; int32_t displayId = DEFAULT_DISPLAY_ID; int c; while ((c = getopt(argc, argv, "phdcr:")) != -1) < switch (c) < case 'p': png = true; break; case 'd': displayId = atoi(optarg); break; case 'c': color = true; break; case 'r': rectangle = true; break; case '?': case 'h': usage(pname); return 1; >> argc -= optind; if (color) < x1 = atoi(argv[2])-1; y1 = atoi(argv[3])-1; x2 = atoi(argv[2]); y2 = atoi(argv[3]); >else if (rectangle) < x1 = atoi(argv[3]); y1 = atoi(argv[4]); x2 = atoi(argv[5]); y2 = atoi(argv[6]); optind--; >argv += optind; int fd = -1; const char* fn = NULL; if (argc == 0) < fd = dup(STDOUT_FILENO); >else if (argc == 1 || argc == 4) < fn = argv[0]; fd = open(fn, O_WRONLY | O_CREAT | O_TRUNC, 0664); if (fd == -1) < fprintf(stderr, "Error opening file: %s (%s)\n", fn, strerror(errno)); return 1; >const int len = strlen(fn); if (len >= 4 && 0 == strcmp(fn+len-4, ".png")) < png = true; >> if (fd == -1 && !color && !rectangle) < usage(pname); return 1; >void const* mapbase = MAP_FAILED; ssize_t mapsize = -1; void const* base = NULL; uint32_t w, s, h, f; size_t size = 0; // Maps orientations from DisplayInfo to ISurfaceComposer static const uint32_t ORIENTATION_MAP[] = < ISurfaceComposer::eRotateNone, // 0 == DISPLAY_ORIENTATION_0 ISurfaceComposer::eRotate270, // 1 == DISPLAY_ORIENTATION_90 ISurfaceComposer::eRotate180, // 2 == DISPLAY_ORIENTATION_180 ISurfaceComposer::eRotate90, // 3 == DISPLAY_ORIENTATION_270 >; ScreenshotClient screenshot; sp display = SurfaceComposerClient::getBuiltInDisplay(displayId); if (display == NULL) < fprintf(stderr, "Unable to get handle for display %d\n", displayId); return 1; >Vector configs; SurfaceComposerClient::getDisplayConfigs(display, &configs); int activeConfig = SurfaceComposerClient::getActiveConfig(display); if (static_cast(activeConfig) >= configs.size()) < fprintf(stderr, "Active config %d not inside configs (size %zu)\n", activeConfig, configs.size()); return 1; >uint8_t displayOrientation = configs[activeConfig].orientation; uint32_t captureOrientation = ORIENTATION_MAP[displayOrientation]; status_t result; if (color || rectangle) < result = screenshot.update(display, Rect(x1,y1,x2,y2), x2-x1, y2-y1, 0, -1U, false, captureOrientation); >else < result = screenshot.update(display, Rect(), 0, 0, 0, -1U, false, captureOrientation); >if (result == NO_ERROR) < base = screenshot.getPixels(); w = screenshot.getWidth(); h = screenshot.getHeight(); s = screenshot.getStride(); f = screenshot.getFormat(); size = screenshot.getSize(); if (color) < size_t Bpp = 4; //bytesPerPixel char* rawImageData; rawImageData = (char *)base; base = (void *)((char *)base + Bpp); for (int i=0; ireturn 1; > > else < const char* fbpath = "/dev/graphics/fb0"; int fb = open(fbpath, O_RDONLY); if (fb >= 0) < struct fb_var_screeninfo vinfo; if (ioctl(fb, FBIOGET_VSCREENINFO, &vinfo) == 0) < uint32_t bytespp; if (vinfoToPixelFormat(vinfo, &bytespp, &f) == NO_ERROR) < size_t offset = (vinfo.xoffset + vinfo.yoffset*vinfo.xres) * bytespp; w = vinfo.xres; h = vinfo.yres; s = vinfo.xres; size = w*h*bytespp; mapsize = offset + size; mapbase = mmap(0, mapsize, PROT_READ, MAP_PRIVATE, fb, 0); if (mapbase != MAP_FAILED) < base = (void const *)((char const *)mapbase + offset); >> > close(fb); > > if (base != NULL) < if (png) < const SkImageInfo info = SkImageInfo::Make(w, h, flinger2skia(f), kPremul_SkAlphaType); SkAutoTUnrefdata(SkImageEncoder::EncodeData(info, base, s*bytesPerPixel(f), SkImageEncoder::kPNG_Type, SkImageEncoder::kDefaultQuality)); if (data.get()) < write(fd, data->data(), data->size()); > if (fn != NULL) < notifyMediaScanner(fn); >> else < write(fd, &w, 4); write(fd, &h, 4); write(fd, &f, 4); size_t Bpp = bytesPerPixel(f); for (size_t y=0 ; y> > close(fd); if (mapbase != MAP_FAILED) < munmap((void *)mapbase, mapsize); >return 0; >
Пример получения цвета пикселя:
./screencap -c 100 100
Пример получения изображения по заданной области:
./screencap -r /sdcard/test.png 0 0 720 640
Данный проект залит на GitHub, так что кому интересно — заходите.
P.S. Если вы знаете лучший способ, как найти нужный исходный код исполняемого файла или необходимых библиотек для компиляции — прошу добавить комментарий и я добавлю данный совет в статью.
APK Editor Studio

APK Editor Studio – это мощный и в то же время простой в использовании инструмент для декомпиляции APK. Он позволяет Вам легко распаковывать, редактировать и заменять ресурсы APK. При помощи APK Editor Studio Вы сможете заменить иконку и имя приложения Android, изучить его внутреннюю структуру, удалить ненужные разрешения, автоматически подписать APK, установить его на Ваше устройство, а также многое другое. Благодаря множеству удобных встроенных инструментов Вы можете как вносить небольшие изменения, так и создавать полноценные модификации APK.
3
платформы
Доступно для Windows, macOS и Linux
Shell-скриптинг в среде Android
Android основан на ядре Linux, включает в себя набор стандартных UNIX-команд и простой шелл sh. Все это значит, что мы можем не только использовать командную строку для выполнения низкоуровневых операций, но и писать шелл-скрипты, которые будут выполнять функции, недоступные из графического интерфейса. В этой статье мы поговорим о том, что с их помощью можно сделать и зачем все это нужно.
Для прошлого номера журнала я написал статью о Tasker — системе, которая позволяет автоматизировать работу Android и заменить сотни сторонних приложений. К сожалению, Tasker ограничен высокоуровневыми функциями Android и не позволяет выполнять такие низкоуровневые операции, как монтирование файловых систем, изменение параметров ядра, системных переменных или запуск демонов. Зато все это можно сделать с помощью скриптов.
Сразу оговорюсь, что в этой статье речь пойдет о шелл-скриптах в традиционном для Linux понимании, без использования инструментов вроде SL4A, QPython или Roboto. Главное назначение таких скриптов — изменение поведения системы, параметров ядра, работа с демонами (ADB, например) и тому подобное. Скрипты могут стартовать на этапе загрузки ОС, установки новой прошивки, после тапа по кнопке или же по традиции — из терминала.
В статье я расскажу, как писать такие скрипты, как заставить их стартовать автоматически, привязывать к определенному системному событию. В качестве бонуса также объясню, как заставить консоль восстановления (recovery) выполнить необходимые тебе действия перед установкой или сразу после установки новой прошивки. Начинаем.
Особенности Android-окружения
В самой своей основе, там, где нет Java и Dalvik, Android представляет собой минималистичный Linux-дистрибутив со всеми свойственными ему атрибутами: ядром, системой инициализации, набором библиотек, демонов, консольных команд и, конечно же, шеллом. Последний — это не что иное, как mksh из MirBSD, переименованный в sh; простой командный интерпретатор с поддержкой языковых конструкций классического Bourne shell из UNIX и автодополнением по нажатию Tab.
В качестве комплекта базовых UNIX-команд здесь используется toolbox, своего рода урезанная альтернатива BusyBox, которая позволяет вызывать несколько разных команд из одного бинарника (с помощью симлинков). Toolbox включает в себя очень ограниченный набор команд, в котором нет не только grep или sort, но даже cp. Поэтому для полноценной работы со скриптами настоятельно рекомендуется установка BusyBox, благо в маркете полно бесплатных инсталляторов.
Сам шелл располагается не совсем по адресу, поэтому «шибанг» в скриптах будет выглядеть несколько по-иному, а именно #!/system/bin/sh. Зато о расположении бинарников можно не думать вообще, так как в переменной $PATH всегда прописаны правильные значения. Каталогов для поиска команд тут всегда три: /system/bin/, /system/sbin/ и /system/xbin/ для внешних бинарников. Туда обычно устанавливается BusyBox.
Основное назначение скриптинга в Android — работа с ядром и системными утилитами. Ядро тут стандартное и экспортирует все те же интерфейсы /proc и /sys, через которые можно рулить железом и состоянием системы. Плюс есть набор специфичных для Android утилит, которые будут очень полезны при разработке скриптов:

- pm — менеджер пакетов, позволяет устанавливать, удалять и перемещать софт;
- am — менеджер активностей (Activity), может быть использован для запуска приложений;
- dumpsys — дамп в консоль массы различной информации о состоянии системы;
- screencap — утилита для снятия скриншота;
- screenrecord — утилита для записи скринкастов;
- getprop/setprop — команды для чтения и изменения системных переменных;
- start/stop — запуск и остановка системных служб;
- input — позволяет отправлять в текущее окно кей-коды (эмуляция клавиатуры);
- service — утилита для управления Java-сервисами, имеет очень много возможностей;
- svc — позволяет управлять Wi-Fi, USB-подключением и питанием.

Другие статьи в выпуске:
Хакер #185. Докажи баг!
- Содержание выпуска
- Подписка на «Хакер» -60%
Первый пример
Теперь давайте попробуем написать первый скрипт. Делать это лучше на компе, а еще лучше в Linux или редакторе, который умеет создавать текстовые файлы без символа возврата каретки (который при открытии в Android будет выглядеть как ^M в конце каждой строки). Наш первый скрипт будет состоять всего из двух строк, которые делают бэкап всех установленных приложений на карту памяти. Его код (требует BusyBox):
#!/system/bin/sh mkdir /sdcard/backup cp /data/app/*.apk /sdcard/backup
Сохраняем (пусть он называется apk_backup.sh) и перекидываем на смартфон с помощью ADB:
$ adb push apk_backup.sh /sdcard/
Теперь его нужно запустить. Проще всего сделать это с помощью все того же ADB:
$ adb shell sh /sdcard/apk_backup.sh
Примерно таким же образом скрипт можно запустить из консоли на самом смартфоне/планшете:
$ sh /sdcard/apk_backup.sh
Само собой, такой способ не очень удобен. Поэтому нам нужен какой-то быстрый способ запуска скрипта. Наиболее удобное из найденных мной решений — это приложение QuickTerminal. Устанавливаем, запускаем, переходим на вкладку Quick Command, нажимаем кнопку «+», вбиваем имя (произвольное) и команду (sh /sdcard/apk_backup.sh), в поле Output Type выбираем либо Dialog Output, либо Nothing. В первом случае во время выполнения скрипта на экране появится окно с результатом, во втором все пройдет в фоне. Кому что удобнее. Далее сохраняем и получаем кнопку, с помощью которой скрипт можно будет запустить быстро и легко.
Теперь напишем скрипт, который восстановит наш бэкап:
#!/system/bin/sh for i in /sdcard/backup/*; do pm install -t -r $i done
В нем мы задействовали команду pm с опцией install и флагами -t и -r, которые заставляют систему устанавливать приложения, даже если они подписаны тестовым ключом или уже установлены. Также можно использовать флаг -s, который принуждает приложения к установке на карту памяти (если такая возможность есть), или -f — установка во внутреннюю память устройства.

Имея рут, можно даже сделать бэкап настроек всех приложений с помощью копирования и архивации каталога /data/data/, однако восстановить его будет очень проблематично, так как в Android каждое приложение исполняется от имени созданного специально для него Linux-юзера и хранит настройки внутри каталога, принадлежащего этому пользователю. Проблема здесь в том, что идентификатор Linux-юзера для каждого приложения генерируется динамически, поэтому после восстановления бэкапа в заново установленной системе идентификаторы не будут совпадать и приложения не смогут прочитать свои настройки. Придется вручную выяснять ID юзера для каждого приложения и менять права доступа на каталоги с данными.
С другой стороны, мы можем использовать встроенный в Android Backup Manager, позволяющий сторонним приложениям использовать возможности системы для бэкапа и восстановления приложений и их данных. Управлять им можно из консоли (а значит, и с помощью скриптов), но сам по себе он никакого бэкапа не производит, а возлагает эту работу на сторонние приложения. Helium — одно из таких приложений. Если установить и настроить его, операцию бэкапа и восстановления можно будет заскриптовать. Например, следующий простой скрипт сделает резервную копию всех сторонних приложений:
#!/system/bin/sh # Получаем список всех сторонних приложений for i in `pm list packages -e`; do # Добавляем каждое из них в очередь bmgr backup $ done # Запускаем операцию бэкапа bmgr run
Конструкция $ здесь нужна, чтобы обрезать слово «packages:», которое pm добавляет в начало имени каждого пакета. Чтобы восстановить бэкап, можно использовать либо тот же Helium, либо команду bmgr:
$ bmgr list sets # Получаем список бэкапов $ bmgr restore # Восстанавливаем нужный бэкап
Автозапуск
«Это все круто, но скрипты должны запускаться сами», — скажешь ты и будешь абсолютно прав. Без автозапуска от скриптов толку мало, но это легко исправить, если воспользоваться все тем же Tasker. Он умеет запускать любые шелл-команды в ответ на любое событие. Чтобы воспользоваться этой функциональностью, достаточно создать новый профиль, выбрать событие (для бэкапа лучшим событием будет время), затем добавляем действие, выбираем Script -> Run Shell, вбиваем команду (sh /sdcard/script.sh), выбираем, если необходимо, файл для записи результата и включаем профиль.
Другой популярный способ автозапуска — это использование средств автоматического исполнения скриптов при загрузке в сторонних прошивках. Сегодня почти все сколько-нибудь известные кастомные прошивки умеют стартовать скрипты из каталога /system/etc/init.d/, а в стоке такую функциональность можно получить с помощью приложения Universal init.d из маркета. С последним, однако, надо быть осторожным, так как оно запускает скрипты не на раннем этапе загрузки, как это происходит в том же CyanogenMod, а уже после полной загрузки системы.
Итак, что мы можем поместить в автозагрузку? Например, скрипт запуска демона ADB в сетевом режиме:
#!/system/bin/sh setprop service.adb.tcp.port 5555 stop adbd start adbd
Для подключения к нему с ПК набираем такую команду:
$ adb connect IP-смартфона
Также мы можем применить некоторые оптимизации подсистемы виртуальной памяти:
#!/system/bin/sh echo "4096" > /proc/sys/vm/min_free_kbytes echo "0" > /proc/sys/vm/oom_kill_allocating_task; echo "0" > /proc/sys/vm/panic_on_oom; echo "0" > /proc/sys/vm/laptop_mode; echo "0" > /proc/sys/vm/swappiness echo "50" > /proc/sys/vm/vfs_cache_pressure echo "90" > /proc/sys/vm/dirty_ratio echo "70" > /proc/sys/vm/dirty_background_ratio
Ну или подогнать механизм lowmemorykiller (автоматическое убийство фоновых приложений при нехватке памяти) под наши нужды:
#!/system/bin/sh echo "2048,3072,6144,15360,17920,20480" > /sys/module/lowmemorykiller/parameters/minfree
Ну и конечно же, автоматический выбор планировщика процессов:
#!/system/bin/sh echo "powersave" > /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor
Все это можно сделать с помощью специализированного софта, но зачем загружать систему дополнительным ПО, которое еще и будет висеть в фоне, когда можно обойтись несколькими простыми скриптами?

Запуск скриптов до и после установки прошивки
Почти каждый, кто устанавливает на свой гаджет стороннюю прошивку, также ставит поверх нее пакет с фирменными приложениями Google (gapps), который включает в себя маркет, YouTube, Gmail и другой софт. Каждый раз, когда происходит обновление прошивки, раздел /system, содержащий ее и gapps, полностью стирается, но приложения Google всегда остаются на месте. Это происходит потому, что, кроме всего прочего, gapps содержит в своем составе специальный скрипт, который размещается в каталоге /system/addon.d/ и запускается консолью восстановления до и после установки прошивки. Этот скрипт делает бэкап и восстановление приложений Google.
Мы можем использовать эту возможность для выполнения наших собственных действий до и после установки прошивки. Вот так, например, выглядит мой скрипт восстановления, который ничего не бэкапит, но подчищает прошивку от мусора сразу после ее установки:
#!/sbin/sh # Загружаем подсобные функции . /tmp/backuptool.functions # Рингтон и звук уведомления, которые должны остаться в системе RINGTONE=Machina NOTIFICATION=Argon case "$1" in backup) # Пусто :) ;; restore) # Рингтоны, уведомления и звук будильника cd /system/media/audio/ringtones/ rm [!$]*.ogg cd /system/media/audio/notifications/ rm [!$]*.ogg rm /system/media/audio/alarms/* # Языки синтеза и офлайн-распознавания речи rm /system/tts/lang_pico/* rm -rf /system/usr/srec/config/* # Приложения A=/system/app/ rm $A/Email.apk rm $A/Exchange2.apk rm $A/LockClock.apk rm $A/PicoTts.apk rm $A/Term.apk rm $A/ThemeChooser.apk rm $APPS/WAPPushManager.apk rm $A/LiveWallpapers.apk rm $A/LiveWallpapersPicker.apk rm $A/VisualizationWallpapers.apk A=/system/priv-app/ rm $A/CMUpdater.apk rm $A/ThemeManager.apk ;; pre-backup) # . ;; post-backup) # . ;; pre-restore) # . ;; post-restore) # . ;; esac
Скрипт удаляет рингтоны, уведомления, движок синтеза речи и несколько приложений. Все эти действия запускаются в ответ на передачу скрипту опции командной строки restore (это делает консоль восстановления после установки прошивки), однако также предусмотрены и варианты обработки таких опций, как backup, pre-backup, post-backup, pre-restore и post-restore. Здесь это просто заглушки, но если бы мы захотели сделать бэкап некоторых файлов и приложений перед установкой прошивки, мы могли бы добавить их в блок backup, как это сделано в скрипте /system/addon.d/70-gapps.sh:
. /tmp/backuptool.functions list_files() < cat case "$1" in backup) list_files | while read FILE DUMMY; do backup_file $S/$FILE done ;; .
Этот кусок скрипта прекрасно иллюстрирует, как сделать бэкап файлов. Ключевые элементы здесь: функция listfiles, которая при запуске выводит листинг файлов, и функция backupfile, которая является частью консоли восстановления (определена в файле /tmp/backuptool.functions). Она делает бэкап файлов в цикле.


INFO
По словам разработчика mksh, изначально пользовательские версии Android-смартфонов вообще не должны были иметь в своем составе шелл, но после выпуска смартфона для разработчиков HTC (T-Mobile) G1 он фактически стал стандартной частью системы.
Версии Android 2.3 и ниже вместо mksh использовали минималистичный шелл ash, который входит в базовый комплект всех BSD-систем.
Чтобы получить одни и те же скрипты на всех устройствах, можно использовать приложение DropSync или FolderSync (автоматическая синхронизация через Dropbox).
Что еще?
С помощью скриптов в Android можно сделать намного больше, чем бэкапы и настройка параметров системы. Вот, например, скрипт, который просыпается каждые десять минут и, если уровень заряда батареи стал меньше 30%, отключает Wi-Fi и Bluetooth:
#!/system/bin/sh while true; do if [ `cat /sys/class/power_supply/battery/capacity` -lt 30 ]; then svc wifi disable service call bluetooth_manager 8 fi sleep 600 done
Чтобы скрипт работал в фоне, достаточно вызвать его следующим образом:
$ script.sh &
А это скрипт, который позволяет быстро заполнять формы, требующие ввода имэйла и пароля (в приложениях и на веб-сайтах):
#!/system/bin/sh adb shell input text "user@gmail.com" adb shell input keyevent 23 adb shell input keyevent 20 adb shell input text "ПАРОЛЬ" adb shell input keyevent 23 adb shell input keyevent 20
Запускать его можно разными способами. Либо перед запуском приложения, установив задержку:
$ sleep 15; sh /sdcard/script.sh
Либо повесить на какое-то событие Tasker, например на взмах смартфоном. Другой вариант — использовать буфер обмена. В Android, чтобы вставить нужный текст в буфер обмена, достаточно выполнить такую команду:
$ service call clipboard 2 i32 1 i32 1 s16 "Этот текст появится в буфере обмена"
Не ахти как удобно, зато работает. Как мы можем использовать такую функциональность? Например, сделать простенький скрипт clip.sh:
#!/system/bin/sh service call clipboard 2 i32 1 i32 1 s16 "$1"
Соль в том, что скрипт можно вызывать через удаленный ADB либо вообще поместить в /system/etc/init.d/, заменив $1 на нужный текст. Так нужные нам данные всегда будут под рукой, а бесполезный на смартфоне механизм копирования/вставки получит хоть какое-то назначение. Консольные команды можно использовать и для более высокоуровневых операций, например позвонить по указанному номеру:
$ am start -a android.intent.action.CALL tel:123
Или просто открыть окно номеронабирателя с нужным номером:
$ am start -a android.intent.action.DIAL tel:123
Примерно таким же образом можно отправить SMS:
#!/system/bin/sh am start -a android.intent.action.SENDTO -d sms:$1 --es sms_body "$2" --ez exit_on_sent true sleep 1 input keyevent 22 sleep 1 input keyevent 66
Скрипт принимает два аргумента: номер телефона и содержимое SMS. После запуска он откроет окно SMS-приложения, вставит в него нужный текст, а затем нажмет кнопку Enter для отправки, после чего окно закроется.
Другие полезные при скриптинге команды:
-
Перезагрузка в режим recovery:
$ su -c reboot recovery
$ setprop ctl.stop zygote
$ am start -n com.android.settings/com.android.settings.Settings
$ am start -a android.intent.action.VIEW http://www.google.com
$ am broadcast -a android.intent.action.BATTERY_LOW
$ ip link set eth0 address 00:11:22:33:44:55
$ echo 100 > /sys/devices/virtual/timed_output/vibrator/enable
$ echo 1 > /sys/devices/platform/flashlight/leds/flashlight/brightness
$ stagefright -a -o file.mp3
$ pm disable com.google.android.calendar
$ dumpsys statusbar | grep StatusBarNotification | awk '< print $2 >' | cut -d '=' -f2
#!/system/bin/sh for i in `find /data -iname "*.db"`; do sqlite3 $i 'VACUUM;' done
$ settings put global tether_dun_required 0

Вместо выводов
Для кого-то все описанное в статье может показаться несколько надуманным. Дескать, все это можно сделать с помощью стандартного софта и Tasker. Но зачем использовать тяжелый Java-софт там, где нужное действие можно выполнить с помощью простенького скрипта, который не занимает лишней памяти и может быть легко перенесен на другое устройство? Скрипты удобны, просты, быстро отрабатывают и дают возможность тонкой настройки под себя.
Евгений Зобнин
Редактор рубрики X-Mobile. По совместительству сисадмин. Большой фанат Linux, Plan 9, гаджетов и древних видеоигр.