思路
开始条件
- 判断系统版本是否在6.0之后
- 判断手机是否支持指纹
- 判断手机中是否已经录入相关指纹(锁屏)
- 以上条件全部满足后,可以进行指纹验证
指纹验证的情况分为3中:
- 验证通过(success),之后进行用户登录、跳转界面等操作
- 验证失败(failed),次数内的失败,例如一次失败后的提示(倒计次数)、震动等操作
- 验证失败(failed),达到五次后,提示次数过多,跳转到密码登录(要么30秒后指纹登录,要么提示这一次只能密码登录)
注:
- 8.0之前,指纹只能错误5次,达到5次时会禁止指纹认证,同时开启30秒倒计时,等待结束后重置错误计数,继续认证
- 8.0之后,依然是每错误5次就会倒计时30秒,然而30秒结束后错误计数并不会被清空,
- 8.0上加入了最大20次的限制,累计错误20次之后就无法使用指纹认证功能了,只能用密码的方式才能重置错误计数
- 9.0之后它。。。。。。被抛弃了 -.-||
- ic_fp_40px.png
- 本文参考了郭霖大神的文章
根据官方demo修改代码如下:
FingerprintDialogFragment 代码:
@TargetApi(23)
public class FingerprintDialogFragment extends DialogFragment {
private RelativeLayout fingerprintContainer;
private TextView fingerprintDescription, fingerprintStatus;
private ImageView fingerprintIcon;
private RelativeLayout backupContainer;
private FrameLayout description;
private TextView passwordDescription, newFingerprintEnrolledDescription;
private EditText password;
private CheckBox useFingerprintInFutureCheck;
private LinearLayout buttonPanel;
private Button cancelButton, secondDialogButton;
private FingerprintManager fingerprintManager;
private CancellationSignal mCancellationSignal;
private Cipher mCipher;
private static final String DEFAULT_KEY_NAME = "default_key";
private KeyStore keyStore;
private Stage mStage = Stage.FINGERPRINT;
private int flag = 5; // 验证次数
private int countdown = 1; // 时间倒计时
private boolean isSelfCancelled;// 标识是否是用户主动取消的认证
// 是否支持指纹
public boolean supportFingerprint() {
if (Build.VERSION.SDK_INT < 23) {
Toast.makeText(getContext(), "您的系统版本过低,不支持指纹功能", Toast.LENGTH_SHORT).show();
return false;
} else {
KeyguardManager keyguardManager = getContext().getSystemService(KeyguardManager.class);
FingerprintManager fingerprintManager = getContext().getSystemService(FingerprintManager.class);
if (fingerprintManager != null) {
if (!fingerprintManager.isHardwareDetected()) {
Toast.makeText(getContext(), "您的手机不支持指纹功能", Toast.LENGTH_SHORT).show();
return false;
} else if (keyguardManager != null) {
if (!keyguardManager.isKeyguardSecure()) {
Toast.makeText(getContext(), "您还未设置锁屏,请先设置锁屏并添加一个指纹", Toast.LENGTH_SHORT).show();
return false;
} else if (!fingerprintManager.hasEnrolledFingerprints()) {
Toast.makeText(getContext(), "您至少需要在系统设置中添加一个指纹", Toast.LENGTH_SHORT).show();
return false;
}
} else {
Toast.makeText(getContext(), "键盘管理初始化失败", Toast.LENGTH_SHORT).show();
return false;
}
} else {
Toast.makeText(getContext(), "指纹管理初始化失败", Toast.LENGTH_SHORT).show();
return false;
}
}
return true;
}
// 初始化密钥库
private void initKey() {
try {
keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);
KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore");
KeyGenParameterSpec.Builder builder = new KeyGenParameterSpec.Builder(DEFAULT_KEY_NAME,
KeyProperties.PURPOSE_ENCRYPT |
KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_CBC)
.setUserAuthenticationRequired(true)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7);
keyGenerator.init(builder.build());
keyGenerator.generateKey();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
// 初始化密钥
private void initCipher() {
try {
SecretKey key = (SecretKey) keyStore.getKey(DEFAULT_KEY_NAME, null);
Cipher cipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/"
+ KeyProperties.BLOCK_MODE_CBC + "/"
+ KeyProperties.ENCRYPTION_PADDING_PKCS7);
cipher.init(Cipher.ENCRYPT_MODE, key);
setCipher(cipher);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public void setCipher(Cipher cipher) {
mCipher = cipher;
}
// 向Activity传值的接口
public interface FragmentInteraction {
void onEditPassword(String strPassword);//输入的密码
void onAuthenticated();//验证成功
}
private FragmentInteraction listener;
private void initView(View view) {
// 指纹登录
fingerprintContainer = (RelativeLayout) view.findViewById(R.id.fingerprint_container);
fingerprintDescription = (TextView) view.findViewById(R.id.fingerprint_description);
fingerprintIcon = (ImageView) view.findViewById(R.id.fingerprint_icon);
fingerprintStatus = (TextView) view.findViewById(R.id.fingerprint_status);
// 密码登录
backupContainer = (RelativeLayout) view.findViewById(R.id.backup_container);
description = (FrameLayout) view.findViewById(R.id.description);
passwordDescription = (TextView) view.findViewById(R.id.password_description);
newFingerprintEnrolledDescription = (TextView) view.findViewById(R.id.new_fingerprint_enrolled_description);
password = (EditText) view.findViewById(R.id.password);
useFingerprintInFutureCheck = (CheckBox) view.findViewById(R.id.use_fingerprint_in_future_check);
// 下方按钮
buttonPanel = (LinearLayout) view.findViewById(R.id.buttonPanel);
cancelButton = (Button) view.findViewById(R.id.cancel_button);
secondDialogButton = (Button) view.findViewById(R.id.second_dialog_button);
getDialog().setCancelable(false);
getDialog().setCanceledOnTouchOutside(false);
}
private void initEvent() {
cancelButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (mStage == Stage.FINGERPRINT) {
stopListening();
dismiss();
} else if (mStage == Stage.PASSWORD) {
password.setText("");
dismiss();
}
}
});
secondDialogButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (mStage == Stage.FINGERPRINT) {
goToPassword();
} else if (mStage == Stage.PASSWORD) {
//LogUtil( "输入密码是:" + password.getText().toString());
listener.onEditPassword(password.getText().toString());
dismiss();
}
}
});
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
listener = (FragmentInteraction) context;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setStyle(DialogFragment.STYLE_NORMAL, android.R.style.Theme_Material_Light_Dialog);
if (supportFingerprint()) {
fingerprintManager = getContext().getSystemService(FingerprintManager.class);
initKey();
initCipher();
} else {
LogUtil("关闭dialog");
dismiss();
}
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {
getDialog().setTitle("指纹验证");
View v = inflater.inflate(R.layout.dialog_fingerprint, container, false);
initView(v);
initEvent();
LogUtil("Fragment名字:" + getTag());
return v;
}
@Override
public void onResume() {
super.onResume();
// 开始指纹认证监听
startListening(mCipher);
}
@Override
public void onPause() {
super.onPause();
// 停止指纹认证监听
stopListening();
}
@Override
public void onDetach() {
super.onDetach();
listener = null;//把传递进来的activity对象释放掉
}
private void startListening(Cipher cipher) {
isSelfCancelled = false;
mCancellationSignal = new CancellationSignal();
fingerprintManager.authenticate(new FingerprintManager.CryptoObject(cipher), mCancellationSignal, 0, new FingerprintManager.AuthenticationCallback() {
@Override
public void onAuthenticationError(int errorCode, CharSequence errString) {
if (!isSelfCancelled) {
fingerprintStatus.setText(errString);
if (errorCode == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT) {
//多次指纹密码验证错误后,进入此方法;并且,不能短时间内调用指纹验证
Message message = new Message();
message.what = -1;
message.obj = errString;
myHandler.sendMessage(message);
}
}
}
@Override
public void onAuthenticationHelp(int helpCode, CharSequence helpString) {
fingerprintStatus.setText(helpString);
}
@Override
public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) {
Message message = new Message();
message.what = 1;
myHandler.sendMessage(message);
}
@Override
public void onAuthenticationFailed() {
Message message = new Message();
message.what = 0;
myHandler.sendMessage(message);
}
}, null);
}
private void stopListening() {
if (mCancellationSignal != null) {
mCancellationSignal.cancel();
mCancellationSignal = null;
isSelfCancelled = true;
}
}
@SuppressLint("HandlerLeak")
private Handler myHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case -1:
LogUtil("handleMessage---多次失败");
/*if (flag == 0) {
// 传值失败时的系统时间,用来判断是否可以再次开启指纹
DateFormat dateFormat = DateFormat.getDateInstance(R.string.data_format);
listener.process(dateFormat.format(new Date(System.currentTimeMillis())));
}*/
Toast.makeText(getContext(), msg.obj.toString(), Toast.LENGTH_SHORT).show();
goToPassword();
//dismiss();
break;
case 1:
LogUtil("handleMessage---成功");
fingerprintIcon.setImageResource(R.drawable.ic_fingerprint_success);
fingerprintStatus.setText("成功");
fingerprintStatus.setTextColor(fingerprintStatus.getResources().getColor(R.color.success_color));
fingerprintStatus.postDelayed(mSucceedFingerRunnable, countdown * 1000);
break;
case 0:
flag--;
LogUtil("指纹识别失败,还剩下" + flag + "次机会");
fingerprintIcon.setImageResource(R.drawable.ic_fingerprint_error);
fingerprintStatus.setText("指纹识别失败,还剩下" + flag + "次机会");
fingerprintStatus.setTextColor(fingerprintStatus.getResources().getColor(R.color.red));
fingerprintStatus.postDelayed(mResetFingerRunnable, countdown * 1000);
break;
default:
break;
}
}
};
private Runnable mResetFingerRunnable = new Runnable() {
@Override
public void run() {
fingerprintStatus.setText("重新验证");
fingerprintStatus.setTextColor(fingerprintStatus.getResources().getColor(R.color.grey));
fingerprintIcon.setImageResource(R.mipmap.ic_fp_40px);
}
};
private Runnable mSucceedFingerRunnable = new Runnable() {
@Override
public void run() {
listener.onAuthenticated();
dismiss();
}
};
private void goToPassword() {
cancelButton.setText(R.string.s_btn_cancel);
secondDialogButton.setText(R.string.s_btn_ok);
fingerprintContainer.setVisibility(View.GONE);
backupContainer.setVisibility(View.VISIBLE);
mStage = Stage.PASSWORD;
password.requestFocus();
stopListening();
}
private void LogUtil(String msg) {
Log.e("指纹界面:", msg);
}
public enum Stage {
FINGERPRINT,
NEW_FINGERPRINT_ENROLLED,
PASSWORD
}
}
dialog_fingerprint布局代码:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<FrameLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<include
layout="@layout/fingerprint_dialog_content" />
<include
layout="@layout/fingerprint_dialog_backup"
android:visibility="gone"/>
</FrameLayout>
<LinearLayout
android:id="@+id/buttonPanel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingStart="12dp"
android:paddingEnd="12dp"
android:paddingTop="4dp"
android:paddingBottom="4dp"
android:gravity="bottom">
<Space
android:id="@+id/spacer"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_weight="1"
android:visibility="invisible" />
<Button
android:id="@+id/cancel_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="取消"/>
<Button
android:id="@+id/second_dialog_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="密码登陆"/>
</LinearLayout>
</LinearLayout>
fingerprint_dialog_backup布局代码:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/backup_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingTop="16dp"
android:paddingBottom="8dp">
<FrameLayout
android:id="@+id/description"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_alignParentStart="true"
android:layout_marginStart="24dp"
android:layout_marginEnd="24dp"
android:layout_alignParentLeft="true">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="输入你的密码"
android:id="@+id/password_description"
android:textSize="18sp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="A new fingerprint was added to this device, so your password is required."
android:id="@+id/new_fingerprint_enrolled_description"
android:visibility="gone"
android:textColor="?android:attr/textColorSecondary" />
</FrameLayout>
<EditText
android:id="@+id/password"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:inputType="textPassword"
android:ems="10"
android:hint="密码"
android:imeOptions="actionGo"
android:layout_below="@+id/description"
android:layout_marginTop="16dp"
android:layout_marginStart="20dp"
android:layout_marginEnd="20dp"
android:layout_alignParentStart="true"
android:layout_alignParentLeft="true" />
<CheckBox
android:id="@+id/use_fingerprint_in_future_check"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/password"
android:layout_alignParentStart="true"
android:layout_marginTop="16dp"
android:layout_marginStart="20dp"
android:layout_marginEnd="20dp"
android:checked="true"
android:visibility="gone"
android:text="Use fingerprint in the future"
android:layout_alignParentLeft="true" />
</RelativeLayout>
fingerprint_dialog_content布局代码:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/fingerprint_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="8dp"
android:paddingStart="24dp"
android:paddingEnd="24dp"
android:paddingTop="16dp">
<TextView
android:id="@+id/fingerprint_description"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:textSize="18sp"
android:text="请确认指纹" />
<ImageView
android:id="@+id/fingerprint_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/fingerprint_description"
android:layout_marginTop="20dp"
android:src="@mipmap/ic_fp_40px"/>
<TextView
android:id="@+id/fingerprint_status"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBottom="@+id/fingerprint_icon"
android:layout_alignTop="@+id/fingerprint_icon"
android:gravity="center_vertical"
android:text="请进行指纹验证"
android:textColor="@color/darkgray"
android:layout_marginLeft="16dp"
android:layout_toRightOf="@+id/fingerprint_icon" />
</RelativeLayout>