Android静态壁纸和动态壁纸的使用和理解

这两天在公众号上偶然看到一篇关于设置动态壁纸的文章,觉得蛮有意思的,学习了一下,以此文章记录一下怎样给手机设置静态壁纸和动态壁纸,设置壁纸的使用方法。

静态壁纸

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

  • 5
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
静库和动态库是常用的代码库形式,下面分别介绍它们的使用方法。 静态库(Static Library)是在程序编译时被链接到可执行文件中的代码库。静态库的使用方法如下: 1. 编译静态库:使用编译器(如gcc)将源代码文件编译成目标文件(.o 文件),然后使用静态库打包工具(如ar)将多个目标文件打包成静态库文件(.a 文件)。 2. 使用静态库:在编译可执行文件时,通过编译选项(如 -l 和 -L)指定静态库的路径和名称,编译器会将静态库链接到可执行文件中。例如,使用以下命令编译可执行文件:`gcc main.c -o main -L/path/to/library -lmylib`,其中 `/path/to/library` 是静态库文件所在的路径,`mylib` 是静态库文件的名称。 动态库(Dynamic Library)是在程序运行时被加载到内存中并链接到可执行文件中的代码库。动态库的使用方法如下: 1. 编译动态库:使用编译器将源代码文件编译成目标文件,然后使用动态库打包工具(如gcc)将多个目标文件打包成动态库文件(.so 文件)。 2. 使用动态库:在编译可执行文件时,通过编译选项(如 -l 和 -L)指定动态库的路径和名称,编译器会在程序运行时动态加载并链接动态库。例如,使用以下命令编译可执行文件:`gcc main.c -o main -L/path/to/library -lmylib`,其中 `/path/to/library` 是动态库文件所在的路径,`mylib` 是动态库文件的名称。 需要注意的是,静态库在编译时已经被链接到可执行文件中,因此可执行文件会变得较大;而动态库在程序运行时加载,可执行文件较小,但需要依赖于动态库文件的存在。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值