视频壁纸属于动态壁纸,所以视频壁纸就可以用Android系统提供的动态壁纸服务来实现。首先先介绍一下在实现过程中会用到的几个类。
WallpaperManager
Android提供的用于管理壁纸的类,里面有几个方法在实现过程中需要用到
getInstance(context) 获取一个实例
clear() 清空所有的壁纸
getWallpaperInfo() 获取当前壁纸的信息
WallpaperService
Android系统提供的壁纸服务的抽象类,实现一个动态壁纸必须要继承该类并实现onCreateEngine()方法,该类的唯一功能就是返回绘制动态壁纸所需要的Engine实例。
WallpaperService.Engine
Engine是WallpaperService一个内部类,动态壁纸的所有显示过程的绘制都由该类完成,我们需要继承该类并实现以下三个方法:
onSurfaceCreated()
onSurfaceDestroyed()
onVisibilityChanged()
当然还有其他的如onCreate()和onDestroy()方法等等。
视频壁纸的实现过程
首先我们需要自定义一个类VideoWallPaperService来继承WallpaperService类并实现onCreateEngine()方法,在该方法中需要返回一个Engine实例。
public class VideoWallPaperService extends WallpaperService {
@Override
public Engine onCreateEngine() {
return new VideoEngine();
}
}
所以我们需要再VideoWallPaperService内部自定义一个VideoEngine类来继承Engine类并实现onSurfaceCreated() 、onSurfaceDestroyed()和onVisibilityChanged()方法。然后在onSurfaceCreated()方法中初始化一个MediaPlayer;在onSurfaceDestroyed()中去释放和销毁MediaPlayer;在onVisibilityChanged()方法中去切换MediaPlayer的播放和暂停。
private class VideoEngine extends Engine {
private MediaPlayer mPlayer;
@Override
public void onVisibilityChanged(boolean visible) {
if(visible) {
mPlayer.start();
} else {
mPlayer.pause();
}
}
@Override
public void onSurfaceCreated(SurfaceHolder holder) {
super.onSurfaceCreated(holder);
mPlayer = new MediaPlayer();
//不能使用setDisplay()方法
mPlayer.setSurface(holder.getSurface());
mPlayer.setDataSource(videoPath);
mPlayer.prepare();
mPlayer.start();
}
@Override
public void onSurfaceDestroyed(SurfaceHolder holder) {
super.onSurfaceDestroyed(holder);
if(mPlayer.isPlaying()) {
mPlayer.stop();
}
mPlayer.release();
mPlayer = null;
}
}
以上都完成后我们还需要对VideoWallPaperService进行注册,这里在注册时写法和普通的Service相同,其中VideoWallPaperService需要一个"android.permission.BIND_WALLPAPER"的权限,需要添加一个action为"android.service.wallpaper.WallpaperService"(固定写法),还需要一个名为"android.service.wallpaper"(固定写法)的meta-data。
android:name=".VideoWallPaperService"
android:permission="android.permission.BIND_WALLPAPER">
android:name="android.service.wallpaper"
android:resource="@xml/wallpaper" />
上面一步的代码中meta-data中需要一个xml文件源,所以我们需要再res的xml文件夹(没有就自己建)下创建一个名为wallpaper的xml文件。xml文件的根标签为wallpaper,有一下三个属性:
description 动态壁纸的文字描述
thumbnail 动态壁纸的图片描述
settingsActivity 动态壁纸的设置界面(会在预览界面出现)
android:description="@string/wallpaper_description"
android:settingsActivity="com.example.videowallpaper.SettingsActivity"
android:thumbnail="@mipmap/ic_launcher">
接下来就是启动壁纸服务了,这里我们不能通过context的startService()方法来启动壁纸服务,我们需要通过启动系统的预览界面来间接启动服务。
Intent intent = new Intent(WallpaperManager.ACTION_CHANGE_LIVE_WALLPAPER);
intent.putExtra(WallpaperManager.EXTRA_LIVE_WALLPAPER_COMPONENT, new ComponentName(context, VideoWallPaperService.class));
context.startActivity(intent);
除了启动还需要关闭壁纸服务,我们可以通过WallpaperManager的clear()方法来关闭,也可以通过WallpaperService的clearWallpaper()(已经被标记过时)方法来关闭壁纸。
try {
WallpaperManager.getInstance(context).clear();
} catch(IOException e) {
e.printStackTrace();
}
其他需要注意的地方
设置壁纸需要"android.permission.SET_WALLPAPER"权限
播放本地视频需要"android.permission.READ_EXTERNAL_STORAGE"权限
VideoEngine的MediaPlayer的播放地址要使用持久化保存数据(数据库、Preference等),否则设置好视频壁纸后将手机关机再开机,就会出bug
WallpaperService的onCreateEngine()方法返回的Engine实例不能使用单例模式,必须每次都返回一个新的Engine实例
可以通过WallpaperManager的getWallpaperInfo()方法来判断当前自己的服务是否已经在运行,从而可以通过广播或者其他通知来直接修改壁纸播放的视频,从而避免每次更换一个视频都需要走一次系统的预览界面。
最后贴一下VideoWallPaperService的完整代码(仅供参考):
package com.example.videowallpaper;
import android.app.WallpaperInfo;
import android.app.WallpaperManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.media.MediaPlayer;
import android.preference.PreferenceManager;
import android.service.wallpaper.WallpaperService;
import android.text.TextUtils;
import android.view.SurfaceHolder;
import java.io.IOException;
public class VideoWallPaperService extends WallpaperService {
private static final String SERVICE_NAME = "com.example.videowallpaper.VideoWallPaperService";
@Override
public Engine onCreateEngine() {
return new VideoEngine();
}
public static void startWallPaper(Context context, String videoPath) {
WallpaperInfo info = WallpaperManager.getInstance(context).getWallpaperInfo();
if(info != null && SERVICE_NAME.equals(info.getServiceName())) {
changeVideo(context, videoPath);
} else {
startNewWallpaper(context, videoPath);
}
}
public static void closeWallpaper(Context context) {
try {
WallpaperManager.getInstance(context).clear();
} catch(IOException e) {
e.printStackTrace();
}
}
private static void startNewWallpaper(Context context, String path) {
saveVideoPath(context, path);
Intent intent = new Intent(WallpaperManager.ACTION_CHANGE_LIVE_WALLPAPER);
intent.putExtra(WallpaperManager.EXTRA_LIVE_WALLPAPER_COMPONENT, new ComponentName(context, VideoWallPaperService.class));
context.startActivity(intent);
}
private static void changeVideo(Context context, String path) {
saveVideoPath(context, path);
Intent intent = new Intent();
intent.setAction(Constant.ACTION);
intent.putExtra(Constant.BROADCAST_SET_VIDEO_PARAM, Constant.ACTION_SET_VIDEO);
context.sendBroadcast(intent);
}
public static void setVolume(Context context, boolean hasVolume) {
Intent intent = new Intent();
intent.setAction(Constant.ACTION);
if(hasVolume) {
intent.putExtra(Constant.BROADCAST_SET_VIDEO_PARAM, Constant.ACTION_VOICE_NORMAL);
} else {
intent.putExtra(Constant.BROADCAST_SET_VIDEO_PARAM, Constant.ACTION_VOICE_SILENCE);
}
context.sendBroadcast(intent);
}
private static void saveVideoPath(Context context, String path) {
SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(context).edit();
editor.putString(Constant.VIDEO_PATH, path);
editor.apply();
}
private String getVideoPath() {
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
return preferences.getString(Constant.VIDEO_PATH, null);
}
private class VideoEngine extends Engine implements MediaPlayer.OnPreparedListener,
MediaPlayer.OnCompletionListener, MediaPlayer.OnErrorListener {
private MediaPlayer mPlayer;
private boolean mLoop;
private boolean mVolume;
private boolean isPapered = false;
private VideoEngine() {
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(VideoWallPaperService.this);
mLoop = preferences.getBoolean("loop", true);
mVolume = preferences.getBoolean("volume", false);
}
private BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
int action = intent.getIntExtra(Constant.BROADCAST_SET_VIDEO_PARAM, -1);
switch(action) {
case Constant.ACTION_SET_VIDEO: {
setVideo(getVideoPath());
break;
}
case Constant.ACTION_VOICE_NORMAL: {
mVolume = true;
setVolume();
break;
}
case Constant.ACTION_VOICE_SILENCE: {
mVolume = false;
setVolume();
break;
}
}
}
};
@Override
public void onCreate(SurfaceHolder surfaceHolder) {
super.onCreate(surfaceHolder);
IntentFilter filter = new IntentFilter();
filter.addAction(Constant.ACTION);
registerReceiver(mReceiver, filter);
}
@Override
public void onDestroy() {
super.onDestroy();
unregisterReceiver(mReceiver);
}
@Override
public void onVisibilityChanged(boolean visible) {
if(isPapered) {
if(visible) {
mPlayer.start();
} else {
mPlayer.pause();
}
}
}
@Override
public void onSurfaceCreated(SurfaceHolder holder) {
super.onSurfaceCreated(holder);
mPlayer = new MediaPlayer();
setVideo(getVideoPath());
}
@Override
public void onSurfaceDestroyed(SurfaceHolder holder) {
super.onSurfaceDestroyed(holder);
if(mPlayer.isPlaying()) {
mPlayer.stop();
}
mPlayer.release();
mPlayer = null;
}
@Override
public void onPrepared(MediaPlayer mp) {
isPapered = true;
mp.start();
}
@Override
public void onCompletion(MediaPlayer mp) {
closeWallpaper(getApplicationContext());
}
@Override
public boolean onError(MediaPlayer mp, int what, int extra) {
closeWallpaper(getApplicationContext());
return true;
}
private void setVideo(String videoPath) {
if(TextUtils.isEmpty(videoPath)) {
closeWallpaper(getApplicationContext());
throw new IllegalArgumentException("video path is null");
}
if(mPlayer != null) {
mPlayer.reset();
isPapered = false;
try {
mPlayer.setOnPreparedListener(this);
mPlayer.setOnCompletionListener(this);
mPlayer.setOnErrorListener(this);
mPlayer.setLooping(mLoop);
// mPlayer.setDisplay(getSurfaceHolder());
mPlayer.setSurface(getSurfaceHolder().getSurface());
setVolume();
mPlayer.setDataSource(videoPath);
mPlayer.prepareAsync();
} catch(IOException e) {
e.printStackTrace();
}
}
}
private void setVolume() {
if(mPlayer != null) {
if(mVolume) {
mPlayer.setVolume(1.0f, 1.0f);
} else {
mPlayer.setVolume(0f, 0f);
}
}
}
}
}
还有一个SettingsActivity的代码也贴出来吧
这里说明一下推荐使用PreferenceFragment来代替PreferenceActivity
package com.example.videowallpaper;
import android.os.Bundle;
import android.preference.PreferenceActivity;
public class SettingsActivity extends PreferenceActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.setting_layout);
}
}
setting_layout
android:key="video_param"
android:title="设置">
android:defaultValue="true"
android:key="loop"
android:title="是否循环播放" />
android:checked="false"
android:key="volume"
android:title="是否开启声音" />
Constant.java(自定义的一些常量)
public class Constant {
public static final String BROADCAST_SET_VIDEO_PARAM = "broadcast_set_video_param";
public static final String ACTION = "action";
public static final String VIDEO_PATH = "action_video_path";
public static final int ACTION_SET_VIDEO = 0x101;
public static final int ACTION_VOICE_SILENCE = 0x102;
public static final int ACTION_VOICE_NORMAL = 0x103;
}