Android-锁屏的应用

来源:互联网 发布:js滑动到底部加载更多 编辑:程序博客网 时间:2024/05/16 05:33

锁屏效果图:

     


记录一个锁屏应用的实现

 

二月底回到公司实习,开始做锁屏应用的小项目,耗时大概一周多,期间遇到挺多问题的。分享出来,希望同样在迷茫的人能找到解决方法。

 

整个小项目分为:锁屏界面的实现和锁屏的应用,锁屏界面的实现其实不难,可以看代码,这里主要介绍如何应用锁屏, 因为自己在实现的过程中,也是这一部分遇到的问题最多。


锁屏的应用难点

(1) 锁住Home键,这是比较有难点的地方

 

预期效果: 锁屏界面出现的时候,除非成功解锁,否则无论如何点击Home键,锁屏界面都不会消失

 

无效方案: 4.0之前,可以通过以下方法屏蔽Home键,但是4.0之后,Google官方为了安全性考虑,把Home键的响应放到的框架层(Framework)。因此,按下Home键,会直接跳到主页。

 

          A.重写以下两个方法

@Overridepublic boolean onKeyDown(int keyCode, KeyEvent event) {    if(KeyEvent.KEYCODE_HOME==keyCode){        return true;    }    return super.onKeyDown(keyCode, event);}@Overridepublic void onAttachedToWindow(){    this.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD);    super.onAttachedToWindow();} 

          B.加入对应权限

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

 

可用方案: 设置一个activity作为Home页,方法如下:


          既然无法屏蔽Home键,那我们只能顺着它的意图跳到主页,不过,可以设置我们自己的“主页”。这里的主页不是系统默认的主页,我们可以在工程里增加一个

Activity,作为用来代替系统主页的“主页”,当按下Home键,自然会跳到我们定义的“主页”,这时候,我们可以在我们的“主页” 上操作了,可以在这里重启维持我们的锁屏界面。


          但是,由于Activity的跳转会闪动的效果,导致用户体验不佳,因此这个方案也是不可取的。

 

         A.创建一个Activity并且设置属性,将其设置为主页

            <action android:name="android.intent.action.MAIN" />

            <category android:name="android.intent.category.HOME" /> 

            <category android:name="android.intent.category.DEFAULT" />

         B.设置该Activity的主题为无界面

         C.在我们设置的“主页”中操作,可以通过startActivity()的方式重启锁屏界面,可以维持锁屏界面存在。

            有网友说通过moveTaskToBack(false)可以让“主页”Activity自然撤到后台,但是亲测似乎没效果。

 

 

最佳方案: 把锁屏页改成悬浮窗,当屏幕点亮,全屏显示悬浮窗


    悬浮框主要是通过WindowManager中的addView,updateView,removeView实现

    WindowManager.LayoutParams这个类用于提供悬浮窗所需的参数

    WindowManager.LayoutParams参数说明:

 

    type 用于确定悬浮窗的类型(window类型,window有三种类型,应用window,子window,系统window,其中悬浮窗中使用的是系统window)

          TYPE_PHONE,表示在所有应用程序之上,状态栏之下

          TYPE_STATUS_BAR(状态栏)

          TYPE_SEARCH_BAR(搜索框)

          TYPE_SYSTEM_ALERT(系统提示框,例如电量很低时提示)等等

          根据需求去选择 flags 用于确定悬浮窗的行为,我们这里选择的是TYPE_SYSTEM_ALERT类型。

 

 

          flag 用于设置悬浮窗的属性

 

          FLAG_NOT_FOCUSABLE(window不需要获得焦点,也不需要接收各种输入事件)

          FLAG_NOT_TOUCHABLE(不可点击)

          FLAG_NOT_TOUCH_MODAL(系统会通过当前window区域以外的单击事件传递给底层的window,当前window区域以内的单击事件则自己处理)

          FLAG_SHOW_WHEN_LOCKED(显示在锁屏的界面上)等等 

 

          gravity 用于确定悬浮窗的对齐方式 

          x 用于确定悬浮窗的横坐标 

          y 用于确定悬浮窗的纵坐标 

 

          width 值用于指定悬浮窗的宽度 

          height 值用于指定悬浮窗的高度


接下来看实现:


        A. xml中添加悬浮窗的权限

          <!-- 悬浮窗 -->

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

 

       B. 屏幕点亮时,开启锁屏悬浮窗


RootView mRootView = new RootView(getApplicationContext());        WindowManager.LayoutParams param = new WindowManager.LayoutParams();        param.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;        if(Config.sIsFull) param.flags = WindowManager.LayoutParams.FLAG_FULLSCREEN |        WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;        param.format = PixelFormat.RGBA_8888;        param.width = WindowManager.LayoutParams.MATCH_PARENT;        param.height = WindowManager.LayoutParams.MATCH_PARENT;        mWindowManager.addView(mRootView, param);


         C. 现在的华为魅族等手机,应用安装之后都是默认关闭了悬浮窗的权限,因此,如果是这类型的手机,我们还需要手动去开启悬浮窗的权限。



   

(2)锁住Back键

 

    相比Home键,这是比较好实现的,Back键比较容易屏蔽掉,重写onKeyDown或onBackPressed方法即可,几乎在所有Android版本上都是可以用的。

 

(3)监听屏幕点亮

 

    系统在按下电源键关闭屏幕或点亮屏幕时会发出相应的广播,如Intent.ACTION_SCREEN_OFF和Intent.ACTION_SCREEN_ON,我们可以注册一个BroadcastReceiver来接收这些广播

。这里我们采取动态注册的方式,建立一个常驻内存的Service,在开启时动态注册一个BroadcastReceiver来监听,当销毁时,移除这个BroadcastReceiver。

    

    当接受到广播,我们需要先屏蔽系统默认的锁屏,再启动自己的锁屏

/** * 通过悬浮窗实现锁屏 * @author lin * @version 1.0 * @date 16-2-25 */public class LockerService extends Service{    private static final String TAG = "LockerService";    private ScreenListener screenListener;    private KeyguardManager mKeyguardManager;    private KeyguardManager.KeyguardLock mKeyguardLock;    private WindowManager mWindowManager;    private RootView mRootView;    private BroadcastReceiver mReceiver = null;    ...//中间代码省略    .../**     * init all the data     */    private void init(){        if(mWindowManager == null){            mWindowManager = (WindowManager) getApplicationContext().getSystemService(Context.WINDOW_SERVICE);        }        if(screenListener == null) {            screenListener = new ScreenListener(LockerService.this);            screenListener.begin(new ScreenListener.ScreenStateListener() {               public void onScreenOn() {    disableDefaultLock();}     @Override     public void onScreenOff() {    enableDefaultLock();    if (mRootView == null)        mRootView = new RootView(getApplicationContext());    addLockView();    createConfig();    createReceiver();       }                @Override                public void onUnlock() {                }            });        }    }        /**     * add the rootView 显示悬浮窗     */    private void addLockView(){        if(mWindowManager == null) return ;        mRootView = new RootView(getApplicationContext());        WindowManager.LayoutParams param = new WindowManager.LayoutParams();        param.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;        if(Config.sIsFull) param.flags = WindowManager.LayoutParams.FLAG_FULLSCREEN |                WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;        param.format = PixelFormat.RGBA_8888;        param.width = WindowManager.LayoutParams.MATCH_PARENT;        param.height = WindowManager.LayoutParams.MATCH_PARENT;        mWindowManager.addView(mRootView, param);    }    /**     * remove the rootView 移除悬浮窗     */    private void removeLockView(){        if(mWindowManager == null) return ;        mWindowManager.removeView(mRootView);    }}     


(4)锁屏自启动

      

    当手机重启时,我们定义的锁屏需要手动开启,才能使用,这样的体验非常不好。因此我们需要让其自启动,当手机开启时,锁屏服务自动开启。

 

   A.在xml中添加自启动权限

  <!-- 自启动 -->

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

 

   B.全局注册一个广播接收器


<!-- 注册自启动接收器 --><receiver    android:name="com.zero.locker.lock.LockerBootReceiver">    <intent-filter >        <!-- 开机启动 -->        <action android:name="android.intent.action.BOOT_COMPLETED"/>        <!-- 后台启动 -->        <action android:name="android.intent.action.QUICKBOOT_POWERON" />        <category android:name="android.intent.category.LAUNCHER" />    </intent-filter></receiver>

   C.编写广播接收器类

/** * Locker 自启动接收器 * @author lin * @version 1.0 * @date 16-2-29 */public class LockerBootReceiver extends BroadcastReceiver{    @Override  public void onReceive(Context context, Intent intent) {        if(Config.sIsLock && !LockerService.isStarted){            context.startService(new Intent(context, LockerService.class));        }    }}


(5)锁屏服务持久驻扎


     方案一: 在Service的onDestory方法里重启Service

 

              理论上是可行的,但是经过一段时间测试,服务莫名其妙的消失了,在本人的魅族手机上是这种情况,因此放弃。

 

     方案二: 把Service设置为前台服务 

 

          Android将进程分为6个等级,它们按优先级顺序由高到低依次是:

                1.前台进程( FOREGROUND_APP)

                2.可视进程(VISIBLE_APP )

                3.次要服务进程(SECONDARY_SERVER )

                4.后台进程 (HIDDEN_APP)

                5.内容供应节点(CONTENT_PROVIDER)

                6.空进程(EMPTY_APP)


          在Service的onStartCommand方法里,调用startForeground方法,将锁屏服务设置为前台服务。

          这样实际上就是提高了进程的优先级,当内存不足的时候,系统会优先回收那些优先级低的进程和服务,因此Service得以常驻内存

但是,即使如此,一旦遇到内存非常吃紧,Service还是有可能会被回收杀死,同时如果调用一些内存清理的功能,Service同样会被回收。

 

             代码上的改动如下:


            A.在Service的onStartCommand方法里设置

/** * 设置前台运行,使Service常驻内存 */Notification.Builder builder = new Notification.Builder(this);PendingIntent contentIntent = PendingIntent.getActivity(this, 0, new Intent(LockerService.this, MainActivity.class), 0);builder.setContentIntent(contentIntent);builder.setSmallIcon(R.mipmap.ic_launcher);builder.setTicker(sNotificationTickerText);builder.setContentTitle(sNotificationContentTitle);builder.setContentText(sNotificationContentText);Notification notification = builder.build();startForeground(sNotificationIdentify, notification);

            B.在Service的onDestroy里设置   stopForeground(true);


     方案三:

 

            然而,在方案二的基础上,依然会出现锁屏服务使用一段时间后,被系统销毁的情况。


            使用双服务守护

 

            原理:通常来说,Service内存开销是比较小的,默认情况下,Service会附在UI进程下运行,这样就很容易在整体内存占用过大的情况被一起清理掉。而单独的进程可以有效避免系统回收。        

 

            让Service运行在单独的进程之外,我们还需要一个轻量级的Service,作为唤醒的守护Service.

 

            步骤:

 

            A.在xml中设置Service运行于单独的进程中

 

  <service android:name=".lock.LockerService"            android:process=":ServiceProcess"            android:enabled="true"            android:exported="true">  </service>  <service     android:name=".lock.LockerProtectService"    android:enabled="true"      android:process=":ServiceProcess"    android:exported="true" >  </service>

           B.编写守护服务,这是一个需要一直保持的服务,因此我们尽量让其保持简单,尽量使其内存开销接近无,尽量不去占用资源,以免对内存的造成影响。


           如何让守护服务一直保持呢,可以通过AlarmManager定时唤醒守护服务,在守护服务唤醒同时,检查锁屏服务是否销毁,如果销毁,则重启锁屏服务。此外,由于让服务长期驻扎内存是非常不被推荐的行为,因此,我们应该尽量保持服务不吃内存开销,同时在不想使用锁屏的时候,应该通过stopService()主动销毁这个守护服务.


/** * 锁屏守护服务 * * @author * @version 1.0 * @date 16-3-2 */public class LockerProtectService extends Service {    private static final String TAG = "LockerProtectService";    …//中间代码省略    @Override    public int onStartCommand(Intent intent, int flags, int startId) {        Log.e(TAG, "onStart");        restoreLockerService();        return START_STICKY;    }    //恢复锁屏服务    private void restoreLockerService(){        if(!isServiceRunning("com.zero.locker.lock.LockerService")){            Log.e(TAG,"restore LockerService");            startService(new Intent(this,LockerService.class));        }    }}

         记录一下Log,检查一下效果:

 

        15:28 锁屏服务启动,同时守护服务启动

        03-02 15:28:56.606 24871-24871/com.zero.locker:ServiceProcess E/LockerProtectService: onCreate

        03-02 15:28:56.606 24871-24871/com.zero.locker:ServiceProcess E/LockerProtectService: onStart

        03-02 15:28:56.612 24871-24871/com.zero.locker:ServiceProcess E/LockerService: onCreate

        03-02 15:28:56.616 24871-24871/com.zero.locker:ServiceProcess E/LockerService: onStart


        15:31 锁屏服务和守护服务都被销毁,守护服务重启,同时检查锁屏服务,唤醒锁屏服务

        03-02 15:31:54.110 26854-26854/? E/LockerProtectService: onCreate

        03-02 15:31:54.111 26854-26854/? E/LockerProtectService: onStart

        03-02 15:31:54.116 26854-26854/? E/LockerProtectService: restore LockerService

        03-02 15:31:54.127 26854-26854/? E/LockerService: onCreate

        03-02 15:31:54.130 26854-26854/? E/LockerService: onStart

 

        15:34 守护服务继续监护

        03-02 15:35:54.026 26854-26854/com.zero.locker:ServiceProcess E/LockerProtectService: onStart

        03-02 15:36:54.038 26854-26854/com.zero.locker:ServiceProcess E/LockerProtectService: onStart

        03-02 15:37:54.023 26854-26854/com.zero.locker:ServiceProcess E/LockerProtectService: onStart

        03-02 15:38:54.034 26854-26854/com.zero.locker:ServiceProcess E/LockerProtectService: onStart

 

        16:10 守护服务和锁屏服务都存在,预期效果达到

        03-02 16:08:54.028 8194-8194/com.zero.locker:ServiceProcess E/LockerProtectService: onStart

        03-02 16:09:54.041 8194-8194/com.zero.locker:ServiceProcess E/LockerProtectService: onStart

        03-02 16:10:54.036 8194-8194/com.zero.locker:ServiceProcess E/LockerProtectService: onStart

 

 

            总归起来,这些方案只能尽量提高Service的持久性,面对内存清理助手等,现在没有应用做的到进程被完全杀死还能唤醒的情况,包括微信,亲测即便其双进程互相守护的方式,在手动关闭微信进程的情况下,也就无法继续收到消息了。

 

           顺便附上源码:locker.tar            

           ..........

           好吧,刚以为可以Service可以持久运行了,坚持了5个小时,锁屏服务又销毁了,关键是手机内存还剩下20%多,这分明是内存还足够,具体什么原因,还需要继续研究~ 未完待续...


2 0