前言:
最近遇到了几个很恶心的问题,第一个是手机录制视频发现资源不够用,第二个是手机播放视频发现卡顿。找了半天两个问题的root cause都是手机动态壁纸。我们手机动态壁纸的实现是无论当前壁纸是否处于前台,video instance都是一直活跃的。不得不说,这个设计真的很过分,一是浪费资源,二是会引起很多video的问题。所以今天就了解下动态壁纸的具体实现过程,看看是否可以优化这个设计……
Android壁纸的实现和管理概述:
Android壁纸的实现和管理分为三层:
- WallpaperService与Engine
壁纸运行在一个Android服务之中,这个服务的名字叫做WallpaperService。当用户选择了一个壁纸之后,此壁纸所对应的WallpaperService便会启动并开始进行壁纸的绘制工作。Engine是WallpaperService中的一个内部类,实现了壁纸窗口的创建以及Surface的维护工作。这一层次的内容主要体现了壁纸的实现原理。
-
WallpaperManagerService
这个系统服务用于管理壁纸的运行与切换,并通过WallpaperManager类向外界提供操作壁纸的接口。这一层次主要体现了Android对壁纸的管理方式。
-
WindowManagerService
用于计算壁纸窗口的Z序、可见性以及为壁纸应用窗口动画。这一层次主要体现了Android对壁纸窗口的管理方式。
上层调用component_deinit的过程:
MediaCodec: stop or release
ACodec::initiateShutdown 在此函数中会判断keepComponentAllocated是否为false,如果是false就发送kWhatReleaseCodecInstance
status_t err = mCodec->mOMXNode->freeNode();
Omx::freeNode // 此处调用mMaster->destroyComponentInstance
QComOMXPlugin::destroyComponentInstance // (*mFreeHandle)(reinterpret_cast<OMX_HANDLETYPE *>(component));
// mFreeHandle = (FreeHandleFunc)dlsym(mLibHandle, "OMX_FreeHandle");
OMX_FreeHandle // qc_omx_component_deinit
qc_omx_component_deinit // eRet = pThis->component_deinit(hComp);
Android动态壁纸的具体实现:
安卓壁纸的实现都是在WallpaperService中完成的,动态壁纸只是对壁纸功能的扩展,所以动态壁纸的实现首先会创建一个服务并集成WallpaperService。WallpaperService中有一个内部类Engine,Engine实现了实现了壁纸窗口的创建以及Surface的维护工作。就是说我们可以获取到一个SurfaceHolder,拿到这个东西就好办了,我们可以在上面画自己想要的东西或者把视频输出到上面去。下面直接看代码:
// packages/apps/ThemeManager/src/com/android/thememanager/service/VideoWallpaperService.java
public class VideoWallpaperService extends WallpaperService {
private static final String TAG = "VideoWallpaper";
public Engine onCreateEngine() { // 创建VideoEngine,VideoWallpaperService实现的内容都在VideoEngine中
return new VideoEngine();
}
private class VideoEngine extends WallpaperService.Engine {
private MediaPlayer mediaPlayer = new MediaPlayer(); // 创建播放器MediaPlayer
private String path;
private boolean isAudioOn;
private AudioManager mAudioManager;
// 创建AudioFocusChangeListener
private AudioManager.OnAudioFocusChangeListener mOnAudioFocusChangeListener = new AudioManager.OnAudioFocusChangeListener() {
@Override
public void onAudioFocusChange(int focusChange) {
// 略
}
};
private BroadcastReceiver mUpdateVideoReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
// 略
initEngine(); // 初始化Engine
}
}
};
@Override
public void onCreate(SurfaceHolder surfaceHolder) {
// 略
}
@Override
public void onDestroy() {
super.onDestroy();
mediaPlayer.release(); // 释放mediaPlayer
unregisterReceiver(mUpdateVideoReceiver);
}
@Override
public void onSurfaceCreated(SurfaceHolder holder) {
super.onSurfaceCreated(holder);
initEngine();
}
@Override
public void onVisibilityChanged(boolean visible) { // 当壁纸有前后台的切换
super.onVisibilityChanged(visible);
try {
if (visible) {
mediaPlayer.start(); // 如果切换为前台,开始播放
} else {
mediaPlayer.pause(); // 如果切换为后台,暂停播放
// 调用pause只会pause source和render不会释放decoder.
}
} catch (IllegalStateException e) {
Log.e(TAG, "onVisibilityChanged video exception ", e);
}
}
private void initEngine() { // 在此函数中实现了对视频文件的处理,及对player的操作
Log.d(TAG, "initEngine");
File videoFile = null;
InputStream inputStream = null;
FileOutputStream outputStream = null;
try {
Context context = VideoWallpaperService.this;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
context = context.createDeviceProtectedStorageContext();
}
File filesDir = context.getFilesDir();
String videoPath = filesDir.getAbsolutePath() + "/video";
File videoFileDir = new File(videoPath);
final String fileName = FileUtils.getFileName(path);
videoFile = new File(videoFileDir, fileName);
if (!videoFile.exists()) {
// Clean all video
FileUtils.rm(videoPath);
videoFileDir.mkdir();
// Write current video
inputStream = new FileInputStream(new File(path));
outputStream = new FileOutputStream(videoFile);
// 16k buffer
byte buffer[] = new byte[16384];
int length = 0;
while ((length = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, length);
}
}
} catch (Exception e) {
Log.e(TAG, "load video file error : ", e);
} finally {
Utils.closeQuietly(outputStream);
Utils.closeQuietly(inputStream);
}
try {
if (videoFile != null && videoFile.exists()) {
mediaPlayer.reset();
Surface surface = getSurfaceHolder().getSurface();
mediaPlayer.setSurface(surface);
mediaPlayer.setDataSource(new FileInputStream(videoFile).getFD());
setAudioState(isAudioOn);
mediaPlayer.setLooping(true);
mediaPlayer.prepare();
mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
@Override
public void onPrepared(MediaPlayer mp) {
mp.start();
}
});
mediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {
@Override
public boolean onError(MediaPlayer mp, int what, int extra) {
mp.reset();
return false;
}
});
}
} catch (Exception e) {
Log.e(TAG, "configVideoEngine err ", e);
}
}
private void setAudioState(boolean isOn) { // 设置音频
try {
float volume = isOn ? 0.5f : 0;
mediaPlayer.setVolume(volume, volume);
} catch (IllegalStateException e) {
Log.e(TAG, "setAudioState err", e);
}
}
}