提示:Android 平台,静态壁纸实现方案
文章目录
- 需求:Android 实现壁纸 设置
- 场景
- 参考资料
- 实现方案
- 直接调用系统 API,WallpaperManager 来实现 wallpaperManager.setResource
- 系统源码分析
- 系统app WallpaperPicker
- WallpaperPickerActivity ->WallpaperCropActivity
- onClick 底部壁纸图片点击事件
- AlphaDisableableButton
- actionbar_set_wallpaper.xml
- DialogUtils
- DialogUtils ->executeCropTaskAfterPrompt
- WallpaperManagerCompat
- WallpaperPicker 源码小结
- WallpaperManager
- 拓展
- 放置默认壁纸 供选择
- 配置壁纸资源包
- 加载壁纸配置
- 总结
需求: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 insteadif (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.AlphaDisableableButtonxmlns: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);}@Overridepublic 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() {@Overridepublic 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() {@Overridepublic 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_SYSTEMpublic static final int FLAG_SET_LOCK = 1 << 1; // TODO: use WallpaperManager.FLAG_SET_LOCKprivate 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());}@Overridepublic void setStream(InputStream data, Rect visibleCropHint, boolean allowBackup,int whichWallpaper) throws IOException {mWallpaperManager.setStream(data);}@Overridepublic void clear(int whichWallpaper) throws IOException {mWallpaperManager.clear();}
}public class WallpaperManagerCompatVN extends WallpaperManagerCompatV16 {public WallpaperManagerCompatVN(Context context) {super(context);}@Overridepublic 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);}}@Overridepublic 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有很多设计思想和架构设计,这里暂不深究。
- 如果客户定制,需要动态壁纸功能。这里暂不分析,后续有机会实现下,暂不提供解决方案。