視頻壁紙屬于動態(tài)壁紙,所以視頻壁紙就可以用Android系統(tǒng)提供的動態(tài)壁紙服務(wù)來實(shí)現(xiàn)卷仑。首先先介紹一下在實(shí)現(xiàn)過程中會用到的幾個類。
WallpaperManager
Android提供的用于管理壁紙的類茁瘦,里面有幾個方法在實(shí)現(xiàn)過程中需要用到
- getInstance(context) 獲取一個實(shí)例
- clear() 清空所有的壁紙
- getWallpaperInfo() 獲取當(dāng)前壁紙的信息
WallpaperService
Android系統(tǒng)提供的壁紙服務(wù)的抽象類,實(shí)現(xiàn)一個動態(tài)壁紙必須要繼承該類并實(shí)現(xiàn)onCreateEngine()方法坠韩,該類的唯一功能就是返回繪制動態(tài)壁紙所需要的Engine實(shí)例。
WallpaperService.Engine
Engine是WallpaperService一個內(nèi)部類炼列,動態(tài)壁紙的所有顯示過程的繪制都由該類完成只搁,我們需要繼承該類并實(shí)現(xiàn)以下三個方法:
- onSurfaceCreated()
- onSurfaceDestroyed()
- onVisibilityChanged()
當(dāng)然還有其他的如onCreate()和onDestroy()方法等等。
視頻壁紙的實(shí)現(xiàn)過程
- 首先我們需要自定義一個類VideoWallPaperService來繼承WallpaperService類并實(shí)現(xiàn)onCreateEngine()方法俭尖,在該方法中需要返回一個Engine實(shí)例氢惋。
public class VideoWallPaperService extends WallpaperService {
@Override
public Engine onCreateEngine() {
return new VideoEngine();
}
}
- 所以我們需要再VideoWallPaperService內(nèi)部自定義一個VideoEngine類來繼承Engine類并實(shí)現(xiàn)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進(jìn)行注冊柿估,這里在注冊時寫法和普通的Service相同,其中VideoWallPaperService需要一個"android.permission.BIND_WALLPAPER"的權(quán)限陷猫,需要添加一個action為"android.service.wallpaper.WallpaperService"(固定寫法)秫舌,還需要一個名為"android.service.wallpaper"(固定寫法)的meta-data。
<service
android:name=".VideoWallPaperService"
android:permission="android.permission.BIND_WALLPAPER">
<intent-filter>
<action android:name="android.service.wallpaper.WallpaperService" />
</intent-filter>
<meta-data
android:name="android.service.wallpaper"
android:resource="@xml/wallpaper" />
</service>
- 上面一步的代碼中meta-data中需要一個xml文件源绣檬,所以我們需要再res的xml文件夾(沒有就自己建)下創(chuàng)建一個名為wallpaper的xml文件足陨。xml文件的根標(biāo)簽為wallpaper,有一下三個屬性:
- description 動態(tài)壁紙的文字描述
- thumbnail 動態(tài)壁紙的圖片描述
- settingsActivity 動態(tài)壁紙的設(shè)置界面(會在預(yù)覽界面出現(xiàn))
<?xml version="1.0" encoding="utf-8"?>
<wallpaper xmlns:android="http://schemas.android.com/apk/res/android"
android:description="@string/wallpaper_description"
android:settingsActivity="com.example.videowallpaper.SettingsActivity"
android:thumbnail="@mipmap/ic_launcher">
</wallpaper>
- 接下來就是啟動壁紙服務(wù)了娇未,這里我們不能通過context的startService()方法來啟動壁紙服務(wù)墨缘,我們需要通過啟動系統(tǒng)的預(yù)覽界面來間接啟動服務(wù)。
Intent intent = new Intent(WallpaperManager.ACTION_CHANGE_LIVE_WALLPAPER);
intent.putExtra(WallpaperManager.EXTRA_LIVE_WALLPAPER_COMPONENT, new ComponentName(context, VideoWallPaperService.class));
context.startActivity(intent);
- 除了啟動還需要關(guān)閉壁紙服務(wù)零抬,我們可以通過WallpaperManager的clear()方法來關(guān)閉镊讼,也可以通過WallpaperService的clearWallpaper()(已經(jīng)被標(biāo)記過時)方法來關(guān)閉壁紙。
try {
WallpaperManager.getInstance(context).clear();
} catch(IOException e) {
e.printStackTrace();
}
- 其他需要注意的地方
- 設(shè)置壁紙需要"android.permission.SET_WALLPAPER"權(quán)限
- 播放本地視頻需要"android.permission.READ_EXTERNAL_STORAGE"權(quán)限
- VideoEngine的MediaPlayer的播放地址要使用持久化保存數(shù)據(jù)(數(shù)據(jù)庫平夜、Preference等)蝶棋,否則設(shè)置好視頻壁紙后將手機(jī)關(guān)機(jī)再開機(jī),就會出bug
- WallpaperService的onCreateEngine()方法返回的Engine實(shí)例不能使用單例模式忽妒,必須每次都返回一個新的Engine實(shí)例
- 可以通過WallpaperManager的getWallpaperInfo()方法來判斷當(dāng)前自己的服務(wù)是否已經(jīng)在運(yùn)行玩裙,從而可以通過廣播或者其他通知來直接修改壁紙播放的視頻,從而避免每次更換一個視頻都需要走一次系統(tǒng)的預(yù)覽界面段直。
最后貼一下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
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
android:key="video_param"
android:title="設(shè)置">
<CheckBoxPreference
android:defaultValue="true"
android:key="loop"
android:title="是否循環(huán)播放" />
<CheckBoxPreference
android:checked="false"
android:key="volume"
android:title="是否開啟聲音" />
</PreferenceScreen>
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;
}