前言
BootAnimation就是安卓系统的开机动画,估计网上面对BootAnimation的源码解读已经一大堆了,但是我想借BootAnimation分析以及和应用的对比来让读者好好理解一个应用的本质。
bootanimation在哪里
bootanimation的源码在frameworks/base/cmd/bootanimation目录下,是c++写的,最后编译成一个可执行程序bootanimation是在手机system/bin目录下。
对照应用:
我们编译出来的是一个APK的压缩包,一般是dex加一些资源,放在我们手机system目录或者data目录。
bootanimation的启动
bootanimation会在android开机启动的时候执行init.rc然后执行以下指令,然后就会找到bootanimation的可执行程序并运行。
service bootanim /system/bin/bootanimation
class core animation
user graphics
group graphics audio
disabled
oneshot
writepid /dev/stune/top-app/tasks
然后就会运行bootanimation_main.cpp中main方法,这是应该大家在刚开始学c语言或者java语言的hello world的时候都清楚,暂时先不用代码细节,我们后面慢慢分析。
int main()
{
setpriority(PRIO_PROCESS, 0, ANDROID_PRIORITY_DISPLAY);
bool noBootAnimation = bootAnimationDisabled();
ALOGI_IF(noBootAnimation, "boot animation disabled");
if (!noBootAnimation) {
sp<ProcessState> proc(ProcessState::self());
ProcessState::self()->startThreadPool();
waitForSurfaceFlinger();
// create the boot animation object
sp<BootAnimation> boot = new BootAnimation(new AudioAnimationCallbacks());
ALOGV("Boot animation set up. Joining pool.");
IPCThreadState::self()->joinThreadPool();
}
ALOGV("Boot animation exit");
return 0;
}
对照应用:
其实一个应用的启动过程和上述有点类似,说白了就是Zygote进程加载应用的dex文件,然后执行ActivityThread.java的中main方法,有点长,有兴趣的可以仔细看看
public static void main(String[] args) {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
// CloseGuard defaults to true and can be quite spammy. We
// disable it here, but selectively enable it later (via
// StrictMode) on debug builds, but using DropBox, not logs.
CloseGuard.setEnabled(false);
Environment.initForCurrentUser();
// Set the reporter for event logging in libcore
EventLogger.setReporter(new EventLoggingReporter());
// Make sure TrustedCertificateStore looks in the right place for CA certificates
final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
TrustedCertificateStore.setDefaultUserDirectory(configDir);
Process.setArgV0("<pre-initialized>");
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, "ActivityThread"));
}
// End of event ActivityThreadMain.
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
bootanimation的main方法
虽然main方法的代码不多,但是值得思考的问题有很多,以下是我对这边所有代码注解
int main()
{
//设置进程的优先级
setpriority(PRIO_PROCESS, 0, ANDROID_PRIORITY_DISPLAY);
//判断是否disable BootAnimation
bool noBootAnimation = bootAnimationDisabled();
ALOGI_IF(noBootAnimation, "boot animation disabled");
if (!noBootAnimation) {
//初始化Binder服务
sp<ProcessState> proc(ProcessState::self());
//启动Binder线程池
ProcessState::self()->startThreadPool();
//等待SurfaceFlinger服务结束
waitForSurfaceFlinger();
//创建开机动画的对象,其实BootAnimation这个对象会另外运行一个线程
// create the boot animation object
sp<BootAnimation> boot = new BootAnimation(new AudioAnimationCallbacks());
ALOGV("Boot animation set up. Joining pool.");
//将当前线程加入Binder线程池避免退出。
IPCThreadState::self()->joinThreadPool();
}
ALOGV("Boot animation exit");
return 0;
}
细节1:
sp<ProcessState> proc(ProcessState::self());
ProcessState::self()->startThreadPool();
这两行代码,会初始化bootanimation的Binder服务,为什么要Binder服务,因为开机动画需要SurfaceFlinger的支持,SurfaceFlinger就是一个Binder服务。
对照应用:
应用除了需要SurfaceFlinger,还需要AMS,PMS,WMS等大量的Binder服务,所以也需要初始化Binder服务,但是我们发现ActivityThread.java的main方法中并没有Binder服务的初始化,其实应用的Binder服务的初始化在onZygoteInit的时候已经完成了。
virtual void onZygoteInit()
{
sp<ProcessState> proc = ProcessState::self();
ALOGV("App process: starting thread pool.\n");
proc->startThreadPool();
}
细节2:
大家会发现bootanimation的最后一步是IPCThreadState::self()->joinThreadPool(),这个的意思是将当前的线程也就是main方法所在的线程加入Binder的线程池,并block住。为什么要这样子做,这样子是保证当前进程不退出。
对照应用:
其实在ActivityThread.java的main方法中也有类似的操作就是Looper.loop(),也是为了避免主线程也就是UI线程退出。
小结:
其实开机动画也好,我们自己写的应用也好,本质上主线程就是一个永远没有返回或者结束的main方法。
bootanimation的界面绘制
先看如下代码,简单总结一下就是通过Binder调用从SurfaceFlinger获取了一块Surface,并将Surface和OpenGL绘制引擎进行绑定,因为开机动画是通过OpenGL绘制的。
status_t BootAnimation::readyToRun() {
mAssets.addDefaultAssets();
//获得屏幕
sp<IBinder> dtoken(SurfaceComposerClient::getBuiltInDisplay(
ISurfaceComposer::eDisplayIdMain));
//获得屏幕信息
DisplayInfo dinfo;
status_t status = SurfaceComposerClient::getDisplayInfo(dtoken, &dinfo);
if (status)
return -1;
//创建SurfaceControl
// create the native surface
sp<SurfaceControl> control = session()->createSurface(String8("BootAnimation"),
dinfo.w, dinfo.h, PIXEL_FORMAT_RGB_565);
//设置Layer
SurfaceComposerClient::Transaction t;
t.setLayer(control, 0x40000000)
.apply();
//获得对应的Surface
sp<Surface> s = control->getSurface();
//初始化opengl egl
// initialize opengl and egl
const EGLint attribs[] = {
EGL_RED_SIZE, 8,
EGL_GREEN_SIZE, 8,
EGL_BLUE_SIZE, 8,
EGL_DEPTH_SIZE, 0,
EGL_NONE
};
EGLint w, h;
EGLint numConfigs;
EGLConfig config;
EGLSurface surface;
EGLContext context;
//获得一块屏幕
EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
//0,0就是代表NULL,正常情况下这个两个是返回,最大,最小的版本
eglInitialize(display, 0, 0);
//选择我们自己需要的配置
eglChooseConfig(display, attribs, &config, 1, &numConfigs);
//创建一个Window,注意这个时间创建一个传入了一个EGLNativeWindowType,建立起OPENGL和Surface之间的关系
surface = eglCreateWindowSurface(display, config, s.get(), NULL);
//创建统一的上下文
context = eglCreateContext(display, config, NULL, NULL);
//获取surface的宽度
eglQuerySurface(display, surface, EGL_WIDTH, &w);
//获取surface的高度
eglQuerySurface(display, surface, EGL_HEIGHT, &h);
//将这个上下文和当前创建的屏幕和Surface绑定
if (eglMakeCurrent(display, surface, surface, context) == EGL_FALSE)
return NO_INIT;
......省略部分代码
return NO_ERROR;
}
对照应用:
一般应用都是基于Activity开发的,其实Activity就是帮你隐藏了太多像SurfaceFlinger申请Surface,并在Surface使用Canvas进行绘制。
申请Surface
/frameworks/base/core/java/android/view/ViewRootImpl.java
private int relayoutWindow(WindowManager.LayoutParams params, int viewVisibility,
boolean insetsPending) throws RemoteException {
...省略部分代码
//mSurface需要通过relayout方法,才能变成可用的Surface
int relayoutResult = mWindowSession.relayout(mWindow, mSeq, params,
(int) (mView.getMeasuredWidth() * appScale + 0.5f),
(int) (mView.getMeasuredHeight() * appScale + 0.5f), viewVisibility,
insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0, frameNumber,
mWinFrame, mPendingOverscanInsets, mPendingContentInsets, mPendingVisibleInsets,
mPendingStableInsets, mPendingOutsets, mPendingBackDropFrame, mPendingDisplayCutout,
mPendingMergedConfiguration, mSurface);
return relayoutResult;
}
通过Surface获得Canvas对象然后传递给view进行ondraw方法的绘制
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
boolean scalingRequired, Rect dirty, Rect surfaceInsets) {
// Draw with software renderer.
final Canvas canvas;
....省略代码
// 通过前面申请的Surface锁定以后,获得Canvas对象
canvas = mSurface.lockCanvas(dirty);
....省略代码
// 将Canvas对象传递给View进行draw的方法的回调,也就是当前的activity开始界面绘制
mView.draw(canvas);
....省略代码
return true;
}
总结
对BootAnimation和应用进行比较,我更多的希望读者可以透过现象看本质,其实开机动画和应用非常类似。只不过应用的启动需要Zygote进程,AMS,PMS,WMS的支持,分装了好多对Binder初始化,Surface的申请,绘制,而开机动画启动比较快,这个时候Zygote进程,AMS,PMS,WMS都还没有准备好,只能依靠SurfaceFlinger和Binder的最原始的接口来显示UI。
进一步思考
BootAnimation不支持触摸事件,应用支持触摸事件,我们能否让BootAnimation也支持触摸事件,应用的触摸事件的本质是什么 ?