您的位置:首页 > 移动开发 > Android开发

Android-锁屏的应用

2016-03-04 01:35 507 查看
锁屏效果图:





记录一个锁屏应用的实现

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



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

锁屏的应用难点

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



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



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



A.重写以下两个方法

@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
    if(KeyEvent.KEYCODE_HOME==keyCode){
        return true;
    }
    return super.onKeyDown(keyCode, event);
}
@Override
public 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自然撤到后台,但是亲测似乎没效果。





最佳方案: 把锁屏页改成悬浮窗,当屏幕点亮,全屏显示悬浮窗(貌似Go锁屏也是这么做的)

悬浮框主要是通过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%多,这分明是内存还足够,具体什么原因,还需要继续研究~ 未完待续...
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: