当前位置: 首页 > news >正文

【Android】Wallpaper学习

从wallpaper的设置来了解相关内容:

一,静态壁纸

静态壁纸设置的原理是在WallpaperManagerService里监听/data/system/users/0/wallpaper_orig相关文件的变化来触发设置,通过相应的组件程序去进行绘制,

相应的组件如:

mWallpaperComponent=ComponentInfo{com.android.systemui/com.android.systemui.wallpapers.ImageWallpaper}

  Wallpaper connection com.android.server.wallpaper.WallpaperManagerService$WallpaperConnection@739a4fa:

以下是一个简单的 Android 程序示例,展示如何设置设备的壁纸。程序会从资源文件中加载一张图片,并将其设置为设备的主屏幕壁纸。

1. 项目结构
(1) 添加权限
在 AndroidManifest.xml 中添加设置壁纸的权限:

Xml
深色版本
<uses-permission android:name="android.permission.SET_WALLPAPER" />
(2) 准备壁纸图片
将你想要设置为壁纸的图片放入 res/drawable 目录中,例如命名为 wallpaper_image.jpg。

2. 代码实现
(1) 主 Activity (MainActivity.java)
创建一个按钮,点击后设置壁纸。

Java
深色版本
package com.example.setwallpaper;

import android.app.WallpaperManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

import androidx.appcompat.app.AppCompatActivity;

import java.io.IOException;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // 获取按钮控件
        Button setWallpaperButton = findViewById(R.id.set_wallpaper_button);

        // 设置按钮点击事件
        setWallpaperButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                setWallpaper();
            }
        });
    }

    private void setWallpaper() {
        // 获取 WallpaperManager 实例
        WallpaperManager wallpaperManager = WallpaperManager.getInstance(getApplicationContext());

        try {
            // 加载图片资源
            Bitmap wallpaperBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.wallpaper_image);

            // 设置壁纸
            wallpaperManager.setBitmap(wallpaperBitmap);

            // 提示用户成功设置壁纸
            Toast.makeText(MainActivity.this, "壁纸设置成功!", Toast.LENGTH_SHORT).show();
        } catch (IOException e) {
            // 捕获异常并提示用户失败
            Toast.makeText(MainActivity.this, "壁纸设置失败!", Toast.LENGTH_SHORT).show();
            e.printStackTrace();
        }
    }
}
(2) 布局文件 (activity_main.xml)
定义一个按钮,用于触发设置壁纸的操作。

Xml
深色版本
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:gravity="center">

    <Button
        android:id="@+id/set_wallpaper_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="设置壁纸" />

</LinearLayout>
3. 运行效果
启动应用后,界面会显示一个按钮。
点击按钮后,应用会从资源文件中加载图片,并将其设置为主屏幕的壁纸。
如果设置成功,会弹出一个 Toast 提示 "壁纸设置成功!";如果失败,则会提示 "壁纸设置失败!"。
4. 扩展功能
(1) 支持锁屏壁纸
如果你想同时设置主屏幕和锁屏壁纸,可以使用以下代码:

Java
深色版本
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
    // 设置主屏幕壁纸
    wallpaperManager.setBitmap(wallpaperBitmap, null, true, WallpaperManager.FLAG_SYSTEM);

    // 设置锁屏壁纸
    wallpaperManager.setBitmap(wallpaperBitmap, null, true, WallpaperManager.FLAG_LOCK);
} else {
    // 对于低版本系统,只能设置主屏幕壁纸
    wallpaperManager.setBitmap(wallpaperBitmap);
}
(2) 从相册选择图片
你可以通过 Intent 让用户从相册中选择一张图片作为壁纸:

Java
深色版本
private static final int PICK_IMAGE_REQUEST = 1;

private void openGallery() {
    Intent intent = new Intent(Intent.ACTION_PICK);
    intent.setType("image/*");
    startActivityForResult(intent, PICK_IMAGE_REQUEST);
}

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);

    if (requestCode == PICK_IMAGE_REQUEST && resultCode == RESULT_OK && data != null) {
        try {
            // 获取选中的图片 URI
            Uri imageUri = data.getData();

            // 将图片转换为 Bitmap
            Bitmap wallpaperBitmap = MediaStore.Images.Media.getBitmap(getContentResolver(), imageUri);

            // 设置壁纸
            WallpaperManager wallpaperManager = WallpaperManager.getInstance(getApplicationContext());
            wallpaperManager.setBitmap(wallpaperBitmap);

            // 提示用户成功设置壁纸
            Toast.makeText(this, "壁纸设置成功!", Toast.LENGTH_SHORT).show();
        } catch (Exception e) {
            e.printStackTrace();
            Toast.makeText(this, "壁纸设置失败!", Toast.LENGTH_SHORT).show();
        }
    }
}
5. 注意事项
权限问题:
设置壁纸不需要动态申请权限,但如果你从相册选择图片,则需要处理运行时权限(如 READ_EXTERNAL_STORAGE)。

setBitmap的调用,可以查看WallpaperManager.java

    public int setBitmap(Bitmap fullImage, Rect visibleCropHint,boolean allowBackup, @SetWallpaperFlags int which, int userId)throws IOException {validateRect(visibleCropHint);if (sGlobals.mService == null) {Log.w(TAG, "WallpaperService not running");throw new RuntimeException(new DeadSystemException());}final Bundle result = new Bundle();final WallpaperSetCompletion completion = new WallpaperSetCompletion();try {ParcelFileDescriptor fd = sGlobals.mService.setWallpaper(null,mContext.getOpPackageName(), visibleCropHint, allowBackup,result, which, completion, userId);if (fd != null) {FileOutputStream fos = null;try {fos = new ParcelFileDescriptor.AutoCloseOutputStream(fd);fullImage.compress(Bitmap.CompressFormat.PNG, 90, fos);fos.close();completion.waitForCompletion();} finally {IoUtils.closeQuietly(fos);}}} catch (RemoteException e) {throw e.rethrowFromSystemServer();}return result.getInt(EXTRA_NEW_WALLPAPER_ID, 0);}

调用到setWallpaper方法

   @Override
3009      public ParcelFileDescriptor setWallpaper(String name, String callingPackage,
3010              Rect cropHint, boolean allowBackup, Bundle extras, int which,
3011              IWallpaperManagerCallback completion, int userId) {
3012          userId = ActivityManager.handleIncomingUser(getCallingPid(), getCallingUid(), userId,
3013                  false /* all */, true /* full */, "changing wallpaper", null /* pkg */);
3014          checkPermission(android.Manifest.permission.SET_WALLPAPER);
3015  
3016          if ((which & (FLAG_LOCK|FLAG_SYSTEM)) == 0) {
3017              final String msg = "Must specify a valid wallpaper category to set";
3018              Slog.e(TAG, msg);
3019              throw new IllegalArgumentException(msg);
3020          }
3021  
3022          if (!isWallpaperSupported(callingPackage) || !isSetWallpaperAllowed(callingPackage)) {
3023              return null;
3024          }
3025  
3026          // "null" means the no-op crop, preserving the full input image
3027          if (cropHint == null) {
3028              cropHint = new Rect(0, 0, 0, 0);
3029          } else {
3030              if (cropHint.width() < 0 || cropHint.height() < 0
3031                      || cropHint.left < 0
3032                      || cropHint.top < 0) {
3033                  throw new IllegalArgumentException("Invalid crop rect supplied: " + cropHint);
3034              }
3035          }
3036  
3037          synchronized (mLock) {
3038              if (DEBUG) Slog.v(TAG, "setWallpaper which=0x" + Integer.toHexString(which));
3039              WallpaperData wallpaper;
3040              final WallpaperData originalSystemWallpaper = mWallpaperMap.get(userId);
3041              final boolean systemIsStatic =
3042                      originalSystemWallpaper != null && mImageWallpaper.equals(
3043                              originalSystemWallpaper.wallpaperComponent);
3044              final boolean systemIsBoth = mLockWallpaperMap.get(userId) == null;
3045  
3046              /* If we're setting system but not lock, and lock is currently sharing the system
3047               * wallpaper, we need to migrate that image over to being lock-only before
3048               * the caller here writes new bitmap data.
3049               */
3050              if (which == FLAG_SYSTEM && systemIsStatic && systemIsBoth) {
3051                  Slog.i(TAG, "Migrating current wallpaper to be lock-only before"
3052                          + " updating system wallpaper");
3053                  if (!migrateStaticSystemToLockWallpaperLocked(userId)
3054                          && !isLockscreenLiveWallpaperEnabled()) {
3055                      which |= FLAG_LOCK;
3056                  }
3057              }
3058  
3059              wallpaper = getWallpaperSafeLocked(userId, which);
3060              if (mPendingMigrationViaStatic != null) {
3061                  Slog.w(TAG, "Starting new static wp migration before previous migration finished");
3062              }
3063              mPendingMigrationViaStatic = new WallpaperDestinationChangeHandler(wallpaper);
3064              final long ident = Binder.clearCallingIdentity();
3065              try {
3066                  ParcelFileDescriptor pfd = updateWallpaperBitmapLocked(name, wallpaper, extras);
3067                  if (pfd != null) {
3068                      wallpaper.imageWallpaperPending = true;
3069                      wallpaper.mSystemWasBoth = systemIsBoth;
3070                      wallpaper.mWhich = which;
3071                      wallpaper.setComplete = completion;
3072                      wallpaper.fromForegroundApp = isFromForegroundApp(callingPackage);
3073                      wallpaper.cropHint.set(cropHint);
3074                      wallpaper.allowBackup = allowBackup;
3075                      wallpaper.mWallpaperDimAmount = getWallpaperDimAmount();
3076                  }
3077                  return pfd;
3078              } finally {
3079                  Binder.restoreCallingIdentity(ident);
3080              }
3081          }
3082      }

又调用到

updateWallpaperBitmapLocked

3121      ParcelFileDescriptor updateWallpaperBitmapLocked(String name, WallpaperData wallpaper,
3122              Bundle extras) {
3123          if (name == null) name = "";
3124          try {
3125              File dir = getWallpaperDir(wallpaper.userId);
3126              if (!dir.exists()) {
3127                  dir.mkdir();
3128                  FileUtils.setPermissions(
3129                          dir.getPath(),
3130                          FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IXOTH,
3131                          -1, -1);
3132              }
3133              ParcelFileDescriptor fd = ParcelFileDescriptor.open(wallpaper.wallpaperFile,
3134                      MODE_CREATE|MODE_READ_WRITE|MODE_TRUNCATE);
3135              if (!SELinux.restorecon(wallpaper.wallpaperFile)) {
3136                  Slog.w(TAG, "restorecon failed for wallpaper file: " +
3137                          wallpaper.wallpaperFile.getPath());
3138                  return null;
3139              }
3140              wallpaper.name = name;
3141              wallpaper.wallpaperId = makeWallpaperIdLocked();
3142              if (extras != null) {
3143                  extras.putInt(WallpaperManager.EXTRA_NEW_WALLPAPER_ID, wallpaper.wallpaperId);
3144              }
3145              // Nullify field to require new computation
3146              wallpaper.primaryColors = null;
3147              Slog.v(TAG, "updateWallpaperBitmapLocked() : id=" + wallpaper.wallpaperId
3148                      + " name=" + name + " file=" + wallpaper.wallpaperFile.getName());
3149              return fd;
3150          } catch (FileNotFoundException e) {
3151              Slog.w(TAG, "Error setting wallpaper", e);
3152          }
3153          return null;
3154      }

二,动态壁纸

在 Android 中开发动态壁纸需要使用 WallpaperService 和 Engine 类。动态壁纸本质上是一个服务,它运行在后台并持续渲染内容到用户的主屏幕或锁屏上。

以下是开发一个简单动态壁纸的步骤和示例代码:

1. 创建动态壁纸项目
(1) 添加权限
在 AndroidManifest.xml 中声明动态壁纸服务,并添加必要的权限。

Xml
深色版本
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.dynamicwallpaper">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/Theme.AppCompat.Light.NoActionBar">
        
        <!-- 动态壁纸服务 -->
        <service
            android:name=".MyWallpaperService"
            android:label="@string/wallpaper_label"

android:exported="true"

            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_info" />
        </service>
    </application>
</manifest>
(2) 创建动态壁纸元数据文件
在 res/xml 目录下创建一个 XML 文件(例如 wallpaper_info.xml),用于描述动态壁纸的元信息。

Xml
深色版本
<wallpaper
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:thumbnail="@drawable/thumbnail_image"
    android:description="@string/wallpaper_description"
    android:settingsActivity="com.example.dynamicwallpaper.SettingsActivity" />
android:thumbnail:动态壁纸的缩略图。
android:description:动态壁纸的描述。
android:settingsActivity(可选):设置界面的 Activity。
2. 实现动态壁纸服务
(1) 继承 WallpaperService
创建一个类继承 WallpaperService,并实现其抽象方法。


package com.example.dynamicwallpaper;

import android.service.wallpaper.WallpaperService;
import android.view.SurfaceHolder;

public class MyWallpaperService extends WallpaperService {

    @Override
    public Engine onCreateEngine() {
        return new MyWallpaperEngine();
    }

    private class MyWallpaperEngine extends Engine {

        private boolean isVisible = false;

private final Handler handler = new Handler();

        @Override
        public void onCreate(SurfaceHolder surfaceHolder) {
            super.onCreate(surfaceHolder);
            // 初始化操作
        }

        @Override
        public void onVisibilityChanged(boolean visible) {
            this.isVisible = visible;
            if (visible) {
                drawFrame();
            }
        }

        @Override
        public void onDestroy() {
            super.onDestroy();
            // 清理资源
        }

        private void drawFrame() {
            if (!isVisible) {
                return;
            }

            SurfaceHolder holder = getSurfaceHolder();
            Canvas canvas = null;

            try {
                canvas = holder.lockCanvas();
                if (canvas != null) {
                    // 在这里绘制你的动态内容
                    drawOnCanvas(canvas);
                }
            } finally {
                if (canvas != null) {
                    holder.unlockCanvasAndPost(canvas);
                }
            }

            // 循环绘制下一帧
            handler.removeCallbacks(drawRunner);
            if (isVisible) {
                handler.postDelayed(drawRunner, 16); // 每 16ms 更新一次(约 60fps)
            }
        }

        private final Runnable drawRunner = new Runnable() {
            @Override
            public void run() {
                drawFrame();
            }
        };

        private void drawOnCanvas(Canvas canvas) {
            // 示例:绘制一个简单的渐变色背景
            Paint paint = new Paint();
            LinearGradient gradient = new LinearGradient(
                    0, 0, canvas.getWidth(), canvas.getHeight(),
                    Color.RED, Color.BLUE, Shader.TileMode.CLAMP);
            paint.setShader(gradient);
            canvas.drawRect(0, 0, canvas.getWidth(), canvas.getHeight(), paint);
        }
    }
}
3. 运行和测试
安装应用:
将 APK 安装到设备上。
设置动态壁纸:
打开设备的 设置 > 壁纸 > 选择壁纸。

这里可以看到注册了wallpaperService的程序


找到你的动态壁纸并设置。
查看效果:
返回主屏幕,你应该能看到动态壁纸在运行。

设置动态壁纸的log

04-20 20:39:25.432  2392  3859 W WallpaperManagerService: getWallpaperIdForUser wallpaper = WallpaperData@f4ca951, id: 10, which: 6, file mod: 1745150434978, no connection, type = 0, userId = 0, uri= null, wallpaperComponent = null, nextWallpaperComponent = null
04-20 20:39:34.437  2392  2705 D SemWallpaperManagerService: getModeEnsuredWhich: detected which = 7
04-20 20:39:34.437  2392  2705 V WallpaperManagerService: setWallpaperComponent name=ComponentInfo{com.example.testmac/com.example.testmac.MyWallpaperService} , which = 7 , coverWallpaperInfo = null
04-20 20:39:34.438  2392  2705 D WALLPAPER_SVC:WallpaperManagerService: Wallpaper file is changed or deleted. Delete previous cropped bitmap
04-20 20:39:34.449  2392  2705 D WALLPAPER_SVC:WallpaperManagerService: bindComponentNow 7
04-20 20:39:34.449  2392  2705 V WallpaperManagerService: bindWallpaperComponentLocked: componentName=ComponentInfo{com.example.testmac/com.example.testmac.MyWallpaperService} force=false
04-20 20:39:34.449  2392  2705 D WallpaperManagerService: WPMS.bindWallpaperComponentLocked-ComponentInfo{com.example.testmac/com.example.testmac.MyWallpaperService}
04-20 20:39:34.454  2392  2705 D WALLPAPER_SVC:WallpaperManagerService: bindService success connect : com.android.server.wallpaper.WallpaperManagerService$WallpaperConnection@c843b0d
04-20 20:39:34.454  2392  2392 D WallpaperManagerService: WPMS.onServiceConnected-ComponentInfo{com.example.testmac/com.example.testmac.MyWallpaperService}
04-20 20:39:34.456  2392  2705 V WallpaperManagerService: Removing window token: android.os.Binder@56ab743 , mDisplayId = 0
04-20 20:39:34.459  2392  2705 D WallpaperManagerService: wallpaper userId = 0 , current user Id = 0
04-20 20:39:34.460  2392  2705 V WallpaperManagerService: WPMS.bindWallpaperComponentLocked-ComponentInfo{com.example.testmac/com.example.testmac.MyWallpaperService} took to complete: 11ms
04-20 20:39:34.466  2392  2705 V WallpaperManagerService: notifyWallpaperColorsChangedOnDisplay 1

调用到bindWallpaperComponentLocked

广播

参考资料:

Android动态壁纸实现与WallPaPerService教程-CSDN博客

【安卓笔记】wallpaper设置系统主页墙纸的一种方法_android wallpaperservice-CSDN博客

Android T wallpaper相关流程_android wallpaperservice-CSDN博客

相关文章:

  • 博客系统案例练习2-用户注册-redis
  • 数据库知识
  • 电解电容失效分析过程、失效分析报告
  • 450.删除二叉搜索树中的节点
  • 【Vulkan 入门系列】创建交换链、图像视图和渲染通道(四)
  • 【2025面试常问Java八股】AQS介绍(AbstractQueuedSynchronizer 抽象队列同步器)
  • PyCharm使用Anaconda 中的虚拟环境
  • PowerBi如何制作KPI的总览页?
  • 关于AI:记忆、身份和锁死
  • Function calling LLMs 的 MCP:AI开发的双剑合璧
  • [OpenGL]使用OpenGL实现基于物理的渲染模型PBR(下)
  • 大数据应用开发——大数据平台集群部署(四)
  • 【KWDB 创作者计划】_上位机知识篇---Arduino
  • 什么是 C++中的const?
  • SpringBoot Actuator指标收集:Micrometer与Prometheus集成
  • Matlab 基于模型参考自适应法和SVPWM的异步电机控制
  • `ImadcnIdentifierGenerator` 深度解析
  • Java学习笔记(数组,方法)
  • JavaWeb 课堂笔记 —— 16 MyBatis 动态SQL
  • Img2img-turbo 在2080Ti上的测试笔记
  • 竹笋食用不当,小心“鲜”变“险”
  • “这是本届政府的态度”,英国明确拒绝与中国脱钩
  • 价格周报|本周生猪均价环比上涨,交易均重继续上升
  • 从高铁到住房:“富足议程”能否拯救美国的进步主义?
  • 摩根大通首席执行官:贸易战损害美国信誉
  • 上海警方:男子拍摄女性视频后在网上配发诱导他人违法犯罪文字,被行拘