首页 > 代码库 > Android下屏幕锁屏弹窗的正确姿势

Android下屏幕锁屏弹窗的正确姿势

最近在做一个关于屏幕锁屏悬浮窗的功能,于是在网上搜索了很多安卓屏幕锁屏的相关资料,鉴于网上的资料比较零碎,所以我在这里进行整理总结。本文将从以下两点对屏幕锁屏进行解析:
1. 如何监听系统屏幕锁屏
2. 如何在锁屏界面弹出悬浮窗

如何监听系统屏幕锁屏

经过总结,监听系统的锁屏可以通过以下两种方式:
1) 代码直接判定
2) 接收广播

1) 代码直接判定
代码判断方式,也有两种方法:
a) 通过PowerManager的isScreenOn方法,代码如下:

PowerManager pm = (PowerManager) 
context.getSystemService(Context.POWER_SERVICE);
//如果为true,则表示屏幕“亮”了,否则屏幕“暗”了。
boolean isScreenOn = pm.isScreenOn();

这里需要解释一下:
屏幕“亮”,表示有两种状态:a、未锁屏 b、目前正处于解锁状态 。这两种状态屏幕都是亮的;
屏幕“暗”,表示目前屏幕是黑的 。

b) 通过KeyguardManager的inKeyguardRestrictedInputMode方法,代码如下:

KeyguardManager mKeyguardManager = (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE);
boolean flag = mKeyguardManager.inKeyguardRestrictedInputMode();

对flag进行一下说明,经过试验,总结为:
如果flag为true,表示有两种状态:a、屏幕是黑的 b、目前正处于锁屏状态 。
如果flag为false,表示目前未锁屏
注明:上面的两种方法,也可以通过反射机制来调用。
反射代码如下:

private static Method mReflectScreenState;
try {
    mReflectScreenState = PowerManager.class.getMethod(isScreenOn, new Class[] {});
    PowerManager pm = (PowerManager) context.getSystemService(Activity.POWER_SERVICE);
    boolean isScreenOn= (Boolean) mReflectScreenState.invoke(pm);
} catch (Exception e) {
    e.printStackTrace()
}

2) 接收广播
当安卓系统锁屏或者屏幕亮起,或是屏幕解锁的时候,系统内部都会发送相应的广播,我们只需要对广播进行监听就可以了
注册广播的伪代码如下:

private ScreenBroadcastReceiver mScreenReceiver;
private class ScreenBroadcastReceiver extends BroadcastReceiver {
    private String action = null;


    @Override
    public void onReceive(Context context, Intent intent) {
        action = intent.getAction();
        if (Intent.ACTION_SCREEN_ON.equals(action)) {           
            // 开屏
        } else if (Intent.ACTION_SCREEN_OFF.equals(action)) { 
            // 锁屏
        } else if (Intent.ACTION_USER_PRESENT.equals(action)) { 
            // 解锁
        }
    }
}
private void startScreenBroadcastReceiver() {
    IntentFilter filter = new IntentFilter();
    filter.addAction(Intent.ACTION_SCREEN_ON);
    filter.addAction(Intent.ACTION_SCREEN_OFF);
    filter.addAction(Intent.ACTION_USER_PRESENT);
    context.registerReceiver(mScreenReceiver, filter);
}

如何在锁屏界面弹出悬浮窗

竟然知道了对于系统屏幕监听的方法,那么接下来就是要在屏幕锁屏的时候,弹出悬浮框了,这个的实现方式有两种:
1) 使用WindowManager
2) 使用Activity
目前情况是,使用这两种方式在真机上都可以实现,如果网友们发现有问题,可以在博客中留言

1) 使用WindowManager
代码如下:

 private void init(Context mContext) {
            this.mContext = mContext;

            mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
            // 更新浮动窗口位置参数 靠边
            DisplayMetrics dm = new DisplayMetrics();
            // 获取屏幕信息
            mWindowManager.getDefaultDisplay().getMetrics(dm);
            mScreenWidth = dm.widthPixels;
            mScreenHeight = dm.heightPixels;
            this.mWmParams = new WindowManager.LayoutParams();
            // 设置window type
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
                mWmParams.type = WindowManager.LayoutParams.TYPE_TOAST;
            } else {
                mWmParams.type = WindowManager.LayoutParams.TYPE_PHONE;
            }
            // 设置图片格式,效果为背景透明
            mWmParams.format = PixelFormat.RGBA_8888;
            // 设置浮动窗口不可聚焦(实现操作除浮动窗口外的其他可见窗口的操作)
            mWmParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
            // 调整悬浮窗显示的停靠位置为左侧置??
            mWmParams.gravity = Gravity.LEFT | Gravity.TOP;

            mScreenHeight = mWindowManager.getDefaultDisplay().getHeight();

            // 以屏幕左上角为原点,设置xy初始值,相对于gravity
            mWmParams.x = 0;
            mWmParams.y = mScreenHeight / 2;

            // 设置悬浮窗口长宽数据
            mWmParams.width = LayoutParams.WRAP_CONTENT;
            mWmParams.height = LayoutParams.WRAP_CONTENT;
            addView(createView(mContext));
            mWindowManager.addView(this, mWmParams);

            mTimer = new Timer();
            hide();
        }

WindowManager的主要配置就是上面的那些代码,这里需要说明一下,type的类型有如下值:

 应用程序窗口。
      public static final int FIRST_APPLICATION_WINDOW = 1;    

      所有程序窗口的“基地”窗口,其他应用程序窗口都显示在它上面。     
      public static final int TYPE_BASE_APPLICATION   =1;

      普通应用功能程序窗口。token必须设置为Activity的token,以指出该窗口属谁。
      public static final int TYPE_APPLICATION       = 2;

       用于应用程序启动时所显示的窗口。应用本身不要使用这种类型。
      它用于让系统显示些信息,直到应用程序可以开启自己的窗口。   
      public static final int TYPE_APPLICATION_STARTING = 3; 

      应用程序窗口结束。
      public static final int LAST_APPLICATION_WINDOW = 99;

      子窗口。子窗口的Z序和坐标空间都依赖于他们的宿主窗口。
      public static final int FIRST_SUB_WINDOW       = 1000;

      面板窗口,显示于宿主窗口上层。
      public static final int TYPE_APPLICATION_PANEL  = FIRST_SUB_WINDOW;

      媒体窗口,例如视频。显示于宿主窗口下层。
      public static final int TYPE_APPLICATION_MEDIA  = FIRST_SUB_WINDOW+1;

      应用程序窗口的子面板。显示于所有面板窗口的上层。(GUI的一般规律,越“子”越靠上)
      public static final int TYPE_APPLICATION_SUB_PANEL = FIRST_SUB_WINDOW +2;

      对话框。类似于面板窗口,绘制类似于顶层窗口,而不是宿主的子窗口。
      public static final int TYPE_APPLICATION_ATTACHED_DIALOG = FIRST_SUB_WINDOW +3;

      媒体信息。显示在媒体层和程序窗口之间,需要实现透明(半透明)效果。(例如显示字幕)
      public static final int TYPE_APPLICATION_MEDIA_OVERLAY  = FIRST_SUB_WINDOW +4;

      子窗口结束。( End of types of sub-windows )
      public static final int LAST_SUB_WINDOW        = 1999;

      系统窗口。非应用程序创建。
      public static final int FIRST_SYSTEM_WINDOW    = 2000;

      状态栏。只能有一个状态栏;它位于屏幕顶端,其他窗口都位于它下方。
      public static final int TYPE_STATUS_BAR        =  FIRST_SYSTEM_WINDOW;

      搜索栏。只能有一个搜索栏;它位于屏幕上方。
      public static final int TYPE_SEARCH_BAR        = FIRST_SYSTEM_WINDOW+1;

      电话窗口。它用于电话交互(特别是呼入)。它置于所有应用程序之上,状态栏之下。
      public static final int TYPE_PHONE            = FIRST_SYSTEM_WINDOW+2;

      系统提示。它总是出现在应用程序窗口之上。
      public static final int TYPE_SYSTEM_ALERT      =  FIRST_SYSTEM_WINDOW +3;

      锁屏窗口。
      public static final int TYPE_KEYGUARD          = FIRST_SYSTEM_WINDOW +4;

      信息窗口。用于显示toast。
      public static final int TYPE_TOAST            = FIRST_SYSTEM_WINDOW +5;

      系统顶层窗口。显示在其他一切内容之上。此窗口不能获得输入焦点,否则影响锁屏。
      public static final int TYPE_SYSTEM_OVERLAY    =  FIRST_SYSTEM_WINDOW +6;

      电话优先,当锁屏时显示。此窗口不能获得输入焦点,否则影响锁屏。
      public static final int TYPE_PRIORITY_PHONE    =  FIRST_SYSTEM_WINDOW +7;

      系统对话框。(例如音量调节框)。
      public static final int TYPE_SYSTEM_DIALOG     =  FIRST_SYSTEM_WINDOW +8;

      锁屏时显示的对话框。
      public static final int TYPE_KEYGUARD_DIALOG   =  FIRST_SYSTEM_WINDOW +9;

      系统内部错误提示,显示于所有内容之上。
      public static final int TYPE_SYSTEM_ERROR      =  FIRST_SYSTEM_WINDOW +10;

      内部输入法窗口,显示于普通UI之上。应用程序可重新布局以免被此窗口覆盖。
      public static final int TYPE_INPUT_METHOD      =  FIRST_SYSTEM_WINDOW +11;

      内部输入法对话框,显示于当前输入法窗口之上。
      public static final int TYPE_INPUT_METHOD_DIALOG= FIRST_SYSTEM_WINDOW +12;

      墙纸窗口。
      public static final int TYPE_WALLPAPER         = FIRST_SYSTEM_WINDOW +13;

      状态栏的滑动面板。
      public static final int TYPE_STATUS_BAR_PANEL   = FIRST_SYSTEM_WINDOW +14;

      系统窗口结束。
      public static final int LAST_SYSTEM_WINDOW     = 2999;

如果想让悬浮窗在所以锁屏之上,使用TYPE_SYSTEM_ERROR,因为它显示在所有内容之上。

2) 使用Activity

Activity的设置

Activity需要进行以下设置,才可以在锁屏状态下弹窗。
首先是onCreate方法,需要添加4个标志,如下:

protected void onCreate(Bundle savedInstanceState) {  
    super.onCreate(savedInstanceState);  
    final Window win = getWindow();  
    win.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED  
            | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD  
            | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON  
            | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);  

    // 自己的代码  
}  

四个标志位顾名思义,分别是锁屏状态下显示,解锁,保持屏幕长亮,打开屏幕。这样当Activity启动的时候,它会解锁并亮屏显示。
然后在AndroidManifest.xml文件当中,对该activity的声明需要加上以下属性:

<activity android:name=".alarm.AlarmHandlerActivity"  
          android:launchMode="singleInstance"  
          android:excludeFromRecents="true"  
          android:taskAffinity=""  
          android:theme="@android:style/Theme.Wallpaper.NoTitleBar"/>  

而对于布局文件,要显示的view居中,背景透明。由于上面已经设置了背景为壁纸的背景,所以显示的是桌面的背景。如果背景设为默认的白色,则导致弹窗后面是一片白色,看起来很丑。如果背景设置为透明,则弹窗后面会显示出解锁后的界面(即使有锁屏密码,也是会显示解锁后的界面的),一样很影响视觉效果。

在广播中启动锁屏弹窗

我们设置的是锁屏下才弹窗的,非锁屏下就不适合弹出这个窗口了(你可以试一下,效果会很怪)。一般是注册一个广播接收器,在接收到指定广播之后判断是否需要弹窗,所以在BroadcastReceiver的接收代码中需要先判断是否为锁屏状态下:

 @Override  
    public void onReceive(Context context, Intent intent) {  
        Log.d(LOG_TAG, intent.getAction());  
        KeyguardManager km = (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE);  
        if (km.inKeyguardRestrictedInputMode()) {  
            Intent alarmIntent = new Intent(context, AlarmActivity.class);  
            alarmIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);  
            context.startActivity(alarmIntent);  
        }  
    }  

这里用到的是KeyguardManager类,用来管理锁屏的,4.1之后该类的API新增了一个isKeyguardLocked()的方法判断是否锁屏,但在4.1之前,我们只能用inKeyguardRestrictedInputMode()方法,如果为true,即为锁屏状态。需要注意的是,在广播中启动Activity的context可能不是Activity对象,所以需要添加NEW_TASK的标志,否则启动时可能会报错。我们就可以结合之前的系统发送广播后进行相应的悬浮窗的弹出处理。

复写onNewIntent方法

再次亮起屏幕,如果该Activity并未退出,但是被手动按了锁屏键,当前面的广播接收器再次去启动它的时候,屏幕并不会被唤起,所以我们需要在activity当中添加唤醒屏幕的代码,这里用的是电源锁。可以添加在onNewIntent(Intent intent),因为它会被调用。也可以添加在其他合适的生命周期方法。添加代码如下:

PowerManager pm = (PowerManager) this.getSystemService(Context.POWER_SERVICE);  
if (!pm.isScreenOn()) {  
    PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.ACQUIRE_CAUSES_WAKEUP |  
            PowerManager.SCREEN_BRIGHT_WAKE_LOCK, "bright");  
    wl.acquire();  
    wl.release();  
}  

最后,是添加如下权限

<uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>  
<uses-permission android:name="android.permission.WAKE_LOCK"/>  

第一条是解锁屏幕需要的,第二条是申请电源锁需要的。

屏幕锁屏的弹窗就总结到这,如果有什么不同的观点,欢迎在博客进行留言
参考博客:http://blog.csdn.net/maosidiaoxian/article/details/40587935

<script type="text/javascript"> $(function () { $(‘pre.prettyprint code‘).each(function () { var lines = $(this).text().split(‘\n‘).length; var $numbering = $(‘
    ‘).addClass(‘pre-numbering‘).hide(); $(this).addClass(‘has-numbering‘).parent().append($numbering); for (i = 1; i <= lines; i++) { $numbering.append($(‘
  • ‘).text(i)); }; $numbering.fadeIn(1700); }); }); </script>

    Android下屏幕锁屏弹窗的正确姿势