【Android高级】锁屏功能简单实现

来源:互联网 发布:linux pv命令安装 编辑:程序博客网 时间:2024/06/06 08:36

普通Activity伪造锁屏

文章开头的GIF图片展示的效果, 就是用一个普通Activity做的. 
国内的app们, 最终都选择了这条道路, 不知道他们是谁抄的谁, 第一个想到使用普通Activity伪造一个锁屏的开发者, 我只能说非常有创造力.

监听锁屏事件

准确来说我们监听的是屏幕熄灭事件, 关屏事件的Intent是Intent.ACTION_SCREEN_OFF, 不需要任何权限就可以监听, 但是必须使用代码注册, 也就是说我们必须有一个Service在后台监听才行, 对音乐类app来说, 这不是问题, 音乐app本身就是使用Service来控制MediaPlayer的. 只需要在Service中注册监听Intent.ACTION_SCREEN_OFF就行. 监听到这个事件,

@Override public void onReceive(Context context, Intent intent) { String action = intent.getAction();if (action.equals(Intent.ACTION_SCREEN_OFF)) { Intent lockscreen = new Intent(PlaybackService.this, LockScreenActivity.class); lockscreen.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); startActivity(lockscreen); } }

注意我们在Service中启动一个Activity, 需要加上Intent.FLAG_ACTIVITY_NEW_TASK这个flag才行.

透明背景

要想做到锁屏那样滑动解锁, 比如像图中的样子, 我们除了要根据手势移动View以外, 还要让Activity的背景透明, 比如将theme设置成下面这样.

<style name="LockScreenBase" parent="AppBaseTheme"> <item name="android:windowIsTranslucent">true</item> <item name="android:windowBackground">@android:color/transparent</item> <item name="android:colorBackgroundCacheHint">@null</item> <item name="android:windowNoTitle">true</item> <item name="android:backgroundDimEnabled">false</item> <item name="android:windowAnimationStyle">@null</item> <item name="android:windowContentOverlay">@null</item> </style>

这个style其实做了很多东西, 大家根据自己的需要可以删减一部分, 比如状态栏透明, 不使用TitleBar之类的.

解锁屏幕与显示在锁屏之上

在显示我们的假锁屏的时候, 我们应当帮用户解锁, 这样我们才能冒充锁屏, 而不会出现用户”解锁”两次的情况, 但我们只能要求系统解锁没有密码的锁屏, 有密码的情况下, 我们是不能解锁屏幕的, 这时我们应该覆盖在锁屏界面上, 幸好, 在API level 5中就引入了两个Flag, FLAG_DISMISS_KEYGUARDFLAG_SHOW_WHEN_LOCKED 
在锁屏Activity的onCreate方法中给Activity加上两个Flag

this.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);

一定要两个一起用, 否则效果不大好, 当时测试了好久, 后来看了一下QQ音乐的实现, 才发现两个一起用效果才好, 否则会有一些奇怪的问题.

隐藏踪迹与独立的Task

作为一个锁屏界面, 应当是独立的, 也就是说, 我们这个Activity应当独立于我们的App存在, 至少看起来是这样. 从Android的角度来看, 我们app的主界面里的所有Activity, 应当在一个Task里, 而锁屏Activity, 应当在一个独立的Task里, 因此我们需要给锁屏Activity一个独立的Task, 而且无论何时, 都只有一个锁屏Activity实例存在. 
另外, Android有查看近期任务的功能, 我们不希望锁屏界面这个独立的Task显示在里面, 所以锁屏Activity不能显示在近期任务中. 
说了这么多, 要做很简单, 只需要在Manifest里面声明Activity时加入几个属性即可

<activity android:excludeFromRecents="true" android:exported="false" android:launchMode="singleInstance"android:name=".view.lockscreen.LockScreenActivity" android:screenOrientation="portrait" android:taskAffinity="com.package.name.lockscreen" android:theme="@style/LockScreenTheme"> </activity>

上面的属性中android:excludeFromRecents="true"让锁屏Activity不显示在近期任务中,android:launchMode="singleInstance"android:taskAffinity="com.package.name.lockscreen"保证锁屏Activity有一个单独的Task, 且这个Task里永远只有它一个实例.

不响应Back按键

锁屏界面当然不响应Back按键, 只需要重写Activity的onBackPressed方法即可

@Override public void onBackPressed() { // do nothing }

对Home按键的处理

我们无法监听Home按键, 但是可以改变因Home进入后台时的处理, 比如在Manifest的activity声明中加上

android:noHistory="true"

这样如果用户通过Home按键让我们的应用进入后台, 我们会让这个activity销毁, 就像我们被滑动关闭一样. 
如果不加, 最好重写Activity的onNewIntent来应对因Home进入后台, 然后Service再次启动锁屏Activity的情况.

处理黑色闪屏

我们的锁屏Activity在滑动”解锁”之后, 理论上是直接进入下面的界面, 但有时如果下面不是launcher, 而是一个app, 有可能会闪一下黑屏, 这个其实是底下activity的入场动画导致的, 某些Android版本会对顶部activity透明时处理有些奇怪, 我们不能保证其他的应用不闪黑屏, 但是对自己的的应用还是可以的, 只需要在我们的主体activity的style中加上

<item name="android:windowAnimationStyle">@null</item>

就不会有这种情况发生了, 但是这样的话入场动画也没了, 总之如何取舍看大家了.


实现的时候发现一个问题就是在mainfest.xml注册的锁屏广播接受不到,原因可能与平台限制有关,因此采用代码注册实现其功能。

public class MainAct extends Activity {    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        this.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);        getWindow().requestFeature(Window.FEATURE_NO_TITLE);        setContentView(R.layout.close_screen_lay);        ViewUtils.inject(this);    }    @OnClick(R.id.open_screen)    public  void test(View v){        MainAct.this.finish();    }    @Override    public void onBackPressed() {    }}

public class ScreenReceiver extends BroadcastReceiver {    @Override    public void onReceive(Context context, Intent intent) {        Log.i("tag","receive0");        String actStr=intent.getAction();       if(actStr.equals(Intent.ACTION_SCREEN_OFF)){           Intent actIntent=new Intent(context,MainAct.class);           actIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);           context.startActivity(actIntent);           Log.i("tag","receive");       }    }}

public class ScreenService extends Service {    @Override    public IBinder onBind(Intent intent) {        // TODO: Return the communication channel to the service.        throw new UnsupportedOperationException("Not yet implemented");    }    @Override    public void onCreate() {        IntentFilter intentFilter=new IntentFilter();        intentFilter.addAction(Intent.ACTION_SCREEN_OFF);        registerReceiver(new ScreenReceiver(), intentFilter);        Log.i("tag","service_onCreate");    }    @Override    public int onStartCommand(Intent intent, int flags, int startId) {        return super.onStartCommand(intent, flags, startId);    }}




0 0
原创粉丝点击