按一般思维理解手机锁屏了,手机窗体应该是处于非活动状态的。而视图的显示 必须依赖活动的窗体。然而在使用市面应用的时候,很多应用却都可以再锁屏界面上活动自己的视图------比如QQ在锁屏时有消息到达,可以弹出对话框;再比如音乐应用在锁屏界面上有自己的歌词等等。
一开始觉得应该就是一个dialog/或者window上再加一个view。
我的猜测是对得,系统中确实有一个属性setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);然而可惜的是,外部应用似乎用不了该属性, 一直提示没有对应的权限。在翻阅WindowManager的其他属性时,当我看到了这个属性时简直是喜出望外WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED。一般我们知道FLAG的标记可以在activity窗体上使用。换句话说,如果给某个activity加上WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED,似乎看起来可以在锁屏时正常打开。经过试验确实可行,以下简单记录之。
最终效果:
1 创建在锁屏时能打开的activity
最终的实现很是简单,只需要在activity的onCreate里面简单增加几个属性即可。
//MainActivity.java
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 );
罗列下WindowManager.LayoutParams的一些属性值,方便查阅
应用程序窗口。
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
;
系统窗口结束。
2 获取手机当前壁纸public
static
final
int
LAST_SYSTEM_WINDOW =
2999
;
我们知道QQ在锁屏弹窗的时候其背景跟我们的壁纸是一致的,这里准确说来是和桌面上的壁纸一致。
这里就需要用到壁纸管理器:
//MainActivity.java
获取到的壁纸直接加载到当前的activity上,作为其背景图片:public Bitmap getWallPaper() { WallpaperManager wallpaperManager = WallpaperManager .getInstance(MainActivity.this); // 获取当前壁纸 Drawable wallpaperDrawable = wallpaperManager.getDrawable(); Bitmap bm = ((BitmapDrawable) wallpaperDrawable).getBitmap(); int heightPixels = getResources().getDisplayMetrics().heightPixels; int widthPixels = getResources().getDisplayMetrics().widthPixels; int with = bm.getHeight()*widthPixels/heightPixels > bm.getWidth() ? bm.getWidth():bm.getHeight()*widthPixels/heightPixels; Bitmap pbm = Bitmap.createBitmap(bm, 0, 0, with, bm.getHeight()); // 设置 背景 return pbm; }
//MainActivity.java
@Override public void onAttachedToWindow() { super.onAttachedToWindow(); if (true) { final View view = getWindow().getDecorView(); final WindowManager.LayoutParams lp = (WindowManager.LayoutParams) view.getLayoutParams(); lp.gravity = Gravity.CENTER;
// 可以通过布局属性来控制activity的大小,现在市面上的一些半截activity(特别是一些支付页面等)都可以通过在这里调节属性来实现 // lp.width = getResources().getDisplayMetrics().widthPixels / 2; // lp.height = getResources().getDisplayMetrics().heightPixels / 3; if (Build.VERSION.SDK_INT >= 16) { view.setBackground(new BitmapDrawable(getWallPaper())); } else { view.setBackgroundColor(Color.WHITE); } getWindowManager().updateViewLayout(view, lp); } }
3 监听屏幕状态/锁屏/解锁/亮屏
这里使用到了系统广播,然而有一个很坑爹的地方 配置静态广播的时候锁屏/亮屏广播无法生效只有用户手动解锁广播可以生效,原因一直没搞懂。因此采用了在广播中注册的方式//FloatService.java
@Override public int onStartCommand(Intent intent, int flags, int startId) { Log.d("ndh--","onStartCommand"); /* 注册屏幕唤醒时的广播 */ IntentFilter intentenfilter = new IntentFilter(); intentenfilter.addAction(Intent.ACTION_SCREEN_ON); intentenfilter.addAction(Intent.ACTION_SCREEN_OFF); intentenfilter.addAction(Intent.ACTION_USER_PRESENT); receiver = new ScreenStateReceiver(); registerReceiver(receiver, intentenfilter); return super.onStartCommand(intent, flags, startId); }
// ScreenStateReceiver.java@Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); Log.d("ndh--", "action=" + action); if (Intent.ACTION_SCREEN_ON.equals(action)) { // 开屏 ScreenStateManager.screenState=1; Log.d("ndh--", "screen on"); } else if (Intent.ACTION_SCREEN_OFF.equals(action)) { // 锁屏 ScreenStateManager.screenState=-1; Log.d("ndh--", "screen off"); Intent intent1 = new Intent(context, MainActivity.class); intent1.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(intent1); } else if (Intent.ACTION_USER_PRESENT.equals(action)) { // 解锁 ScreenStateManager.screenState=0; Log.d("ndh--", "user_present"); } } }
PS 可能真正的场景是,有一条通知到来,然后我们判断一下当前的屏幕活动状态,如果是亮屏,挂载普通界面,如果是锁屏则挂载特制的锁屏Activity界面。这里判断屏幕是否处于活动状态
除了这里的广播外,系统的PowerManager同样可以使用:
//ScreenStateManager.java
//判断当时屏幕是否处于打开状态
public boolean isScreenOn(Context context) {
PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
if (pm.isScreenOn()) {
return true;
}
return false;
}
事例代码地址:https://github.com/killer8000/popup-window-on-lock-screen