Перейти к содержимому

Surfaceflinger что за процесс android

  • автор:

Выпил SurfaceFlinger

$subj реален? Выпилить из init’a запуска GUI части.
То есть, чтобы при загрузке телефона был черный экран или заставка, а по usb был adb. А также хотелось бы добавит запуск /data/local/init.sh
все равно экран разбит, сенсор глючит
а в консоли стартовать mpd, чтобы музыку слушать

ubuntuawp ★★
13.05.14 19:07:50 MSK

и всё это для того, чтобы запустить mpd в консоли? Да вы ядерный извращенец, батенька! Тащите!

stevejobs ★★★★☆
( 13.05.14 19:18:52 MSK )

intelfx ★★★★★
( 13.05.14 19:19:55 MSK )

В гугле пишешь disable SurfaceFlinger и получаешь профит. 🙂

a1batross ★★★★★
( 13.05.14 19:24:10 MSK )

Посмотрел на заголовок, на теги … и стало интересно что тут кто выпил. А тут ерунда какая-то.

vyazovoi ★★★
( 13.05.14 19:52:00 MSK )
mittorn ★★★★★
( 13.05.14 20:14:37 MSK )
Ответ на: комментарий от mittorn 13.05.14 20:14:37 MSK

я не нашел там surfaceflinger
еще бы хотел ril и bt убрать))

ubuntuawp ★★
( 14.05.14 13:37:38 MSK ) автор топика
Ответ на: комментарий от ubuntuawp 14.05.14 13:37:38 MSK

service surfaceflinger /system/bin/surfaceflinger 

В книге Embedded Android написано что он стал отдельным сервисом, начаная с 4.0.

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

У меня самого есть телефон с разбитым экраном, захожу на него через WebKey. Думаешь если отключить графику будет быстрее работать?

Или у тебя ложные срабатывания, которые мешают удаленному управлению?

tlx ★★★★★
( 14.05.14 14:53:25 MSK )
Ответ на: комментарий от tlx 14.05.14 14:53:25 MSK

Мне графика ненужна)
Я хочу использовать как домашний сервер — mpd и бложик
Графика отъедает память и lighttpd постоянно падает (OOM, mothefucker)
Дело в том, что у меня 2.3

Изучаем принципы взаимодействия Ubuntu Touch и Android

Привет, хабр.

Пару месяцев назад я занимался портированием Ubuntu Touch на платформу Allwinner A10 ,
в процессе делал заметки себе на память. Сейчас, на мой взгляд, они всё ещё актуальны, пока Ubuntu Touch окончательно не переехала на свой графический сервер Mir и так далее.

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

Стиль изложения далёк от технического, но если вы не против, то
приглашаю под кат.

Введение
Что такое libhybris

libhybris — прослойка, позволяющая подгружать в Glibc userspace библиотеки из Bionic userspace, на лету заменяя некоторые символы вариантами из Glibc. Проще говоря, данное решение позволяет использовать проприетарные библиотеки для Android в Linux-пространстве. Наибóльшая польза, конечно же, в возможности использовать проприетарные GPU-драйвера, собранные производителем только под Android.

Что такое surfaceflinger

surfaceflinger — нативный сервис андроида, композитный менеджер графических слоёв.

  • http://www.opersys.com/downloads/inside-androids-ui-130530.pdf
  • http://0xlab.org/~jserv/android-binder-ipc.pdf
  • dpolishuk поделился отличным документом — Android Binder
Ubuntu Touch

Ubuntu Touch Developer Preview сама по себе основана на Android, заимствует необходимые сервисы для работы с железом. Общий обзор зависимости можно почитать здесь — Ubuntu Touch Porting или в заметке на OpenNet.

В качестве базовой операционной системы используется обычный Android JB 4.2 , а точнее CyagenMod-10.1 (репозиторий подпроектов CM — phablet.ubuntu.com/gitweb). Из него удалено всё что связано с dalvik и java — оставлена только нативная часть, состоящая из системных сервисов и HAL . При желании можно использовать AOSP 4.1 , но будьте готовы к адаптации под нативное API от 4.1, оно не покрыто никакой документацией и тем более спецификацией и меняется от релиза к релизу.

Компоненты UT располагаются в chroot , используется самописная утилита uchroot, отрывок:

static int ubuntum(void *a) < /* Chroot */ chroot("/data/ubuntu"); chdir("/"); /* Set basic env variables */ char *const envp[8] = < "container=aal", "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "SHELL=/bin/bash", "HOME=/root", "USER=root", "USERNAME=root", "LOGNAME=root", NULL >; /* Exec shell */ execle("/sbin/init", "/sbin/init", "--verbose", NULL, envp); return 0; > 

Для взаимодействия Android-окружения и chroot-окружения Ubuntu задействован механизм libhybris .

Компоненты Ubuntu Touch
  • platform-api
  • qtubuntu
bzr branch lp:platform-api bzr branch lp:qtubuntu 
Ubuntu Platform API

Ubuntu platform API — низкоуровневый API для выполнения базовых операций с использованием возможностей платформы (Android).

  • ubuntu_application_ui_show_surface
  • ubuntu_application_ui_hide_surface
  • ubuntu_application_ui_move_surface_to
  • ubuntu_application_ui_resize_surface_to
  • include — абстрактная декларация platform API
  • android — реализация platform API под Android (я бы уточнил — под Android 4.2)
Package: libplatform-api1-hybris Depends: libhybris Description: Hybris implementation of the Platform API (runtime) This package provides the hybris implementation of the Platform API. The produced library should be used via libhybris, to communicate with the Android userspace, which is where the Ubuntu Application Manager lives. 

Ага, значит, судя по android/hybris/Android.mk, реализация platform API собирается в виде библотеки libubuntu_application_api с линковкой с нативными либами андроида и помещается в android userspace:

LOCAL_SRC_FILES := \ ubuntu_application_api_for_hybris.cpp \ ubuntu_application_gps_for_hybris.cpp \ ubuntu_application_sensors_for_hybris.cpp \ ../default/default_ubuntu_application_sensor.cpp \ ../default/default_ubuntu_application_ui.cpp \ ../default/default_ubuntu_ui.cpp \ application_manager.cpp LOCAL_MODULE := libubuntu_application_api LOCAL_SHARED_LIBRARIES := \ libandroidfw \ libbinder \ libutils \ libgui \ libEGL \ libGLESv2 \ libhardware \ libhardware_legacy 

Без внимания осталась директория platform-api/src/android, рассмотрим её в деталях. Судя по наличию файла CMakeLists.txt , сборка идёт уже для glibc.

Есть один-единственный файл с кодом — ubuntu_application_api.cpp, заглянув в которой мы увидим:

extern void *android_dlopen(const char *filename, int flag); extern void *android_dlsym(void *handle, const char *symbol); 

— использование процедур libhybris для динамической загрузки символов из shared-либы из android userspace.

struct Bridge < static const char* path_to_library() < return "/system/lib/libubuntu_application_api.so"; >Bridge() : lib_handle(android_dlopen(path_to_library(), RTLD_LAZY)) < assert(lib_handle && "Error loading ubuntu_application_api"); >. void* resolve_symbol(const char* symbol) const < return android_dlsym(lib_handle, symbol); >void* lib_handle; >; 

— нехитрый мост для подгрузки символов из libubuntu_application_api.so , которая ладит с нативными сервисами андроида, и которую мы совсем недавно мысленно «собрали» с помощью android/hybris/Android.mk.

#define IMPLEMENT_VOID_FUNCTION3(symbol, arg1, arg2, arg3) \ void symbol(arg1 _1, arg2 _2, arg3 _3) < \ static void (*f)(arg1, arg2, arg3) = NULL; \ DLSYM(&f, #symbol); \ f(_1, _2, _3); >. IMPLEMENT_VOID_FUNCTION2(ubuntu_application_ui_init, int, char**); IMPLEMENT_FUNCTION0(StageHint, ubuntu_application_ui_setup_get_stage_hint); IMPLEMENT_FUNCTION0(FormFactorHint, ubuntu_application_ui_setup_get_form_factor_hint); IMPLEMENT_VOID_FUNCTION1(ubuntu_application_ui_start_a_new_session, SessionCredentials*); IMPLEMENT_VOID_FUNCTION2(ubuntu_application_ui_set_clipboard_content, void*, size_t); . 

— куча обёрток для символов API, реализованных в libubuntu_application_api.so .

  • libubuntu_application_api.so — библиотека под bionic, живёт в android userspace;
  • libubuntu_application_api.so — библиотека под glibc, живёт в linux userpace (chroot), грузит символы из первой через libhybris.
Ubuntu Application Manager

В platform-api/android/hybris помимо реализации Ubuntu platform API находятся исходники ubuntuappmanager — сервиса приложений Ubuntu, он живёт в android userspace и, судя по Android.mk, активно использует libubuntu_application_api и общается через Binder IPC с сервисами андроида.

LOCAL_SRC_FILES:= \ application_manager.cpp \ default_application_manager.cpp \ LOCAL_MODULE:= ubuntuappmanager LOCAL_MODULE_TAGS := optional LOCAL_SHARED_LIBRARIES := \ libbinder \ libinput \ libgui \ libskia \ libandroidfw \ libutils \ libEGL \ libGLESv2 \ libubuntu_application_api 

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

 void update_app_lists(); void binderDied(const android::wp& who); void register_a_surface(. ); void request_fullscreen(. ); int get_session_pid(const android::sp& session); void focus_running_session_with_id(int id); void unfocus_running_sessions(); int32_t query_snapshot_layer_for_session_with_id(int id); android::IApplicationManagerSession::SurfaceProperties query_surface_properties_for_session_id(int id); void switch_focused_application_locked(size_t index_of_next_focused_app); void switch_focus_to_next_application_locked(); void kill_focused_application_locked(); void start_a_new_session( int32_t session_type, int32_t stage_hint, const android::String8& app_name, const android::String8& desktop_file, const android::sp& session, int fd); 
QtUbuntu

Разбираемся с частью UT, отвечающей за взаимодействие между Ubuntu platform API и Qt/QML приложениями.

Если вы не знакомы с Qt Platform Abstraction, то, в кратце, это возможность абстрагироваться от платформы, на которой запускаются приложения Qt с помощью специально написанных QPA-плагинов.

В QPA-плагине реализуются базовые методы вроде createPlatformWindow , а затем Qt приложение, когда захочет создать окошко, использует символ createPlatformWindow из плагина абстракции и в ус не дует, куда оно там дальше пошло.

В данном случае мы будем иметь дело с QPA плагином для работы с Ubuntu application API.

~/ubuntu/qtubuntu $ tree . ├── qtubuntu.pro ├── src │ ├── modules │ │ ├── application  

Судя по содержимому ubuntu.pro, платформа линкуется с glibc-версией libubuntu_application_api.so
Обратим внимание на следующие вызовы методов из набора platform API, использованные в integration.cc и window.cc:

#include ubuntu_application_ui_start_a_new_session(&credentials); ubuntu_application_ui_destroy_surface(surface_); ubuntu_application_ui_create_surface(&surface_, "QUbuntuWindow", geometry.width(), geometry.height(), static_cast(role), flags, eventCallback, this); ubuntu_application_ui_move_surface_to(surface_, geometry.x(), geometry.y()); ubuntu_application_ui_request_fullscreen_for_surface(surface_); ubuntu_application_ui_move_surface_to(surface_, rect.x(), rect.y()); ubuntu_application_ui_resize_surface_to(surface_, rect.width(), rect.height()); ubuntu_application_ui_request_fullscreen_for_surface(surface_); ubuntu_application_ui_show_surface(surface_); ubuntu_application_ui_hide_surface(surface_); 

Теперь понятно, что когда наше Qt приложение захочет создать окошко, то оно вызовет метод из QPA платформы qubuntu — QUbuntuIntegration::createPlatformWindow из файла integration.cc:

QPlatformWindow* QUbuntuIntegration::createPlatformWindow(QWindow* window) < . // Create the window. QPlatformWindow* platformWindow = new QUbuntuWindow(. ); . >

Заглядывая в конструктор QUbuntuWindow в файле window.cc, находим вызов метода QUbuntuWindow::createWindow() :

void QUbuntuWindow::createWindow() < . ubuntu_application_ui_create_surface( &surface_, "QUbuntuWindow", geometry.width(), geometry.height(), static_cast(role), flags, eventCallback, this); . ubuntu_application_ui_move_surface_to(surface_, geometry.x(), geometry.y()); . > 

Это крайне урезанный код, но суть ясна — делаются вызовы к Ubuntu platform API , которое у нас реализовано в glibc-версии libubuntu_application_api.so , которая, на самом деле, является мостом к bionic-версии libubuntu_application_api.so , код которой лежит в platform-api/android.

// Это в ubuntu_application_ui_create_surface ubuntu::application::ui::Surface::Ptr surface = session->create_surface(props, ubuntu::application::ui::input::Listener::Ptr(new CallbackEventListener(cb, ctx))); // Это в ubuntu_application_ui_move_surface_to auto s = static_cast*>(surface); s->value->move_to(x, y); 

Нам осталось открыть матрёшку и найти, как же реализованы ubuntu::application::ui::Session и, соответственно, ubuntu::application::ui::Surface . А реализованы они в этом файле — ubuntu_application_api_for_hybris.cpp:

namespace android < . struct Session : public ubuntu::application::ui::Session, public UbuntuSurface::Observer < . Session(. ) < . ubuntu::application::ui::Surface::Ptr create_surface( const ubuntu::application::ui::SurfaceProperties& props, const ubuntu::application::ui::input::Listener::Ptr& listener) < . // О, а вот и вызов конструктора. Осталось только перемотать вверх и найти реализацию UbuntuSurface UbuntuSurface* surface = new UbuntuSurface(client, client_channel, looper, props, listener,this); . // 100% это наш клиент, теперь нужно смотреть UbuntuSurface return ubuntu::application::ui::Surface::Ptr(surface); . 

Перематываем, находим UbuntuSurface :

struct UbuntuSurface : public ubuntu::application::ui::Surface < . UbuntuSurface(const sp& client, . ) : ubuntu::application::ui::Surface(listener) < // Вот это место - прямое обращение к Android API surface_control = client->createSurface( String8(props.title), props.width, props.height, PIXEL_FORMAT_RGBA_8888, props.flags & . ); surface = surface_control->getSurface(); . 

Получаем некий объект типа android::SurfaceControl , который является результатом вызова android::SurfaceComposerClient()->createSurface() .
Через него проходят все обращения к android::SurfaceComposerClient (frameworks/native/libs/gui/Surface.cpp), такие как: изменение размеров, перемещение, изменение порядка слоёв и так далее.

Возвращаясь назад по цепочке, понимаем, что же на самом деле происходит, когда мы запускаем очередное Qt приложение с QPA платформой Ubuntu.

Заключение

На этом моменте я вынужден себя остановить, поскольку, на мой взгляд, рассмотренный принцип взаимодействия Ubuntu Touch и Android является самодостаточным. Дальнейшие рассуждения могут идти уже в отрыве от всего вышеописанного. Нерасмотренными остались вопросы взаимодействия qmlscene и ubuntuappmanager , принцип контроля ввода с помощью сервисов SurfaceFlinger и InputDispatcher и другие вопросы из уголков этой простороной темы. Но это уже совсем другая история.

Через неделю приедет телефон на Firefox OS, распотрошу его…

  • ubuntu touch
  • android native application
  • surfaceflinger
  • qt5

What is Android's SurfaceFlinger

Android’s SurfaceFlinger is a system service provided on the Android Operating System. Now that that is out of the way… What does it actually do? If you’ve worked with scrolling, you’ll know that “flinging” is an action that a user can do, but it’s not related to that. Actually, the SurfaceFlinger is a service that plays a critical part in determining what is rendered on the screen on any Android device. It’s important to note that the SurfaceFlinger doesn’t directly render anything to the screen. In fact, all it does is merely composite buffers of data before handing them off to the HAL. In layman’s terms — The SurfaceFlinger takes in buffers of display data, composites them into a single buffer, and then passes that on to the hardware abstraction layer. The HAL is an acronym for the Hardware Abstraction Layer. This layer bridges the gap between the software that runs on Android and the actual hardware responsible for running the operating system. This layer allows Android to effectively run on thousands of different devices!

What is a Buffer?

A buffer is simply a region of memory allocated for storing something temporarily while it’s moved to another place. However, the SurfaceFlinger works specifically with buffers that contain graphics and display data. It’s the same idea as the regular buffers that I just mentioned, except that all the data contained in them is used to inform something how the screen should be displayed.

Where does the SurfaceFlinger get it's buffers?

3parts.jpg

In most Android applications, the SurfaceFlinger has access to three different buffers. A buffer for the Status Bar, a buffer for the Navigation Bar, and a Buffer for the application content. You can see each of these regions in the diagram below.

The above image shows three different sections of our Android device’s screen, each powered by their own buffer! If you’ve built apps before, you’ll know that we cant completely control the status bar or the navigation bar, so we can assume that these buffers are provided by the operating system. However, the Application Content section is created by us but we don’t have to new up a Buffer Object or anything like that to render content to the screen! This is all taken care of for us, by the Operating System.

How many Buffers?

In a vast majority of applications, you (and consequently the SurfaceFlinger) will only deal with three buffers. However, it’s possible that you will actually have more than three buffers. It’s even possible that you’ll have fewer than three buffers as well! We know that we can hide the status bar by setting the appropriate WindowManager flags (or using a theme that omits the status bar). In this example, we’d only have two buffers. Consequentially, we can have 4 or more buffers by including a SurfaceView in our application. Each SurfaceView will have it’s own buffer that is composited by the SurfaceFlinger and not your application!

How does the SurfaceFlinger work?

When an app is summoned to the foreground, the WindowManager system-service asks the SurfaceFlinger system-service for a surface. The SurfaceFlinger then creates a new layer and a Binder object. The Binder is then returned to the WindowManager (and consequentially to the application) which can then start sending frames to SurfaceFlinger.

How does this all fit together?

ape_fwk_graphics.png

A great question! Fortunately Google has built a fantastic diagram that shows the flow of data from inception to render.

In the above image, you can see that many different components can create buffer data. The buffer data makes its way to the SurfaceFlinger (or an OpenGL ES App, but that’s another topic entirely) and ultimately to the HAL so that it can be rendered to the screen.

Handling Refresh Rate and VSYNC

Most Android Devices (phones and tablets) have a 60FPS (or 60 frames per second) refresh rate. If we were to refresh our screen while processing buffer data, the results could be uncomfortable. Often times, this display phenomena appears as tearing. To avoid this, it’s imperative that the SurfaceFlinger only updates contents between refreshes. This is accomplished by the system sending a VSYNC signal (or a vertical-sync signal). This signal is used to to help the operating system (and your application) sync up with the rate of the display hardware. With these things in mind, it would be odd to assume that the surface flinger just draws whenever there is buffer data available. While it may looks this way, because our eyes have trouble seeing this fast, the SurfaceFlinger actually sleeps until the VSYNC signal arrives. When that signal does arrive, the SurfaceFlinger wakes up and walks through each list of layers, looking for new buffers. If it finds a new buffer, it’ll toss the reference to the old one and use the new buffer instead, otherwise it’ll hold onto the old buffer. Finally, the SurfaceFlinger then reaches out to the HAL to ask how it should handle compositing these buffers.

Why should I care about the SurfaceFlinger?

If you’ve ever used systrace to profile your Android app, you’ve probably seen a lot of content in the SurfaceFlinger section. In fact, this entire post started after a disappointing systrace report. That report prompted me to do research into how I can and can’t influence SurfaceFlinger to be more performant. While you can’t do much to influence SurfaceFlinger much as an application developer, it’s important to know how SurfaceFlinger works so that you can easily identify oddities while performance profiling and create a plan on how to fix those oddities. Hopefully you’ve found this post helpful! If you’d like to learn more about Android Development, you can find more of my Android posts here !

Connect with me to follow along on my journey in my career, open source, and mentorship. Occasionally, I'll share good advice and content (quality not guaranteed).

Android Window: Базовые концепции

Window это abstract базовый класс который использует для отображения контента и взаимодействия пользователя с приложением.

public abstract class Window

Говоря проще Window представляет собой прозрачный прямоугольник на котором присходит отрисовка всех наших View.

Во время создания Activity , класс ActivityThread вызывает метод attach у Activity .

Метод attach вызывается перед onCreate .

В методе attach происходит создание Window для Activity:

@UnsupportedAppUsage 
final void attach(Context context, . ) // .
mWindow = new PhoneWindow(this, window, activityConfigCallback);
// .
>

PhoneWindow есть 2 важных параметра DecorView и ViewGroup .

private DecorView mDecor;

ViewGroup mContentParent;

DecorView является inner классом PhoneWindow и представляем собой root контейнер в иерархии View для Activity . DecorView расширяет FrameLayout .

Например: DecorView содержит фон окна, который можно отрисовать и вызов метода getWindow().setBackgroundDrawable(Drawable drawable) из вашей Activity изменяет фон окна.

mContentParent хранит View в котором размещается содержимое окна. Те по сути это root container для UI элементов которые мы добавляем на нашей Activity.

Window имеет единую иерархию View’s которые прикреплены к Window и которые обеспечивают поведение данного Window .

Так же стоит обратить внимание что макет для DecorView создается на основе темы, указанной для Activity или для приложения в целом.

mContentParent = generateLayout(mDecor);


protected ViewGroup generateLayout(DecorView decor)// .
// .
int layoutResource;
if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) layoutResource = R.layout.screen_simple_overlay_action_mode;
> else // Embedded, so no decoration is needed.
layoutResource = R.layout.screen_simple;
// System.out.println("Simple!");
>
// .
>


final View root = inflater.inflate(layoutResource, null);

Мы можем обратить внимание что кроме нашего контента Activity хранить еще дополнительные View's .

mContentParent — является ViewGroup и предназначен для хранения в себе других элементов View .

Как правило когда мы создаем нашу Activity то используем метод :

setContentView(R.layout.activity_main);

но мы можем использовать еще один метод addContentView :

@Override
public void addContentView(View view, ViewGroup.LayoutParams params) initViewTreeOwners();
getDelegate().addContentView(view, params);
>

Данный метод не удаляет элементы которые были добавлены к mContentParent а добавляет View как Child элемент.

@Override
protected void onCreate(Bundle savedInstanceState) super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

TextView textView = new TextView(this);
textView.setText("Hello Alex");
textView.setTextSize(50);
ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
addContentView(textView, lp);
>

И как выглядит наш TextView :

Такие элементы как Activity , Dialog , StatusBar имеют свой Window . Каждый Window имеет свой собственный Surface на котором происходит отрисовка.

Surface представляет собой тонкую оболочку вокруг списка буферов, совместно используемого службой SurfaceFlinger .

Помимо Surface нашего приложения, существуют системные Surface .

SurfaceFlinger — это системная служба Android , отвечающая за компоновку всех Surfaces приложения и системы в единый буфер, который в конечном итоге должен отображаться контроллером дисплея. SurfaceFlinger не доступен напрямую разработчику приложения.

Распространенным заблуждением является то, что SurfaceFinger предназначен для рисования, это не так. Рисование — это работа OpenGL. SurfaceFlinger также использовал OpenGL для композитинга.

Результат композиции будет помещен в системный буфер или собственное Window , которое является источником, из которого контроллер дисплея извлекает данные. Это то, что мы видим на экране.

Surface обычно имеет более одного буфера (обычно два) для выполнения рендеринга с двойной буферизацией: приложение может отрисовывать свое следующее UI состояние , в то время как SurfaceFlinger компонует экран используя последнее значение буфера, не дожидаясь завершения отрисовки.

Проще говоря Surface — это объект, содержащий пиксели, которые компонуются на экране.

Всякий раз, когда Window необходимо перерисоваться, то это происходит в Surface данного Window .

Window вызывает метод lockCanvas у Surface . Метод lockCanvas возвращает объект Canvas который прокидывается в метод onDraw нашего View .

После того как работа с Canvas завершена, вызывается метод unlockCanvasAndPost у Surface и передается Canvas в буфер на отрисовку.

Объект Canvas ни чего не рисует, это всего лишь набор команд которые необходимо выполнить.

drawCircle(centerX, centerY, radius, paint)
drawRoundRect(left, top, right, bottom, cornerRadiusX, cornerRadiusY, paint)

Методы lockCanvas и unlockCanvasAndPost synchronized , синхронизация гарантирует, что одновременно может рисовать только один поток.

ViewRootImpl:

Ранее мы говорили о том что DecorView является root View для иерархии всех View на Window , но мы не упомянули что еще одни View это ViewRootImpl .

ViewRootImpl — это вершина иерархии View , которая является специальной View которая ни чего не отрисовывает но отвечает за взаимодействие Window и View .

ViewRootImpl владеет экземпляром DecorView , через который осуществляется управление отрисовкой DecorView .

Как же DecorView добавляется на экран?

За это отвечает WindowManager .

Давайте посмотрим на метод handleResumeActivity в ActivityThread :

// ActivityThread public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward, 
String reason) …
final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
final int forwardBit = isForward
? WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION : 0 ;

// If the window hasn't yet been added to the window manager,
// and this guy didn't finish itself or start another activity,
// then go ahead and add the window.
boolean willBeVisible = ! a.mStartedActivity;
(! willBeVisible) try willBeVisible = ActivityManager.getService().willActivityBeVisible(
a. getActivityToken());
> catch (RemoteException e) throw e. rethrowFromSystemServer();
>
>
if (r.window == null && !a.mFinished && willBeVisible) r.window = r.activity.getWindow();
View decor = r. window. getDecorView();
decor.setVisibility(View.INVISIBLE);
ViewManager wm = a. getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
l.softInputMode |= forwardBit;

if (a. mVisibleFromClient) if (! a. mWindowAdded) a.mWindowAdded = true ;
wm.addView(decor, l);
> else // The activity will get a callback for this change
// earlier. However, at that time the decor will not be set (this is set
// in this method), so no action will be taken. This call ensures the
// callback occurs with the decor set.
a. onWindowAttributesChanged(l);
>
>

// If the window has already been added, but during resume
// we started another activity, then don't yet make the
// window visible.
> else if (! willBeVisible) if (localLOGV) Slog.v(TAG, "Launch " + r + " mStartedActivity set" );
r.hideForNow = true ;
>

// Get rid of anything left hanging around.
cleanUpPendingRemoveWindows(r, false /* force */ );

// The window is now visible if it has been added, we are not
// simply finishing, and we are not starting another activity.
if (!r.activity.mFinished && willBeVisible && r.activity.mDecor != null && ! r. hideForNow) if (r. newConfig != null ) performConfigurationChangedForActivity(r, r. newConfig);
if (DEBUG_CONFIGURATION) Slog.v(TAG, "Resuming activity " + r.activityInfo.name + "with newConfig"
+ r.activity.mCurrentConfig);
>
r.newConfig = null ;
>
if (localLOGV) Slog.v(TAG, "Resuming " + r + " with isForward=" + isForward);
WindowManager.LayoutParams l = r.window.getAttributes();
if ((l.softInputMode
& WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION)
!= forwardBit) l.softInputMode = (l.softInputMode
& (~ WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION))
| forwardBit;
if (r.activity.mVisibleFromClient) ViewManager wm = a. getWindowManager();
View decor = r. window. getDecorView();
wm.updateViewLayout(decor, l);
>
>

r.activity.mVisibleFromServer = true ;
mNumVisibleActivities ++ ;
if (r.activity.mVisibleFromClient) // addview will also be called here
r.activity.makeVisible();
>
>

r.nextIdle = mNewActivities;
mNewActivities = r;
if (localLOGV) Slog.v(TAG, "Scheduling idle handler for " + r);
Looper.myQueue().addIdleHandler( new Idler());
>

Приведенный выше код в основном выполняет следующие действия:

Первое, получает DecorView , и делает его невидимым, а затем добавьте View в WindowManager через wm.addView(decor, l);

После того, вызваем метод makeVisible , чтобы сделать View видимым. Если в это время DecorView не добавлен в WindowManager , он будет добавлен.

void makeVisible() if (!mWindowAdded) ViewManager wm = getWindowManager(); 
wm.addView(mDecor, getWindow().getAttributes());
mWindowAdded = true;
>
mDecor.setVisibility(View.VISIBLE);
>

Интерфейс WindowManager реализуется в WindowManagerImpl , и он реализует метод addView через прокси-сервер WindowManagerGlobal . Давайте посмотрим на метод addView :

@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) applyTokens(params);
mGlobal.addView(view, params, mContext.getDisplayNoVerify(), mParentWindow,
mContext.getUserId());
>

В WindowManagerGlobal происходит создание ViewRootImpl :

public void addView(View view, ViewGroup. LayoutParams params, 
Display display, Window parentWindow) // .

root = new ViewRootImpl(view. getContext(), display);
view.setLayoutParams(wparams);

mViews.add(view);
mRoots. add(root);
mParams. add(wparams);
// do this last because it fires off messages to start doing things
try root.setView(view, wparams, panelParentView);
> catch (RuntimeException e) // BadTokenException or InvalidDisplayException, clean up.
if (index >= 0 ) removeViewLocked(index, true );
>
throw e;
>
>

В WindowManagerGlobal хранится ViewRootImpl а также происходить добавление DecorView к ViewRootImpl .

И так что мы имеем в итоге:

В ActivityThread вызывается метод handleResumeActivity в котором мы получаем DecorView . Прокидываем DecorView к WindowManager который в свою очередь обращается в WindowManagerGlobal для добавления DecorView .

WindowManagerGlobal создает ViewRootImpl и добавляет DecorView если это нужно.

ViewRootImpl является родителем DecorView. Поскольку DecorView — это верхний уровень нашего макета.

DecorView создается в PhoneWindow . Цепочка вызовов для создания DecorView :

Activity.setContentView -> PhoneWindow.setContentView -> installDecor

View:

И так мы поняли что у нас с иерархией View в приложении, давайте обсудим что же из себя представляет View .

View это интерактивный UI элемент находящийся на Window .

Для наглядности рассмотрим схему того как инициализируется View .

Constructor:

И так когда мы создаем нашу View вызывается один из конструкторов. View имеет четыре конструктора.

// следует использовать если мы создаем View из кода
View(Context context)

// используется для создание через XML макет
View(Context context, @Nullable AttributeSet attrs)


// Два других конструктора предназначены для вызова дочерними классами
// для предоставления стиля по умолчанию через атрибут темы и
// прямой ресурс стиля по умолчанию.
View(Context context, @Nullable AttributeSet attrs, int defStyleAttr)
View(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes)

onAttachedToWindow:

Метод вызывается, когда View присоединено к Window . В этот момент у него есть Surface , и View может начать рисовать.

Обратите внимание, что эта функция гарантированно будет вызываться до onDraw(Canvas), однако ее можно вызывать в любое время до первого onDraw, в том числе до или после onMeasure(int, int).

Данный метод вызывается когда мы делаем Inflate View у Activity или Fragment.onCreateView .

onDetachedFromWindow:

Вызывается, когда View отсоединено от Window . В этот момент Surface недоступен и View не может ни чего рисовать.

onMeasure:

Данный метод вызывается что бы определить размеры View.

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) super.onMeasure(widthMeasureSpec, heightMeasureSpec);
>

Метод принимает в параметры переменные widthMeasureSpec и heightMeasureSpec , которые в свою очередь представляют собой требования измерения ширины и высоты View .

Если мы самостоятельно указываем размер View то следует использовать метод setMeasuredDimension в который мы передаем высоту и ширину View.

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) super.onMeasure(widthMeasureSpec, heightMeasureSpec);

int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
setMeasuredDimension(width, height);
>

MeasureSpec — это класс, используемый для определения размеров View в Android.

MeasureSpec инкапсулирует требования к макету, передаваемые от родителя к дочернему элементу.

Каждый MeasureSpec представляет собой требование либо к ширине, либо к высоте. MeasureSpec состоит из размера и режима. Возможны три режима:

  • UNSPECIFIED размер View может быть любым, parent View ни как не ограничивает размерй нашей View.
  • EXACTLY parent View определил точный размер для View. View будут даны эти границы независимо от того, насколько большим он хочет быть.
  • AT_MOST View может быть настолько большим, насколько он хочет, до указанного размера.

Для определения размеров View можно использовать методы MeasureSpec.getSize() и MeasureSpec.getMode() .

getMode — возвращает один из режимов измерения: UNSPECIFIED,EXACTLY,AT_MOST

getSize — возвращает размер в пикселях.

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)  
int desiredWidth = 100;
int desiredHeight = 100;

int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);

int width;
int height;

//Measure Width
if (widthMode == MeasureSpec.EXACTLY) //Must be this size
width = widthSize;
> else if (widthMode == MeasureSpec.AT_MOST) //Can't be bigger than.
width = Math.min(desiredWidth, widthSize);
> else //Be whatever you want
width = desiredWidth;
>

//Measure Height
if (heightMode == MeasureSpec.EXACTLY) //Must be this size
height = heightSize;
> else if (heightMode == MeasureSpec.AT_MOST) //Can't be bigger than.
height = Math.min(desiredHeight, heightSize);
> else //Be whatever you want
height = desiredHeight;
>

//MUST CALL THIS
setMeasuredDimension(width, height);
>

onSizeChanged:

Вызывается когда View изменила размеры.

Метод вызывается до onLayout() , но внутри метода layout() , вызванным parent View.

Метод layout() вызывает setFrame() , который вызывает onSizeChanged() , а уже потом вызывается onLayout() .

В метод onSizeChanged мы не можем положиться на то что Child View правильно расположен и его размер высчитан.

onLayout:

Методы вызывает когда у View изменился размер или позиция.

Обычно переопределяется когда нужно задать размер дочерним View .

@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) if (left > 0) layout(10, 50, right, bottom);
> else super.onLayout(changed, left, top, right, bottom);
>
>

onDraw:

Размер и позиция на Window расчитаны для View , по этому View готово для отрисовки. В метод приходит объект Canvas которому можно задать команды для отрпавки их на GPU .

onFinishInflate:

Вызывается после того как все Child View были добавлены к Window .

Если View было создано таким образом:

new CustomView(context);

то мы не получим вызов метода onFinishInflate .

Сохранение состояния:

View предоставляем методы onSaveInstanceState и onRestoreInstanceState для возможности сохранить какое то состояние в Bundle .

Обновление View:

Иногда возникает ситуация когда нам нужно программно изменить нашу View . Для этого вызываются методы invalidate и requestLayout

  • invalidate() — для перерисовки View.
  • requestLayout() — для изменения размеров View.

layout, measure:

Когда мы используем ViewGroup то может возникнуть ситуация что нам необходимо перерасчитать child View для этого вызываются методы layout и measure .

draw:

Вызывает метод onDraw передав туда Canvas который мы передали в метод draw(Canvas canvas) .

final View child = new TextView(getContext());

@Override
protected void onDraw(Canvas canvas) super.onDraw(canvas);
child.draw(canvas);
>

onTouchEvent:

Давайте представим что у нас есть некая View на экране на которую нажал пользователь:

Window c которым взаимодействует пользоатель получает событие в методе superDispatchTouchEvent

@Override
public boolean superDispatchTouchEvent(MotionEvent event) return mDecor.superDispatchTouchEvent(event);
>

Событие передается в виде объекта MotionEvent . MotionEvent содержит такие данные как координаты, тип события , время события и другие данные.

Window передает событие DecorView . DecorView является ViewGroup по этому оповестит всех своих child View о событии. Значит root контейнер который мы указали для нашего Activity получит событие в методе dispatchTouchEvent .

Для ViewGroup мы можем переопредилть метод onInterceptTouchEvent и на определенные события возвращать true, тем самых не отправлять какие то события в child View .

Например ScrollView не отправляем событие скролла для child View .

Если мы ни как не переопределяем логику в методе dispatchTouchEvent то событие доходт в top View .

Стоит обратить внимание что если у View задан OnTouchListener то он первым получается шанс обработать MotionEvent.

childView.setOnTouchListener(new OnTouchListener() @Override 
public boolean onTouch(View v, MotionEvent event) // handle MotionEvent
return true;
>
>);

Далее идем по цепочке вниз пока на найдем View которая обрабатает MotionEvent .

Если ни одна из View не обработала событие MotionEvent то шанс предоставляется для Activity :

public boolean onTouchEvent(MotionEvent event) if (mWindow.shouldCloseOnTouch(this, event)) finish(); 
return true;
>

return false;
>

GestureDetector:

В Android есть такой класс GestureDetector которые помогает работать с MotionEvent .

C помощью GestureDetector мы можем распознать singleTap , doubleTap , longPress , scroll и fling .

public interface OnGestureListener  
boolean onDown(@NonNull MotionEvent e);

void onShowPress(@NonNull MotionEvent e);

boolean onSingleTapUp(@NonNull MotionEvent e);

boolean onScroll(@NonNull MotionEvent e1, @NonNull MotionEvent e2, float distanceX,
float distanceY);

void onLongPress(@NonNull MotionEvent e);

boolean onFling(@NonNull MotionEvent e1, @NonNull MotionEvent e2, float velocityX,
float velocityY);
>

Основная идетя использования GestureDetector состоит в том что бы в OnTouchListener не обрабатывать типы событий, такие как ACTION_DOWN, ACTION_MOVE, ACTION_UP, а вместо этого мы отдаем событие в GestureDetector и он за нас обрабатывает эти события а мы получаем удобный API который позволяет понять какое действие выполнил пользователь на основании жестов.

public class MainActivity extends AppCompatActivity  
private GestureDetector mDetector;

@Override
protected void onCreate(Bundle savedInstanceState) super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

// this is the view we will add the gesture detector to
View myView = findViewById(R.id.my_view);

// get the gesture detector
mDetector = new GestureDetector(this, new MyGestureListener());

// Add a touch listener to the view
// The touch listener passes all its events on to the gesture detector
myView.setOnTouchListener(touchListener);
>

// This touch listener passes everything on to the gesture detector.
// That saves us the trouble of interpreting the raw touch events
// ourselves.
View.OnTouchListener touchListener = new View.OnTouchListener() @Override
public boolean onTouch(View v, MotionEvent event) // pass the events to the gesture detector
// a return value of true means the detector is handling it
// a return value of false means the detector didn't
// recognize the event
return mDetector.onTouchEvent(event);

>
>;

// In the SimpleOnGestureListener subclass you should override
// onDown and any other gesture that you want to detect.
class MyGestureListener extends GestureDetector.SimpleOnGestureListener
@Override
public boolean onDown(MotionEvent event) Log.d("TAG","onDown: ");

// don't return false here or else none of the other
// gestures will work
return true;
>

@Override
public boolean onSingleTapConfirmed(MotionEvent e) Log.i("TAG", "onSingleTapConfirmed: ");
return true;
>

@Override
public void onLongPress(MotionEvent e) Log.i("TAG", "onLongPress: ");
>

@Override
public boolean onDoubleTap(MotionEvent e) Log.i("TAG", "onDoubleTap: ");
return true;
>

@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2,
float distanceX, float distanceY) Log.i("TAG", "onScroll: ");
return true;
>

@Override
public boolean onFling(MotionEvent event1, MotionEvent event2,
float velocityX, float velocityY) Log.d("TAG", "onFling: ");
return true;
>
>
>

Так же есть ScaleGestureDetector который позволяет определить жесты для маштабирования:

public interface OnScaleGestureListener public boolean onScale(@NonNull ScaleGestureDetector detector);

public boolean onScaleBegin(@NonNull ScaleGestureDetector detector);

public void onScaleEnd(@NonNull ScaleGestureDetector detector);
>

ViewTreeObserver:

Android тратит какое то время что бы рассчитать размер, разместить и отрисовать View . Если мы попытаемся узнать какое то значение нашего View то можем получить 0 или значение по умолчанию. Что бы получить события о готовности нашего View добавлии ViewTreeObserver .

ViewTreeObserver позволяет добавить слушателя на глобальные изменения в дереве View .

Например такие события как:

OnGlobalLayoutListener
OnWindowAttachListener
OnWindowFocusChangeListener

container.getViewTreeObserver()
.addOnWindowAttachListener(new ViewTreeObserver.OnWindowAttachListener() @Override
public void onWindowAttached()
>

@Override
public void onWindowDetached()
>
>);

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

Обратим внимание на метод получаения ViewTreeObserver

public ViewTreeObserver getViewTreeObserver() if (mAttachInfo != null) return mAttachInfo.mTreeObserver; 
>
if (mFloatingTreeObserver == null) mFloatingTreeObserver = new ViewTreeObserver(mContext);
>
return mFloatingTreeObserver;
>

В View для проверки приаттачена View к Window или нет проверяется не равен ли null mAttachInfo

AttachInfo mAttachInfo — это какой то набор информации предостовляемой View об Window .

Специальный ViewTreeObserver используется если мы не приатачили View к Window .

SurfaceView:

SurfaceView это View у которой есть свой собственный Surface .

Для SurfaceView WindowManager создается новое Window и Surface .

Основной плюс SurfaceView в том что мы можем выполнять тяжелые графические расчеты в отдельном потоке.

RenderThread:

Мы знаем то что работа с UI в android происходи на main потоке. Но в Lollipop добавлен еще один поток RenderThread .

RenderThread отвечает за преобразование DisplayList в команды OpenGL и отсылку их в графический процессор.

DisplayList это последовательность графических команд, которые определяют выходное изображение, преобразовываемые позже в команды OpenGL , которые понимает графический процессор.

GPU не знает, что такое анимация, он может понимать только основные команды, например:

translation(x,y,z)
rotate(x,y)


// or basic drawing utilities:
drawCircle(centerX, centerY, radius, paint)
drawRoundRect(left, top, right, bottom, cornerRadiusX, cornerRadiusY, paint)

У нас могут быть большие значения из за сложности View которые создают большое количетсво DisplayList .

Вместе эти команды образуют сложную анимацию, которую мы видим на экране.

Rendering выполняется в два этапа:

View.draw() — Выполнение на UI Thread.

DrawFrame -> Выполнение в RenderThread , который выполняет работу на основе View.draw() .

Поток RenderThread просто выполняет рендеринг onDraw() , UI Thread выполняет onMeasure() , onLayout() и т. д.

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

Пока RenderThread занимается отрисовкой, UI Thread может подготовить данные для следующего Frame .

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *