Android视频壁纸的实现

视频壁纸属于动态壁纸,所以视频壁纸就可以用Android系统提供的动态壁纸服务来实现。首先先介绍一下在实现过程中会用到的几个类。

WallpaperManager

Android提供的用于管理壁纸的类,里面有几个方法在实现过程中需要用到

  1. getInstance(context) 获取一个实例
  2. clear() 清空所有的壁纸
  3. getWallpaperInfo() 获取当前壁纸的信息

WallpaperService

Android系统提供的壁纸服务的抽象类,实现一个动态壁纸必须要继承该类并实现onCreateEngine()方法,该类的唯一功能就是返回绘制动态壁纸所需要的Engine实例。

WallpaperService.Engine

Engine是WallpaperService一个内部类,动态壁纸的所有显示过程的绘制都由该类完成,我们需要继承该类并实现以下三个方法:

  1. onSurfaceCreated()
  2. onSurfaceDestroyed()
  3. 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。
<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文件夹(没有就自己建)下创建一个名为wallpaper的xml文件。xml文件的根标签为wallpaper,有一下三个属性:
  1. description 动态壁纸的文字描述
  2. thumbnail 动态壁纸的图片描述
  3. settingsActivity 动态壁纸的设置界面(会在预览界面出现)
<?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>
  • 接下来就是启动壁纸服务了,这里我们不能通过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();
}
  • 其他需要注意的地方
  1. 设置壁纸需要"android.permission.SET_WALLPAPER"权限
  2. 播放本地视频需要"android.permission.READ_EXTERNAL_STORAGE"权限
  3. VideoEngine的MediaPlayer的播放地址要使用持久化保存数据(数据库、Preference等),否则设置好视频壁纸后将手机关机再开机,就会出bug
  4. WallpaperService的onCreateEngine()方法返回的Engine实例不能使用单例模式,必须每次都返回一个新的Engine实例
  5. 可以通过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

<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
    android:key="video_param"
    android:title="设置">
    <CheckBoxPreference
        android:defaultValue="true"
        android:key="loop"
        android:title="是否循环播放" />
    <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;
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,185评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,652评论 3 393
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,524评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,339评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,387评论 6 391
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,287评论 1 301
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,130评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,985评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,420评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,617评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,779评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,477评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,088评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,716评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,857评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,876评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,700评论 2 354

推荐阅读更多精彩内容