9月3日全国放假,心情大好,虽然离过年还有段距离。
下午,客户打来电话,说以前做好的app公司(这个app是给联想做的)安全部门没过,最好加上手势密码,不过这个功能就不在加钱了,长期合作吗。我就是这样被忽悠了。心肠太好,没办法。
参看了京东钱包的做法,当用户离开app或者机子睡眠后开始定时,超过一定时间后(比如十分钟),那么,你在回到app后就显示手势密码,只有通过验证后才能进入app。
由于是每个页面都必须加手势密码,这让我很是想念j2ee中的拦截器。如果每个activity中都重复加上手势密码的代码,这显然是违反了尽量避免重复原则,另外一但有修改,维护起来将是一场灾难。最重要的一点就是,这样的代码,拿出去太丢人了。
能否集中处理那,这让我想起了kyswipeback组件,通过继承一个共有的activity实现:
完整源码:
package com.lenovo.logistics.activity.gusturelock; import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; import android.content.Context; import android.content.Intent; import android.graphics.drawable.BitmapDrawable; import android.os.Bundle; import android.view.Gravity; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; import android.widget.Button; import android.widget.CheckBox; import android.widget.FrameLayout; import android.widget.LinearLayout; import android.widget.PopupWindow; import android.widget.RelativeLayout; import android.widget.TextView; import com.lenovo.logistics.R; import com.lenovo.logistics.activity.BaseActivity; import com.lenovo.logistics.activity.LoginActivity; import com.lenovo.logistics.common.ActivityManager; import com.lenovo.logistics.common.ConfigInfoUtil; import com.lenovo.logistics.common.GusturelockUtil; import com.lenovo.logistics.view.KyGustureLock; import com.lenovo.logistics.view.KyGustureLock.OnSelectedListener; import com.lenovo.logistics.wrapper.ConfigInfo; import com.lenovo.logistics.wrapper.GusturelockInfo; /** * 实现手势密码 * @author pc * */ public class GustureLockActivity extends BaseActivity{ private boolean gustureLockEnable = true;//是否关闭密码锁功能 private FrameLayout root; private ViewGroup decorChild; private RelativeLayout gusturelock; /** *这是核心原理,其他的可以不看,这个必须看 *原理:在已有的布局的外层套个FrameLayout,将手势密码布局和以前的布局都加入到这个FrameLayout中,这样 *就可以很容根据超时是否显示密码手势。 所有的Activity只要继承GustureLockActivity就可,不必单独添加代码,所有手势密码逻辑都放到GustureLockActivity即可。 **/ @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ActivityManager.addActivity(this); ViewGroup decor = (ViewGroup) this.getWindow().getDecorView(); decorChild = (ViewGroup) decor.getChildAt(0);//已有的布局,即this.setContentView(R.layout.about)设置的 decor.removeView(decorChild); //重新生成个根布局 FrameLayout rootLayout = new FrameLayout(this); this.root = rootLayout; rootLayout.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); rootLayout.addView(decorChild); //创建手势密码布局文件 LayoutInflater inflate = (LayoutInflater) this.getSystemService(Context.LAYOUT_INFLATER_SERVICE); gusturelock = (RelativeLayout)(inflate.inflate(R.layout.gusturelock, null)); gusturelock.setVisibility(View.GONE); rootLayout.addView(gusturelock); //将其添加到新建的根布局中 //将根布局附加到窗口上 decor.addView(rootLayout); setUpdateTime(); bindEvent(); } @Override protected void onResume() { super.onResume(); decorChild.setVisibility(View.VISIBLE); gusturelock.setVisibility(View.GONE); if(this.gustureLockEnable && isGusturelock()){ decorChild.setVisibility(View.GONE); gusturelock.setVisibility(View.VISIBLE); } } @Override protected void onPause() { // TODO Auto-generated method stub super.onPause(); setUpdateTime(); } /** * 绑定事件 */ private void bindEvent(){ KyGustureLock kyGustureLock = (KyGustureLock)gusturelock.findViewById(R.id.kyGustureLock); final TextView msg = (TextView)gusturelock.findViewById(R.id.msg); //手势密码监听 kyGustureLock.setOnSelectedListener(new OnSelectedListener(){ int restNum = 5;//剩余次数 @Override public void onSelected(View view, int[] values) { msg.setText(""); if(restNum <= 0){ msg.setTextColor(0xFFD74A44); msg.setText("密码错误次数过多,请重新登录"); toLoginPage("密码错误次数过多,请重新登录"); return ; } msg.setTextColor(0xFFFFFFFF); if(values.length < 4){ msg.setText("至少连续绘制四个点"); return ; } StringBuffer sb = new StringBuffer(); for(int val : values){ if(sb.length()>0) sb.append("_"); sb.append(val); } restNum--; GusturelockInfo info = GusturelockUtil.get(GustureLockActivity.this); if(sb.toString().equals(info.getPassword())){//登录成功 decorChild.setVisibility(View.VISIBLE); gusturelock.setVisibility(View.GONE); //重置 restNum = 5; setUpdateTime(); return ; } if(restNum <= 0){ msg.setTextColor(0xFFD74A44); msg.setText("密码错误次数过多,请重新登录"); toLoginPage("密码错误次数过多,请重新登录"); return ; } msg.setTextColor(0xFFD74A44); msg.setText("密码错误,还可以再输入"+restNum+"次"); return ; } }); //忘记密码监听 TextView forgetPsw = (TextView)gusturelock.findViewById(R.id.forgetPsw); forgetPsw.setClickable(true); forgetPsw.setOnClickListener(new OnClickListener(){ @Override public void onClick(View arg0) { toLoginPage("忘记手势密码,需要重新登录"); } }); } /** * 设置更新时间 */ private void setUpdateTime(){ try { DateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); GusturelockInfo info = GusturelockUtil.get(GustureLockActivity.this); info.setUpdateTime(format.format(new Date()));; GusturelockUtil.save(GustureLockActivity.this, info); } catch (Exception e) { e.printStackTrace(); } } /** * 是否需要显示手势密码 */ private boolean isGusturelock(){ try { GusturelockInfo info = GusturelockUtil.get(this); if(info.getPassword() == null || info.getPassword().trim().equals(""))//未设置密码锁 return false; if(info.getUpdateTime() == null || info.getUpdateTime().trim().equals("")) return false; DateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); Date updateTime = format.parse(info.getUpdateTime()); long current = System.currentTimeMillis(); if(current - updateTime.getTime() > 5 * 60 * 1000)//5分钟 //if(current - updateTime.getTime() > 5 * 1000)//5分钟 return true; } catch (ParseException e) { e.printStackTrace(); } return false; } /** * 手势密码是否正在显示 */ protected boolean isGusturelockShowing(){ if(gusturelock.getVisibility() == View.VISIBLE) return true; return false; } /** * 退出应用,同时清除密码 */ protected void exitApplication(){ ConfigInfo info = ConfigInfoUtil.getConfigInfo(this); info.setAutoLogin(false); info.setPassword(""); ConfigInfoUtil.saveConfigInfo(this, info); ActivityManager.exit(); } /** * 返回到登录页 */ private void toLoginPage(String message){ //当手势密码验证没通过时,返回到登录页 } /** * 监听键盘 */ @Override public boolean onKeyDown(int keyCode, KeyEvent event) { if(keyCode == KeyEvent.KEYCODE_BACK){ if(isGusturelockShowing()){ exitApplication(); return true; } } return super.onKeyDown(keyCode, event); } public boolean isGustureLockEnable() { return gustureLockEnable; } public void setGustureLockEnable(boolean gustureLockEnable) { this.gustureLockEnable = gustureLockEnable; } }
KyGustureLock是手势密码控件,从组件中心http://www.see-source.com/androidwidget/list.html可以看到,
啥时候判断是否超时
可以在onResume中,这样无论是第一次进入,还是重新返回到app,或者是从睡眠状态恢复,都能立马进行处理。
@Override protected void onResume() { super.onResume(); decorChild.setVisibility(View.VISIBLE); gusturelock.setVisibility(View.GONE); if(this.gustureLockEnable && isGusturelock()){ decorChild.setVisibility(View.GONE);//隐藏正常的布局 gusturelock.setVisibility(View.VISIBLE);//显示手势密码布局 } }
离开app时开启定时
可以在onPause方法中开启定时,当用户离开app或进入睡眠状态,此时页面的焦点必定消失,onPause方法调用:
@Override protected void onPause() { // TODO Auto-generated method stub super.onPause(); setUpdateTime(); }
修改下记录的时间戳即可。以这个时间作为判断超时的起始时间。
大功告成,已经发给客户,目前还没反馈说有bug。