这两天在公众号上偶然看到一篇关于设置动态壁纸的文章,觉得蛮有意思的,学习了一下,以此文章记录一下怎样给手机设置静态壁纸和动态壁纸,设置壁纸的使用方法。
静态壁纸
Android中WallpaperManager系统服务用于管理壁纸的运行与切换,并通过WallpaperManager类向外界提供操作壁纸的接口。
设置静态壁纸是通过调用系统WallpaperManager的方法来实现的,
主要分为下面三种,同一种因不同的方法参数会对应多个方法:
(1)通过bitmap设置壁纸:setBitmap
(2)通过资源文件设置壁纸:setResource
(3)通过流设置壁纸:setStream
具体例子如下:
WallpaperManager wallpaperManager = WallpaperManager.getInstance(this);
try {
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher);
wallpaperManager.setBitmap(bitmap);
} catch (IOException e) {
e.printStackTrace();
}
注意:
(1)设置壁纸需要声明权限
<uses-permission android:name="android.permission.SET_WALLPAPER" />
(2)壁纸分系统壁纸和锁屏壁纸,但是API 24才能调用系统api设置。
比如:
WallpaperManager wallpaperManager = WallpaperManager.getInstance(this);
try {
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.N){
wallpaperManager.setResource(R.raw.wallpaper, WallpaperManager.FLAG_SYSTEM);
}
} catch (IOException e) {
e.printStackTrace();
}
其中WallpaperManager.FLAG_SYSTEM表示系统壁纸,WallpaperManager.FLAG_LOCK表示锁屏壁纸。
既然可以设置壁纸,相对应的就可以清除壁纸,具体如下:
WallpaperManager wallpaperManager = WallpaperManager.getInstance(this);
try {
wallpaperManager.clear();
} catch (IOException e) {
e.printStackTrace();
}
动态壁纸
动态壁纸是通过Android中的WallpaperService服务启动并开始进行壁纸的绘制工作,其中内部类Engine,实现了壁纸窗口的创建以及Surface的维护工作。
那怎样实现一个简单的动态壁纸呢?主要分为以下几步:
1.定义MyWallpaperService继承WallpaperService并实现onCreateEngine方法,返回自己的Engine实现类:
public class MyWallpaperService extends WallpaperService {
// 实现动态壁纸必须要实现的抽象方法
@Override
public Engine onCreateEngine() {
bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.star);
return new MyEngine();
}
}
其中在MyEngine中通过对生命周期方法的调用实现壁纸进行动画和绘制工作:
class MyEngine extends Engine{
@Override
public void onCreate(SurfaceHolder surfaceHolder) {
super.onCreate(surfaceHolder);
当引擎初始化时被调用,此时绘制surface还未被创建
可以进行paint等的初始化工作
}
@Override
public void onVisibilityChanged(boolean visible) {
super.onVisibilityChanged(visible);
当壁纸可见或隐藏时被调用,应该暂停你的动画,不再绘制任何东西,以节省CPU(比如说移除Runnable等)。
}
@Override
public void onSurfaceCreated(SurfaceHolder holder) {
super.onSurfaceCreated(holder);
当surface被创建时被调用
}
@Override
public void onSurfaceChanged(SurfaceHolder holder, int format, int width, int height) {
super.onSurfaceChanged(holder, format, width, height);
当surface的图形结构发生变化(大小、格式)
}
@Override
public void onSurfaceDestroyed(SurfaceHolder holder) {
super.onSurfaceDestroyed(holder);
当surface被销毁时被调用
}
@Override
public void onOffsetsChanged(float xOffset, float yOffset, float xOffsetStep, float yOffsetStep,
int xPixelOffset, int yPixelOffset) {
super.onOffsetsChanged(xOffset, yOffset, xOffsetStep, yOffsetStep, xPixelOffset,
yPixelOffset);
当壁纸发生偏移变化,例如用户左右滑动主屏幕,这个可以用来创建一个平行滑动的效果,就是用户滑动时,壁纸同时发生偏移。
}
@Override
public void onTouchEvent(MotionEvent event) {
super.onTouchEvent(event);
当显示壁纸时,用户跟主屏幕有触摸交互时被调用
}
@Override
public void onDestroy() {
super.onDestroy();
引擎销毁时被调用。此方法调用之后,引擎变得不可用。
}
}
2.配置AndroidManifest.xml,声明上述中的自定义的service和一个SettingActivity:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.liuwei.wallpagerdemo">
<uses-permission android:name="android.permission.SET_WALLPAPER" />
<uses-feature
android:name="android.software.live_wallpaper"
android:required="true"/>
<application
......
<activity android:name=".SettingActivity"
android:exported="true"
android:label="@string/app_name"/>
<service android:name=".MyWallpaperService"
android:enabled="true"
android:label="@string/app_name"
android:permission="android.permission.BIND_WALLPAPER">
<intent-filter android:priority="1">
<action android:name="android.service.wallpaper.WallpaperService"/>
</intent-filter>
<meta-data
android:name="android.service.wallpaper"
android:resource="@xml/wallpager"/>
</service>
</application>
</manifest>
注意:
(1)设置动态壁纸需要在MyWallpaperService中声明android:permission="android.permission.BIND_WALLPAPER权限
(2)必须声明,表示动态壁纸
<uses-feature
android:name="android.software.live_wallpaper"
android:required="true"/>
(3)声明MyWallpaperService的action为android:name="android.service.wallpaper.WallpaperService,其中在meta-data中有一个resource在下一步中说明。
可能比较疑惑为什么要创建SettingActivity,关于为什么创建SettingActivity,在下述中说明。
3.在res/xml下创建xml文件,我这里命名为wallpaper.xml,为上述中service中的resource:
<?xml version="1.0" encoding="utf-8"?>
<wallpaper xmlns:android="http://schemas.android.com/apk/res/android"
android:description="@string/app_name"
android:settingsActivity="com.example.liuwei.wallpagerdemo.SettingActivity"
android:thumbnail="@mipmap/star" />
其中description(应用描述)settingsActivity(应用指定的壁纸设置页面),thumbnail(应用图标)三个属性自定义设置。
4.配置工作完成,启动壁纸服务:
通过下面这种方式打开壁纸设置页面设置动态壁纸:
Intent intent = new Intent(
WallpaperManager.ACTION_CHANGE_LIVE_WALLPAPER);
intent.putExtra(WallpaperManager.EXTRA_LIVE_WALLPAPER_COMPONENT,
new ComponentName(this, MyWallpaperService.class));
startActivity(intent);
这时候心里想MyWallpaperService是service,那能不能通过startService()启动壁纸,通过stopService()清除壁纸呢?
经测试,不能。这也是上述声明SettingActivity的原因。
**所以启动壁纸服务时,不能通过context的startService()方法来启动壁纸服务,需要通过启动系统的预览界面来间接启动服务。**但明明是一个service为什么这种方式不能成功设置壁纸呢,在下述原理中说明。
通过以上四个步骤我们就可以设置动态壁纸了,在运行app时点击设置动态壁纸进入到下面页面:
点击应用就可以应用此动态壁纸,但是点击设置按钮,因为这个例子中的的SettingActivity继承的是PreferenceActivity,进入到下面页面:
此页面内容为SettingActivity中设置的PreferenceScreen的xml文件,可以通过设置xml文件在此页面中对动态壁纸进行设置:
public class SettingActivity extends PreferenceActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.prefs);
Preference circlePreferennce = getPreferenceScreen().findPreference("numberOfCircles");
circlePreferennce.setOnPreferenceChangeListener(numberCheckListener);
}
Preference.OnPreferenceChangeListener numberCheckListener = new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
if(newValue != null && newValue.toString().length() > 0 && newValue.toString().matches("\\d*")){
return true;
}
return false;
}
};
}
res/xml下的prefs.xml文件:
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<CheckBoxPreference
android:key="touch"
android:title="Touch"/>
<EditTextPreference
android:key="numberOfCircles"
android:title="Number"/>
</PreferenceScreen>
使用PreferenceActivity还可以做一些其他事情,关于PreferenceActivity的用法请参考这里
最后关于清除动态壁纸:
可以使用上述中的清除静态壁纸的方法进行清除,或是如下利用WallpaperService的onDestroy方法:
if(myWallpaperService != null){
myWallpaperService.onDestroy();
}
除此之外,还可以使用视频,gif等设置动态壁纸,流程相同,只是在Engine中的不同的实现对壁纸进行不同的绘制工作,后续有时间学习一下,再补充。
学习demo点击这里。
原理
知道了怎样设置静态壁纸和动态壁纸之后引发一些思考:动态壁纸需要自定义继承WallpaperService,在onCreateEngine中返回Engine实现,为什么静态壁纸只需要调用WallpaperManager的api方法就可以呢?而动态壁纸继承WallpaperService也就说明了是一个service,为什么通过startService启动方式不能成功设置动态壁纸呢?系统是怎样通过WallpaperService进行通信的?此时需要了解动态壁纸和静态壁纸的原理了。
对于上述几个疑问:
1.通信:
动态壁纸和静态壁纸都是以一个Service的形式运行在系统后台,并在一个类型为TYPE_WALLPAPER的窗口上绘制内容。
WallpaperService是由WallpaperManagerService(系统Service,在系统启动时,由系统进程SystemServer创建)启动(通过绑定服务的方式)并管理的。
在设置壁纸时,应用程序通过WallpaperManager通过AIDl与WallpaperManagerService进行通信。
Android的动态墙纸虽然似乎是显示在Launcher的背景里,但其实这只是假象,动态墙纸和Launcher是完全不同的两个进程,只不过Launcher和动态墙纸的进程可以通过框架里的WallpaperManager进行进程间通信罢了,用户在Launcher桌面滑动、点击屏幕时有的动态墙纸能产生交互效果,实际上就是这个进程通信完成的。如果你通过代码将Launcher的背景设置为非透明的,比如以不透明的图片或者颜色作为背景,那么,你将看不到任何动态墙纸效果,当然,这样的话,静态墙纸你也不会看到了。
2.启动动态壁纸方法:
查看源码可知,启动动态壁纸是通过WallpaperManager.java的setWallpaperComponent这个方法实现的,如下:
@SystemApi
@RequiresPermission(android.Manifest.permission.SET_WALLPAPER_COMPONENT)
public boolean setWallpaperComponent(ComponentName name) {
return setWallpaperComponent(name, mContext.getUserId());
}
setWallpaperComponent这个方法是系统api,不能直接被开发者调用,因此第三方应用程序是无法进行动态壁纸的设置的,只能通过上述中的方法启动动态壁纸。
3.静态壁纸:
ImageWallpaper表示静态壁纸,对应静态壁纸,系统中有一个ImageWallpaper(继承自WallpaperService) 和DrawableEngine(继承自Engine)。
查看源码深入理解壁纸原理请点击这里
参考
https://mp.weixin.qq.com/s/4_ER7XsObMjRfHGOkOWmhg
https://blog.csdn.net/wangjinyu501/article/details/83317542
https://blog.csdn.net/qxs965266509/article/details/61925652
https://blog.csdn.net/u012968084/article/details/52160399
https://blog.csdn.net/leopard21/article/details/32201501