Android 静态壁纸设置实现方案

发布于:2025-03-26 ⋅ 阅读:(16) ⋅ 点赞:(0)

提示: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 的, 实际上 我们设置壁纸两个入口:

  • 首页 长按,设置壁纸
  • 进入设置目录,进入壁纸设置【接下来从设置入口进行分析】
    在这里插入图片描述

![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/a1222db5ee604392b1faf8bdb67ace90.png

在这里插入图片描述

在这里插入图片描述

一路跟踪界面,我们发现了以下几个类,可以看出最终是从设置跳转到了一个wallpaperpicker 的包中。

com.android.settings/.Settings$WallpaperSettingsActivity 
com.android.wallpaperpicker/.WallpaperPickerActivity  

系统里面搜索结果如下:
在这里插入图片描述
可以肯定的是 wallpaperpicker 就是一个单独apk, 将这个apk 源码进行分析下。

WallpaperPickerActivity ->WallpaperCropActivity

上面已经分析了,系统对应的壁纸选择跳转到一个apk 进行选择,对应的Activity 为 WallpaperPickerActivity

public class WallpaperPickerActivity extends WallpaperCropActivity
        implements OnClickListener, OnLongClickListener, ActionMode.Callback {
    static final String TAG = "WallpaperPickerActivity";
onClick 底部壁纸图片点击事件

这里突破点是左上角设置壁纸图标,当无壁纸选中 默认情况是不可点击的,下面壁纸选项点击选中时候左上角 设置壁纸按钮可点击了。
看该方法的注释:

Called when a wallpaper tile is clicked 

也就是点击后底部弹框的操作 
 /**
     * Called when a wallpaper tile is clicked
     */
    @Override
    public void onClick(View v) {
        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);
        }
    }
}


哈哈,这里最终调用的其实是 WallpaperManagerCompatVNWallpaperManagerCompatV16  类的 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有很多设计思想和架构设计,这里暂不深究。
  • 如果客户定制,需要动态壁纸功能。这里暂不分析,后续有机会实现下,暂不提供解决方案。