您的位置:首页 > 移动开发 > Android开发

Android 性能优化—开机动画优化

2017-01-05 18:18 1381 查看
Android 手机的分辨率越来越大,从矮穷锉到百富美都是如此.现在矮穷锉也是HD的屏, 而百富美一般是2K屏,
8核CPU, 3GRAM.然并卵,我们依然会发现大多数的开机动画并不是很流畅,而比较流畅的,大都要么画面简单有的只是showlogo, 要么降低动画的分辨率/FPS.比如本来是百富美的HW(2K屏),却用矮穷锉的开机动画(HD/10fps开机动画).当然,这里有androiddesign 的问题,也有手机厂商的设置animation内容的问题.本文将提供如何在矮穷锉HW上实现真正百富美HW才有的功能(FHD,或者2K
30FPS).


1.1 Bootanimation架构

在android 中,开机动画并不仅仅是开机动画,而还包含开机铃声.开机动画都压缩在bootanimation.zip中,而开机铃声, 在L以及以前的版本中, 可以指定某一目录下的audio 文件, 也可以把开机铃声放到bootanimation.zip中, 而M 就只能放到bootanimation.zip中除非修改android的code.本文只讨论动画.事实上所谓的开机动画, 其实就是几十张或上百张的PNG或者JPG 图片按照一定的顺序,
FPS播放出来. 所有的开机动画都压缩在bootanimation.zip中,其位置是/system/media/bootanimation.zip. 现在我们打开bootanimation.zip来看看究竟有些什么内容.

+bootanimation.zip

| | +part0

| |480_854_00000.png

| |480_854_00001.png

| |480_854_00002.png

| |480_854_00003.png

| |480_854_00004.png

| |480_854_00005.png

|+------------------

| +part1

| | 480_854_00000.png

| |480_854_00001.png

| |480_854_00002.png

| |480_854_00003.png

| |480_854_00004.png

| |480_854_00005.png

……

|+------------------

| +partn

| | 480_854_00000.png

| |480_854_00001.png

| |480_854_00002.png

| |480_854_00003.png

| |480_854_00004.png

| |480_854_00005.png

|+------------------

| | audio.wav

|+------------------

| desc.txt

+--------------------

|+------------------

| audio_conf.txt

+--------------------

这里我们可以看到在bootanimation.zip中由part1,part2,part3 … partn, audio.wav

, desc.txt和audio_conf.txt 构成.其中audio.wav和audio_conf.txt是和开机动画的audio相关, 我们在本文略去不表.而part和desc.txt与开机动画有关.每一个part 又由一系列的png或jpg格式的图片按照数字或字母的大小顺序排列而成.
desc.txt则是描述怎样处理这些图片.


desc.txt

#typicaldesc.txt

480854 10

p1 0 part0

p0 0 part1

这里是典型的矮穷锉的配置.但是百富美的架构也和这个相同, 只是内容不同而已. 

“480” 表示这些图片的宽度是480.
“854” 表示这些图片的高度是854.
“10” 表示开机动画的目标fps 是10. 

“p” 这个flag表示某一个part的图片的处理方式,bootanimation 程序在处理某part的图片时会参考这个标志.P表示bootanimation可以随时退出当处理完一张图片后,如果bootanimation可以退出.
”C” 则表示必须把这部分的图片处理完毕才可以退出.事实上这个标识只要不为C, 则其行为完全一致.

“1”这个flag表示这part的图片需要循环处理多少次.
1表示处理循环处理一次.”0” 表示无限循环.

“0” 这个flag 表示播放完这部分所有图片后需要pause多久时间.”0” 表示不需要pause.
“part0”表示动画的第一部分内容.
“part1” 表示动画的第二部分内容.

“partn” 表示动画的第n+1部分内容.

这里我们讨论bootanimation程序的退出时机,正如前面讨论的,bootanimation程序会根据part 描述的第一个标识来处理怎样退出bootanimation.我们先假定此时bootanimation满足退出条件(propservice.bootanim.exit 为1)
bootanimation 在三种条件下可以退出.

开始处理part的新循环或新part. 如果这部分part的处理标识不是”c”则可以退出.否则不能退出.

开始处理新的一帧, 如果这部分part的处理标识不是”c”则可以退出.否则不能退出.

处理完一次循环后, 如果这部分part的处理标识是”c”,并且这部分是无限循环,则可以退出.如果不是无限循环则不能退出, 需要完全处理完这部分的图片才可能退出.

总之如果我们设置part的处理标识设置为”c”,则需要好好考虑和组织我们animation的图片.一步小心可能会把百富美的HW的性能搞成矮穷锉HW的性能.(开机时间过长).不建议设置此标识位为”C”.

frameworks\base\cmds\bootanimation\bootAnimation.cpp

bool BootAnimation::movie()

{

….

elseif (sscanf(l, " %c %d %d %s #%6s", &pathType, &count,&pause, path, color) >= 4)

Animation::Part part;

part.playUntilComplete = pathType == 'c';

}

….

for(int r=0 ; !part.count || r<part.count ; r++) {

// Exitany non playuntil complete parts immediately

if(exitPending() &&!part.playUntilComplete) //check exit condition 1

break;

….

for(size_t j=0 ; j<fcount && (!exitPending() || part.playUntilComplete) ; j++) //check exit condition 2

{

……

}

// For infinite parts, we've now played them at least once,so perhaps exit

if(exitPending()&& !part.count) //check exit condition 3

break;

}

我们知道boot animation在开机中占有重要地位,它也会影响开机时间.下面我们来讨论开机动画的工作流程.


1.2 开机动画的流程

在开机动画的生命周期中,至少有5个不同的进程参与其中.他们分别是:init
,surfaceflinger, system server,bootanimation 和Lancher.



Kernel将会启动init 进程当kernel启动完成后.

Init进程将会启动那些定义在init.rc 中的进程.如zygote,surfaceflinger等.

Sufacefinger进程会启动bootanimation 进程当surfaceflinger 进程启动时.(SurfaceFlinger::startBootAnim()).

Bootanimation进程处理开机动画.同时zygote 进程启动systemserver 进程. AMS ,WMS 将会在systemserver 进程里启动,进而启动launcher 进程.

Launcher进程的UI线程将会call AMS的activityIdle() 当Launcher 进程完成了启动,并且没有其它event 需要处理.注意这是Android
framwork的功能.每一个app启动完成后,在其主线程有空闲的时候(没有需要处理的event)都会call
AMS的activityIdle().这个idle handler 是在handleResumeActivity中注册的.所以当app 启动完成后都会call
AMS的activityIdle(). 在AMS的activityIdle()中会调用WindowManagerService. performEnableScreen() call.

frameworks\base\services\core\java\com\android\server\am\ActivityThread.java

final voidhandleResumeActivity(IBinder token,

boolean clearHide, booleanisForward, boolean reallyResume)

{

….

f(!r.onlyLocalRequest) {

r.nextIdle = mNewActivities;

mNewActivities = r;

if (localLOGV) Slog.v(

TAG, "Scheduling idlehandler for " + r);

Looper.myQueue().addIdleHandler(new Idler());

}

}

Idler 定义.

private classIdler implements MessageQueue.IdleHandler {

@Override

public final boolean queueIdle() {

ActivityClientRecord a =mNewActivities;

boolean stopProfiling = false;

if (mBoundApplication != null&& mProfiler.profileFd != null

&&mProfiler.autoStopProfiler) {

stopProfiling = true;

}

if (a != null) {

mNewActivities = null;

IActivityManager am =ActivityManagerNative.getDefault();

ActivityClientRecord prev;

do {

if (localLOGV) Slog.v(

TAG, "Reportingidle of " + a +

" finished=" +

(a.activity != null&& a.activity.mFinished));

if (a.activity != null&& !a.activity.mFinished) {

try {

am.activityIdle(a.token,a.createdConfig, stopProfiling); //这里会callAMS. activityIdle by binder

a.createdConfig =null;

} catch(RemoteException ex) {

// Ignore

}

}

prev = a;

a = a.nextIdle;

prev.nextIdle = null;

} while (a != null);

}

if (stopProfiling) {

mProfiler.stopProfiling();

}

ensureJitEnabled();

return false;

}

}

WindowManagerService.performEnableScreen()将通过发送IBinder.FIRST_CALL_TRANSACTION.从而callsurfaceFlinger. bootFinished().如果比较顺利的话.注意这里也是很容易由白富美变为矮穷锉如果Keyguard和Wallpaper处理得不好的话.开机动画和开机时间会边长.

frameworks\base\services\core\java\com\android\server\wm\WindowManagerService.java

public voidperformEnableScreen() {

synchronized(mWindowMap) {

……

// Don't enable the screen untilall existing windows have been drawn.

//这里会check Keyguard和Wallpaper.

if (!mForceDisplayEnabled &&checkWaitingForWindowsLocked()) {

return;

}

if (!mBootAnimationStopped) {

// Do this one time.

try {

IBinder surfaceFlinger =ServiceManager.getService("SurfaceFlinger");

if (surfaceFlinger != null){

//Slog.i(TAG,"******* TELLING SURFACE FLINGER WE ARE BOOTED!");

Parcel data = Parcel.obtain();

data.writeInterfaceToken("android.ui.ISurfaceComposer");

surfaceFlinger.transact(IBinder.FIRST_CALL_TRANSACTION,// BOOT_FINISHED

data, null, 0);

data.recycle();

}

} catch (RemoteException ex) {

Slog.e(TAG, "Bootcompleted: SurfaceFlinger is dead!");

}

mBootAnimationStopped = true;

}

….

}

private booleancheckWaitingForWindowsLocked(){

…..

final WindowList windows =getDefaultWindowListLocked();

final int N = windows.size();

for (int i=0; i<N; i++) {

WindowState w = windows.get(i);

if (w.isVisibleLw() &&!w.mObscured && !w.isDrawnLw()) {

return true;

}

if (w.isDrawnLw()) {

if (w.mAttrs.type ==TYPE_BOOT_PROGRESS) {

haveBootMsg = true;

} else if (w.mAttrs.type ==TYPE_APPLICATION) {

haveApp = true;

} else if (w.mAttrs.type ==TYPE_WALLPAPER) {

haveWallpaper = true;

} else if (w.mAttrs.type ==TYPE_STATUS_BAR) {

haveKeyguard =mPolicy.isKeyguardDrawnLw();

}

}

}

// If we are turning on the screen toshow the boot message,

// don't do it until the boot messageis actually displayed.

if (!mSystemBooted &&!haveBootMsg) {

return true;

}

….

// If we are turning on the screenafter the boot is completed

// normally, don't do so until we havethe application and

// wallpaper.

if (mSystemBooted && ((!haveApp&& !haveKeyguard) ||

(wallpaperEnabled &&!haveWallpaper))) {

return true;

}

return false;

}

如前所述Bootanimation 会在关键节点去check 是否需要exit. 当且仅当属性“service.bootanim.exit“ 为1 时,
bootanimation 会退出.


1.3 矮穷锉逆袭百富美

前面我们分析了开机动画的组织架构以及工作流程, 现在就到了最重要的时刻了.我们来看看矮穷锉如何逆袭百富美.那么哪些是矮穷锉的行为, 典型的行为有如下两点:

Kernel logo 的时间太长 (10s以上), 而开机动画的时间太短.

如果出现这种现象,给人的第一印象便是这台手机的CPU太TM 矮穷锉了. 我们来看看问题到底在哪.我们知道android 原来的开机动画design 为只有开机动画而没有开机铃声或者开机铃声不在bootanimation里处理. 所以其bootanimation 进程的user 定义为Graphic
,随着消费者要求的提高就出现了需要在bootanimation进程中处理audio,这样就需要修改bootanimation进程的用户为media. 因为auido
service 只能media 或root 用户能访问.

那么问题到底出现在哪里?我们来看看bootanimation 进程的定义. 大多数手机的定义如下:

service bootanim/system/bin/bootanimation

class core

user media

group graphics audio

disabled

oneshot

其user 是media.我们接着看其Bootanimation构造函数的定义.

Bootanimation会new SurfaceComposerClient().

frameworks\base\cmds\bootanimation\bootAnimation.cpp

BootAnimation::BootAnimation(): Thread(false), mZip(NULL)

{

mSession = new SurfaceComposerClient();

}

b.SurfaceComposerClient的定义如下:

frameworks\native\libs\gui\SurfaceComposerClient.cpp

SurfaceComposerClient::SurfaceComposerClient()

: mStatus(NO_INIT),mComposer(Composer::getInstance())

{

}

c.在onFirstRef()会去连接surfacefinger.

voidSurfaceComposerClient::onFirstRef() {

sp<ISurfaceComposer>sm(ComposerService::getComposerService());

if (sm != 0) {

sp<ISurfaceComposerClient> conn =sm->createConnection();

if (conn != 0) {

mClient = conn;

mStatus = NO_ERROR;

}

}

}

d.连接surfaceflinger时,会check permission.现在**部分来了

frameworks\native\services\surfaceflinger\ SurfaceFlinger.cpp

status_tSurfaceFlinger::onTransact(

uint32_t code, constParcel& data, Parcel* reply, uint32_t flags)

{

switch (code) {

case CREATE_CONNECTION:

case CREATE_DISPLAY:

case SET_TRANSACTION_STATE:

case BOOT_FINISHED:

case CLEAR_ANIMATION_FRAME_STATS:

case GET_ANIMATION_FRAME_STATS:

case SET_POWER_MODE:

{

// codes that require permissioncheck

IPCThreadState* ipc =IPCThreadState::self();

const int pid =ipc->getCallingPid();

const int uid = ipc->getCallingUid();

if ((uid != AID_GRAPHICS && uid != AID_SYSTEM) &&

!PermissionCache::checkPermission(sAccessSurfaceFlinger, pid, uid)){

ALOGE("Permission Denial:"

"can't accessSurfaceFlinger pid=%d, uid=%d", pid, uid);

return PERMISSION_DENIED;

}

break;

…..

}

}

e.我们从上面的代码中看到在surfaceflinger的很多操作例如连接,创建display和设置power 等都需要检查permission。除非进程的user 是graphics 和system。然而几乎在android的所有版本中,media 用户都是有ACCESS_SURFACE_FLINGER 的permission的。其定义在平台的permission文件中。

frameworks\base\data\etc\platform.xml

<assign-permissionname="android.permission.ACCESS_SURFACE_FLINGER"uid="media" />

<assign-permissionname="android.permission.ACCESS_SURFACE_FLINGER"uid="graphics" />

另一方面,permission的check是在javaservice permission service。当bootanimation开始运行的时候permission 还没有看是运行。Permisison
service运行起来还需一段时间(8-9s)。因此开机动画在这里被阻塞了。其结果就是百富美变成了矮穷锉。如果我们要从矮穷锉完美的逆袭为百富美,只需做如下改动即可。我们为什么我们不把bootanimation 改为root 用户,我想你懂的。

- if ((uid !=AID_GRAPHICS && uid != AID_SYSTEM) &&

!PermissionCache::checkPermission(sAccessSurfaceFlinger, pid, uid))

+ if ((uid != AID_GRAPHICS && uid != AID_SYSTEM&&uid != AID_MEDIA) &&

!PermissionCache::checkPermission(sAccessSurfaceFlinger,pid, uid))

开机动画fps 太低,不流程。

我们知道很多开机动画要么是手机厂商logo或者手机的宣传片,要么是运行商的宣传片。可以说是手机的脸面,在现如今的刷脸时代,没有一个好的面孔是很难找到高大帅的。那么是什么原因导致矮穷锉满天下。我们还是看看code吧。

frameworks\base\cmds\bootanimation\bootAnimation.cpp

boolBootAnimation::movie()

{

….

for (size_t i=0 ; i<pcount ; i++) {



for (int r=0 ; !part.count ||r<part.count ; r++) {

…..

for (size_t j=0 ; j<fcount&& (!exitPending() || part.playUntilComplete) ; j++) {

….

initTexture(frame);

}

return false;

}

status_t BootAnimation::initTexture(Texture* texture, AssetManager&assets,

const char* name) {

SkBitmap bitmap;

SkImageDecoder::DecodeMemory(asset->getBuffer(false),asset->getLength(),

&bitmap, kUnknown_SkColorType,SkImageDecoder::kDecodePixels_Mode);

asset->close();

delete asset;

….

glTexParameteriv(GL_TEXTURE_2D,GL_TEXTURE_CROP_RECT_OES, crop);

glTexParameterx(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER, GL_NEAREST);

glTexParameterx(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER, GL_NEAREST);

glTexParameterx(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S, GL_REPEAT);

glTexParameterx(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T, GL_REPEAT);

return NO_ERROR;

}

看出点什么名堂出来没。好像没有什么问题吧?是的,功能上是没有什么问题。那么从性能上看是否有问题的?对的,这里用的是SW decoder SkImageDecoder::DecodeMemory(),而且是单线程那么其解码效率如何?如果你巧合用了百富美平台,又巧合的对SW decoder进行了优化如neon指令做了优化,那么恭喜你。你已经没有此问题的烦恼。对此问题如何解决呢?聪明的你一定想到两种方案。一种是比较low的方案,继续往矮穷锉的方向发展,那就是降低开机动画的分辨率来提高解码速度从而提高开机动画的fps。另一种就是向百富美看其,用多线程来进行解码,把解码好的数据放到一个buffer
queue中,需要用到解码的frame时直接从bufferqueue里拿而不是解一个frame,画一个frame。充分利用多核的计算能力。

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: