屏保的一些知识点

Posted 福尔摩聪

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了屏保的一些知识点相关的知识,希望对你有一定的参考价值。

一.屏保的一些配置
1.默认屏保启动时间
frameworks/base/packages/SettingsProvider/res/values/defaults.xml
600000
(正常是配置在这个文件里面的,但是许多项目会在device下面重新写一个defaults.xml文件,类似于overlay,替换掉默认的一些配置,eg:device/msm/common/overlay_screenoff/frameworks/base/packages/SettingsProvider/res/values/defaults.xml)
2.默认屏保类型
frameworks/base/core/res/res/values/config.xml
com.lcc.screensaver.dynamic/com.lcc.screensaver.dynamic.DynamicDream
(和默认的启动时间类似,大部分项目也都会在overlay下重新创建一个config.xml,重写一部分配置。 eg:device/msm/common/tv/overlay/frameworks/base/core/res/res/values/config.xml)

二.设置是如何设置屏保相关的
1.获取当前设备内所有类型的屏保
final List<DreamBackend.DreamInfo> infos = mBackend.getDreamInfos();

    public List<DreamInfo> getDreamInfos() 
        logd("getDreamInfos()");
        ComponentName activeDream = getActiveDream();
        PackageManager pm = mContext.getPackageManager();
        Intent dreamIntent = new Intent(DreamService.SERVICE_INTERFACE);
        List<ResolveInfo> resolveInfos = pm.queryIntentServices(dreamIntent,
                PackageManager.GET_META_DATA);
        List<DreamInfo> dreamInfos = new ArrayList<>(resolveInfos.size());
        for (ResolveInfo resolveInfo : resolveInfos) 
            if (resolveInfo.serviceInfo == null)
                continue;
            DreamInfo dreamInfo = new DreamInfo();
            dreamInfo.caption = resolveInfo.loadLabel(pm);
            dreamInfo.icon = resolveInfo.loadIcon(pm);
            dreamInfo.componentName = getDreamComponentName(resolveInfo);
            dreamInfo.isActive = dreamInfo.componentName.equals(activeDream);
            dreamInfo.settingsComponentName = getSettingsComponentName(pm, resolveInfo);
            dreamInfos.add(dreamInfo);
        
        Collections.sort(dreamInfos, mComparator);
        return dreamInfos;
    

从源码里面可以看出,通过PMS的queryIntentServices方法,获取所有满足action为 "android.service.dreams.DreamService"的resolveInfos 。之后再解析成对应的DreamInfo 。

2.打开对应屏保的预览界面(也可以说是设置界面)

    private void setActiveDream(String componentNameString) 
        Log.i(TAG, "setActiveDream: componentNameString");
        final DreamBackend.DreamInfo dreamInfo = mDreamInfos.get(componentNameString);
        if (dreamInfo != null) 
            if (dreamInfo.settingsComponentName != null) 
                startActivity(new Intent().setComponent(dreamInfo.settingsComponentName));
            
            if (!mBackend.isEnabled()) 
                mBackend.setEnabled(true);
            
            // if (!Objects.equals(mBackend.getActiveDream(), dreamInfo.componentName)) 
            //     mBackend.setActiveDream(dreamInfo.componentName);
            // 
         else 
            if (mBackend.isEnabled()) 
                mBackend.setActiveDream(null);
                mBackend.setEnabled(false);
            
        
    

这里的关键是dreamInfo.settingsComponentName。(后面会有讲)
3.设置屏保启动时间

String SCREEN_OFF_TIMEOUT = “system screen_off_timeout”
private void setDreamTime(int ms) 
Settings.System.putInt(getActivity().getContentResolver(), 	SCREEN_OFF_TIMEOUT, ms);

//设置成永不启动,按道理说应该是将屏保关闭掉:
Settings.Secure.putInt(mContext.getContentResolver(), 0);
但是原生设置里面并不是这么做的,而是将屏保启动时间设置成最大:2147483647
这么做的原因暂时未知。

三.屏保应用该如何做
1.AndroidManifest文件配置

<service android:name=".DynamicDream"
            android:exported="true"
            android:permission="android.permission.BIND_DREAM_SERVICE"
            android:label="@string/app_name">
            <meta-data
                android:name="android.service.dream"
                android:resource="@xml/dynamic_dream"/>
            <intent-filter>
                <action android:name="android.service.dreams.DreamService" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </service>

①android:permission=“android.permission.BIND_DREAM_SERVICE”
这个是必须的,在DreamManagerService里面 会有一个如下判断:

    private boolean validateDream(ComponentName component) 
        if (component == null) return false;
        final ServiceInfo serviceInfo = getServiceInfo(component);
        if (serviceInfo == null) 
            Slog.w(TAG, "Dream " + component + " does not exist");
            return false;
         else if (serviceInfo.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.LOLLIPOP
                && !BIND_DREAM_SERVICE.equals(serviceInfo.permission)) 
            Slog.w(TAG, "Dream " + component
                    + " is not available because its manifest is missing the " + BIND_DREAM_SERVICE
                    + " permission on the dream service declaration.");
            return false;
        
        return true;
    

在获取本设备上所有的屏保应用时,会加上如上判断。如果该类型的屏保没有这个权限,则会被判定成无效屏保,不会被添加到屏保列表里面。

这个是必须的,只有加上这个action,才会被搜索到。

public static final String SERVICE_INTERFACE =
"android.service.dreams.DreamService";

public List<DreamInfo> getDreamInfos() 
        logd("getDreamInfos()");
        ComponentName activeDream = getActiveDream();
        PackageManager pm = mContext.getPackageManager();
        Intent dreamIntent = new Intent(DreamService.SERVICE_INTERFACE);
        List<ResolveInfo> resolveInfos = pm.queryIntentServices(dreamIntent,
                PackageManager.GET_META_DATA);
        List<DreamInfo> dreamInfos = new ArrayList<>(resolveInfos.size());
        for (ResolveInfo resolveInfo : resolveInfos) 
            if (resolveInfo.serviceInfo == null)
                continue;
            DreamInfo dreamInfo = new DreamInfo();
            dreamInfo.caption = resolveInfo.loadLabel(pm);
            dreamInfo.icon = resolveInfo.loadIcon(pm);
            dreamInfo.componentName = getDreamComponentName(resolveInfo);
            dreamInfo.isActive = dreamInfo.componentName.equals(activeDream);
            dreamInfo.settingsComponentName = getSettingsComponentName(pm, resolveInfo);
            dreamInfos.add(dreamInfo);
        
        Collections.sort(dreamInfos, mComparator);
        return dreamInfos;
    

③ < meta-data >的设置
这个是预览界面相关的。
dynamic_dream.xml:

<?xml version="1.0" encoding="utf-8"?>
<dream xmlns:android="http://schemas.android.com/apk/res/android"
android:settingsActivity="com.lcc.screensaver.dynamic/.DynamicDreamSettings" />

google建议通过上述方法绑定屏保和屏保预览界面:

这种配置在后续跳转到预览界面时会起作用:
设置是如何跳转到预览界面呢?

private void setActiveDream(String componentNameString) 
        Log.i(TAG, "setActiveDream: componentNameString");
        final DreamBackend.DreamInfo dreamInfo = mDreamInfos.get(componentNameString);
        if (dreamInfo != null) 
            if (dreamInfo.settingsComponentName != null) 
                startActivity(new Intent().setComponent(dreamInfo.settingsComponentName));
            
            if (!mBackend.isEnabled()) 
                mBackend.setEnabled(true);
            
            // if (!Objects.equals(mBackend.getActiveDream(), dreamInfo.componentName)) 
            //     mBackend.setActiveDream(dreamInfo.componentName);
            // 
         else 
            if (mBackend.isEnabled()) 
                mBackend.setActiveDream(null);
                mBackend.setEnabled(false);
            
        
    

那么这个settingsComponentName是怎么来的呢?
可以从获取所有屏保的函数里面看到这个:
dreamInfo.settingsComponentName = getSettingsComponentName(pm, resolveInfo);

 private static ComponentName getSettingsComponentName(PackageManager pm, ResolveInfo resolveInfo) 
        if (resolveInfo == null
                || resolveInfo.serviceInfo == null
                || resolveInfo.serviceInfo.metaData == null)
            return null;
        String cn = null;
        XmlResourceParser parser = null;
        Exception caughtException = null;
        try 
            parser = resolveInfo.serviceInfo.loadXmlMetaData(pm, DreamService.DREAM_META_DATA);
            if (parser == null) 
                Log.w(TAG, "No " + DreamService.DREAM_META_DATA + " meta-data");
                return null;
            
            Resources res = pm.getResourcesForApplication(resolveInfo.serviceInfo.applicationInfo);
            AttributeSet attrs = Xml.asAttributeSet(parser);
            int type;
            while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
                    && type != XmlPullParser.START_TAG) 
            
            String nodeName = parser.getName();
            if (!"dream".equals(nodeName)) 
                Log.w(TAG, "Meta-data does not start with dream tag");
                return null;
            
            TypedArray sa = res.obtainAttributes(attrs, com.android.internal.R.styleable.Dream);
            cn = sa.getString(com.android.internal.R.styleable.Dream_settingsActivity);
            sa.recycle();
         catch (PackageManager.NameNotFoundException|IOException|XmlPullParserException e) 
            caughtException = e;
         finally 
            if (parser != null) parser.close();
        
        if (caughtException != null) 
            Log.w(TAG, "Error parsing : " + resolveInfo.serviceInfo.packageName, caughtException);
            return null;
        
        if (cn != null && cn.indexOf('/') < 0) 
            cn = resolveInfo.serviceInfo.packageName + "/" + cn;
        
        return cn == null ? null : ComponentName.unflattenFromString(cn);
    

从上述函数可以看到,获取屏保的预览界面的ComponentName时,就是通过解析AndroidManifest文件里面的service的属性。其中DREAM_META_DATA = “android.service.dream”;
所以按照google建议的流程做,就是每个屏保service都绑定一个预览界面。这样做的好处就是屏保应用和设置不需要再相互传递消息。

2.继承DreamService

重写onAttachedToWindow(),将屏保界面展示出来。其中有一点需要注意,setInteractive(false)。这个函数里面的参数是需要注意的。
false:按下任何按键都会退出屏保
true:按下返回键退出屏保。
设置成true的好处就是,可以自定义其他按键事件。比如说设置成true之后,我可以按左右键的时候切换屏保资源,按其他键再退出。
原理如下:

@Override
    public boolean dispatchKeyEvent(KeyEvent event) 
        // TODO: create more flexible version of mInteractive that allows use of KEYCODE_BACK
        if (!mInteractive) 
            if (mDebug) Slog.v(TAG, "Waking up on keyEvent");
            wakeUp();
            return true;
         else if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) 
            if (mDebug) Slog.v(TAG, "Waking up on back key");
            wakeUp();
            return true;
        
        return mWindow.superDispatchKeyEvent(event);
    

3.将本屏保设置成当前屏保
这个建议在屏保应用本身做,这个是咱们的需求决定的。之前原生的几种屏保,都是只要进入预览界面,就会将屏保设置成系统当前屏保,所以这是可以在设置里面做的。但是咱们这边的需求不太一样,咱们是要先进入到预览界面,点击设成屏保之后才能设置成当前屏保。如果点击返回,那么屏保类型不变。所以建议在屏保应用本身做.

	private final IDreamManager mDreamManager;
	mDreamManager = IDreamManager.Stub.asInterface(
	ServiceManager.getService(DreamService.DREAM_SERVICE));
    public void setActiveDream(ComponentName dream) 
        logd("setActiveDream(%s)", dream);
        if (mDreamManager == null)
            return;
        try 
            ComponentName[] dreams =  dream ;
            mDreamManager.setDreamComponents(dream == null ? null : dreams);
         catch (RemoteException e) 
            Log.w(TAG, "Failed to set active dream to " + dream, e);
        
    

四:FWK如何做保护
按照android系统原生的流程,启动屏保失败,比如说屏保应用不存在或者bindservice失败,都会进入到休眠状态。而进入休眠是我们不想看到的,所以我们要在fwk层做一层保护,当启动屏保失败的时候,我们执行一次点亮屏幕的操作,然后重新开始计时(其实就是申请一下wakelock锁,然后再释放一下)。
具体的实现在code/frameworks/base/services/core/java/com/android/server/dreams/DreamController.java文件里面:
首先先实现点亮的函数:

import android.os.PowerManager;

private final PowerManager mPowerManager;
mPowerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE);

 private void acquireWakeLock()
        Slog.w(TAG, "acquireWakeLock to avoid going to sleep due to screensaver has preblems");
        if(mPowerManager != null)
            PowerManager.WakeLock wl = mPowerManager.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK,
                    "znds tag");
            wl.acquire();
            wl.release();
        
    

然后再在出意外的地方调用点亮函数:

    private final Runnable mStopUnconnectedDreamRunnable = new Runnable() 
        @Override
        public void run() 
            if (mCurrentDream != null && mCurrentDream.mBound && !mCurrentDream.mConnected) 
                Slog.w(TAG, "Bound dream did not connect in the time allotted");
                //stopDream(true /*immediate*/);
                acquireWakeLock();
            
        
    ;

public void startDream(Binder token, ComponentName name,
            boolean isTest, boolean canDoze, int userId, PowerManager.WakeLock wakeLock) 
                           boolean isTest, boolean canDoze, int userId, PowerManager.WakeLock wakeLock) 
        stopDream(true /*immediate*/);

        Trace.traceBegin(Trace.TRACE_TAG_POWER, "startDream");
                mIWindowManager.addWindowToken(token, TYPE_DREAM, DEFAULT_DISPLAY);
             catch (RemoteException ex) 
                Slog.e(TAG, "Unable to add window token for dream.", ex);
                //stopDream(true /*immediate*/);
                acquireWakeLock();
                return;
            



五.去掉fwk里面原有的屏保启动机制
原来屏保的启动机制是这样的:

	在PhoneWindowManager.java里面,每次当有新的按键事件的时候,会开启一个定时器,定时器的时间就是设置里面设置的屏保启动时间。如果在这期间有其他按键事件,那么会重新开始计时。 到启动时间之后,会判断当前顶部的activity是否是通话啥的,然后再决定是否拉起屏保。如果决定要拉,那么会通过startActivity拉起来屏保。
 现在要在去掉这个机制:
 ![在这里插入图片描述](ht

以上是关于屏保的一些知识点的主要内容,如果未能解决你的问题,请参考以下文章

屏保的一些知识点

屏保的一些知识点

AndroidTV如何自定义屏保更改屏保时长

怎么取消电脑屏保和睡眠。

Linux终端界面屏保

优效时钟屏保-一款极简风格的时钟屏保