提示:Android 平台,静态壁纸实现方案
文章目录
需求:Android 实现壁纸 设置
最近看到一个友商实现了一个功能,壁纸作为单独apk 拎出来作为一个apk 单独出现形式,比较有意义。 自己也实现一个,实现需求同时,分析源码实现方案 和 流程。
#需求
将壁纸设置作为一个apk形式,实现壁纸设置功能
场景
很多平板方案,将这个功能单独实现,作为一个app,方便客户使用。
这里只是用一个静态壁纸设置实现的方式,来初步了解Android壁纸相关内容。
参考资料
android WallpaperPicker7.0源码分析
Android 切换壁纸代码流程追踪:
Android 12新特性之获取壁纸主色调并设置系统主题色
实现方案
直接调用系统 API,WallpaperManager 来实现 wallpaperManager.setResource
WallpaperManager wallpaperManager = WallpaperManager.getInstance(this);
try {
wallpaperManager.setResource(R.drawable.picture);
} catch (IOException e) {
e.printStackTrace();
}
设置静态壁纸有很多途径,但归根结底都是一下三种方法:
- 使用WallpaperManager的setResource(int ResourceID)方法
- 使用WallpaperManager的setBitmap(Bitmap bitmap)方法
- 使用WallpaperManager的setStream(InputStream data)方法
可以参考WallpaperManager 源码分析,源码代码量还好,不多。
\frameworks\base\core\java\android\app\WallpaperManager.java
系统源码分析
系统app WallpaperPicker
我们先看一下如何进入到WallpaperPicker 的, 实际上 我们设置壁纸两个入口:
- 首页 长按,设置壁纸
- 进入设置目录,进入壁纸设置【接下来从设置入口进行分析】
 {
if (mActionMode != null) {
// When CAB is up, clicking toggles the item instead
if (v.isLongClickable()) {
onLongClick(v);
}
return;
}
WallpaperTileInfo info = (WallpaperTileInfo) v.getTag();
if (info.isSelectable() && v.getVisibility() == View.VISIBLE) {
selectTile(v);
setWallpaperButtonEnabled(true);
}
info.onClick(this);
}
public void setWallpaperButtonEnabled(boolean enabled) {
mSetWallpaperButton.setEnabled(enabled);
}
mSetWallpaperButton = findViewById(R.id.set_wallpaper_button);
在 WallpaperPickerActivity 类中并没有找到 mSetWallpaperButton 定义地方,只是找到加载地方 findViewById,那么去父类 WallpaperCropActivity.java 看看
protected View mSetWallpaperButton;
// WallpaperCropActivity.java 类中,也是通过findViewById 来加载,并声明它是一个View
mSetWallpaperButton = findViewById(R.id.set_wallpaper_button);
那这个set_wallpaper_button 对应的到底是什么,继续找
这里看到了 在一个布局文件和上面所讲WallpaperPickerActivity 、WallpaperCropActivity 两个类加载了。
看看布局文件如下
<com.android.wallpaperpicker.AlphaDisableableButton
xmlns:android="http://schemas.android.com/apk/res/android"
style="@style/ActionBarSetWallpaperStyle"
android:id="@+id/set_wallpaper_button"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingRight="20dp"
android:drawableLeft="@drawable/ic_actionbar_accept"
android:drawablePadding="8dp"
android:gravity="start|center_vertical"
android:text="@string/wallpaper_instructions"
android:enabled="false" />
具体分析 如下 分析,AlphaDisableableButton 相关内容
AlphaDisableableButton
上面找到了AlphaDisableableButton , 其实就是一个自定义的View,看代码。
/**
* A Button which becomes translucent when it is disabled
*/
public class AlphaDisableableButton extends Button {
public static float DISABLED_ALPHA_VALUE = 0.4f;
public AlphaDisableableButton(Context context) {
this(context, null);
}
public AlphaDisableableButton(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public AlphaDisableableButton(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
setLayerType(LAYER_TYPE_HARDWARE, null);
}
@Override
public void setEnabled(boolean enabled) {
super.setEnabled(enabled);
if(enabled) {
setAlpha(1.0f);
} else {
setAlpha(DISABLED_ALPHA_VALUE);
}
}
}
备注:AlphaDisableableButton 类源码如上,就是一个自定义View,如上类说明:不可见的时候透明状态。
A Button which becomes translucent when it is disabled
actionbar_set_wallpaper.xml
上面已经分析到了AlphaDisableableButton 源码和布局文件,那这个xml 又是哪里加载的。
这里说明WallpaperPickerActivity 、 WallpaperCropActivity 加载这个布局文件,不就是显示ActionBar 嘛,如 加载说明:
Show the custom action bar view
DialogUtils
承接上述分析 ,actionbar 点击后,也就是 设置壁纸按钮 点击,弹出选择框逻辑
actionBar.getCustomView().setOnClickListener(
new View.OnClickListener() {
@Override
public void onClick(View v) {
actionBar.hide();
// Never fade on finish because we return to the app that started us (e.g.
// Photos), not the home screen.
cropImageAndSetWallpaper(imageUri, null, false /* shouldFadeOutOnFinish */);
}
});
cropImageAndSetWallpaper(imageUri, null, false /* shouldFadeOutOnFinish */);
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
public void cropImageAndSetWallpaper(Uri uri,
CropAndSetWallpaperTask.OnBitmapCroppedHandler onBitmapCroppedHandler,
boolean shouldFadeOutOnFinish) {
。。。。。。。。。。。。。。
DialogUtils.executeCropTaskAfterPrompt(this, cropTask, getOnDialogCancelListener());
}
DialogUtils ->executeCropTaskAfterPrompt
接下来就是dialog 具体源码如下:
/**
* Utility class used to show dialogs for things like picking which wallpaper to set.
*/
public class DialogUtils {
/**
* Calls cropTask.execute(), once the user has selected which wallpaper to set. On pre-N
* devices, the prompt is not displayed since there is no API to set the lockscreen wallpaper.
*
* TODO: Don't use CropAndSetWallpaperTask on N+, because the new API will handle cropping instead.
*/
public static void executeCropTaskAfterPrompt(
Context context, final AsyncTask<Integer, ?, ?> cropTask,
DialogInterface.OnCancelListener onCancelListener) {
if (Utilities.isAtLeastN()) {
new AlertDialog.Builder(context)
.setTitle(R.string.wallpaper_instructions)
.setItems(R.array.which_wallpaper_options, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int selectedItemIndex) {
int whichWallpaper;
if (selectedItemIndex == 0) {
whichWallpaper = WallpaperManagerCompat.FLAG_SET_SYSTEM;
} else if (selectedItemIndex == 1) {
whichWallpaper = WallpaperManagerCompat.FLAG_SET_LOCK;
} else {
whichWallpaper = WallpaperManagerCompat.FLAG_SET_SYSTEM
| WallpaperManagerCompat.FLAG_SET_LOCK;
}
cropTask.execute(whichWallpaper);
}
})
.setOnCancelListener(onCancelListener)
.show();
} else {
cropTask.execute(WallpaperManagerCompat.FLAG_SET_SYSTEM);
}
}
}
其实这个类说得很明白了,就是设置壁纸用的。
这里 我们记住 我们选择的是第三个选项,也就是 桌面壁纸和锁屏壁纸,参数flag 不一样而已
whichWallpaper = WallpaperManagerCompat.FLAG_SET_SYSTEM
| WallpaperManagerCompat.FLAG_SET_LOCK;
WallpaperManagerCompat
DialogUtils 源码分析到了 源码 ,那就继续追踪到 WallpaperManagerCompat
whichWallpaper = WallpaperManagerCompat.FLAG_SET_SYSTEM
| WallpaperManagerCompat.FLAG_SET_LOCK;
cropTask.execute(whichWallpaper);
public abstract class WallpaperManagerCompat {
public static final int FLAG_SET_SYSTEM = 1 << 0; // TODO: use WallpaperManager.FLAG_SET_SYSTEM
public static final int FLAG_SET_LOCK = 1 << 1; // TODO: use WallpaperManager.FLAG_SET_LOCK
private static WallpaperManagerCompat sInstance;
private static final Object sInstanceLock = new Object();
public static WallpaperManagerCompat getInstance(Context context) {
synchronized (sInstanceLock) {
if (sInstance == null) {
if (Utilities.isAtLeastN()) {
sInstance = new WallpaperManagerCompatVN(context.getApplicationContext());
} else {
sInstance = new WallpaperManagerCompatV16(context.getApplicationContext());
}
}
return sInstance;
}
}
public abstract void setStream(InputStream stream, Rect visibleCropHint, boolean allowBackup,
int whichWallpaper) throws IOException;
public abstract void clear(int whichWallpaper) throws IOException;
}
发现这里 WallpaperManagerCompat 类还是一个抽象类,那么实际的控制其实是 WallpaperManagerCompatVN 、WallpaperManagerCompatV16, 继续看其中代码如下
public class WallpaperManagerCompatV16 extends WallpaperManagerCompat {
protected WallpaperManager mWallpaperManager;
public WallpaperManagerCompatV16(Context context) {
mWallpaperManager = WallpaperManager.getInstance(context.getApplicationContext());
}
@Override
public void setStream(InputStream data, Rect visibleCropHint, boolean allowBackup,
int whichWallpaper) throws IOException {
mWallpaperManager.setStream(data);
}
@Override
public void clear(int whichWallpaper) throws IOException {
mWallpaperManager.clear();
}
}
public class WallpaperManagerCompatVN extends WallpaperManagerCompatV16 {
public WallpaperManagerCompatVN(Context context) {
super(context);
}
@Override
public void setStream(final InputStream data, Rect visibleCropHint, boolean allowBackup,
int whichWallpaper) throws IOException {
try {
// TODO: use mWallpaperManager.setStream(data, visibleCropHint, allowBackup, which)
// without needing reflection.
Method setStream = WallpaperManager.class.getMethod("setStream", InputStream.class,
Rect.class, boolean.class, int.class);
setStream.invoke(mWallpaperManager, data, visibleCropHint, allowBackup, whichWallpaper);
} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
// Fall back to previous implementation (set both)
super.setStream(data, visibleCropHint, allowBackup, whichWallpaper);
}
}
@Override
public void clear(int whichWallpaper) throws IOException {
try {
// TODO: use mWallpaperManager.clear(whichWallpaper) without needing reflection.
Method clear = WallpaperManager.class.getMethod("clear", int.class);
clear.invoke(mWallpaperManager, whichWallpaper);
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
// Fall back to previous implementation (set both)
super.clear(whichWallpaper);
}
}
}
哈哈,这里最终调用的其实是 WallpaperManagerCompatVN 、WallpaperManagerCompatV16 类的 setStream 方法。 再最终调用的就是 WallpaperManager 类的 setStream 方法。
WallpaperPicker 源码小结
上面的代码流程分析,最终共调用到了Framework层的WallpaperManager类的 setStream 方法。
WallpaperManager
上面已经分析到了 最终调用到 WallpaperManager 服务,通过反射调用
如下 类说明
/**
* Provides access to the system wallpaper. With WallpaperManager, you can
* get the current wallpaper, get the desired dimensions for the wallpaper, set
* the wallpaper, and more.
*
* <p> An app can check whether wallpapers are supported for the current user, by calling
* {@link #isWallpaperSupported()}, and whether setting of wallpapers is allowed, by calling
* {@link #isSetWallpaperAllowed()}.
*/
@SystemService(Context.WALLPAPER_SERVICE)
public class WallpaperManager {
.....
}
提供了系统壁纸入口,可以获取当前壁纸、设置壁纸…, app 可以检查当前用户是否允许设置壁纸,是否有权限等。继续分析源码 ,查看部分方法如下
这里看到设置壁纸的三种类型方法,如上文开始的分析,
归根结底都是一下三种方法:
- 使用WallpaperManager的setResource(int ResourceID)方法
- 使用WallpaperManager的setBitmap(Bitmap bitmap)方法
- 使用WallpaperManager的setStream(InputStream data)方法
尝试三种方案,设置静态壁纸就是很简单的逻辑了,直接调用api ,反射或者拿到framework.jar,调用API 直接调用的事情了。 如下,某个友商做出来的效果,实际效果大家可以自己随便做了。
拓展
如上通过WallerpaperPicker 包分析了壁纸设置的整体流程,如果只是需要定制壁纸选择需求可以显示图片并调用api 反射或者 api 调用实现需求;
如果需要添加壁纸选择怎么办呢? 那只需要在 app 里面配置资源包即可。
思路如下:
放置默认壁纸 供选择
在 资源包中添加壁纸图片
配置壁纸资源包
加载壁纸配置
总结
- 这里实现了静态壁纸设置的方法,就是一个反射或者api 调用。 实际 逻辑比较简单的
- 这里只是从系统app WallpaperPicker,通过界面,反推实现设置静态壁纸的逻辑和分析代码层面的流程和业务。 实际上 WallpaperPicker App有很多设计思想和架构设计,这里暂不深究。
- 如果客户定制,需要动态壁纸功能。这里暂不分析,后续有机会实现下,暂不提供解决方案。