安卓手机Google智能锁使用教程:轻松设置智能解锁功能
在本教程中,我们将讨论智能锁定功能,并将其应用于我们的Android应用程序。
Google智能锁
Google智能锁用于自动登录您的应用程序,只需保存一次凭据。这意味着,如果您在一段时间后重新安装应用程序,只要您没有从Chrome密码中删除它们,您就可以使用先前保存的凭据自动登录。
谷歌智能锁通过一次轻触即可登录。
为了在您的应用程序中集成智能锁,您需要使用凭据API。凭据API允许用户:
- 在打开应用程序时请求凭据。
- 从登录表单保存凭据。
- 在应用程序和网站之间同步凭据。
- 显示电子邮件提示,以便在登录/注册过程中帮助用户。
为了在您的应用程序中使用Google智能锁,您需要添加以下依赖项:
dependencies {
implementation 'com.google.android.gms:play-services-auth:16.0.0'
}
SmartLock在您的Android应用程序中需要设置GoogleApiClient。只有一个凭证时,SmartLock允许自动登录。如果存在多个凭证,则会在对话框中显示它们。
之前我们习惯于使用SharedPreferences来自动登录并本地保存凭据。现在有了Google智能锁定,一切都由Google服务器处理。
以下是凭证API中的主要方法:
- save(GoogleApiClient client, Credential credential) – 保存凭据
- request(GoogleApiClient client, CredentialRequest request) – 请求为应用程序保存的所有凭据
- getHintPickerIntent(GoogleApiClient client, HintRequest request) – 显示您已登录的帐户列表,以便快速填写登录表单
- disableAutoSignIn(GoogleApiClient client) – 禁用自动登录
- delete(GoogleApiClient client, Credential credential) – 删除凭据
您可以通过访问 passwords.google.com 查看保存在Google账户中的所有凭据。对于具有智能锁的应用程序,登录屏幕代码需要按照以下方式结构化:
- 检查凭据。如果存在单个凭据,则自动登录或自动填充登录表单。
- 如果有多个凭据,则在对话框中显示它们,让用户选择。
- 如果没有保存的凭据,您可以让用户填写表单,或者通过自动填充或显示包含可用登录帐户的提示对话框来简化操作。
开始入门
让我们开始在Android应用中实现智能锁功能。建立GoogleApiClient。
mGoogleApiClient = new GoogleApiClient.Builder(this)
.addConnectionCallbacks(this)
.addApi(Auth.CREDENTIALS_API)
.enableAutoManage(this, this)
.build();
实现GoogleApiClient接口并实现方法。初始化凭证客户端。
CredentialsOptions options = new CredentialsOptions.Builder()
.forceEnableSaveDialog()
.build();
CredentialsClient mCredentialsApiClient = Credentials.getClient(this, options);
需要在Android Oreo及以上版本中使用forceEnableSaveDialog()。创建CredentialRequest。
CredentialRequest mCredentialRequest = new CredentialRequest.Builder()
.setPasswordLoginSupported(true)
.setAccountTypes(IdentityProviders.GOOGLE)
.build();
提取凭证
Auth.CredentialsApi.request(mGoogleApiClient, mCredentialRequest).setResultCallback(this);
setResultCallBack需要我们重写接口ResultCallback中的onResult方法。
@Override
public void onResult(@NonNull CredentialRequestResult credentialRequestResult) {
Status status = credentialRequestResult.getStatus();
if (status.isSuccess()) {
onCredentialRetrieved(credentialRequestResult.getCredential());
} else {
if (status.getStatusCode() == CommonStatusCodes.RESOLUTION_REQUIRED) {
try {
isResolving = true;
status.startResolutionForResult(this, RC_READ);
} catch (IntentSender.SendIntentException e) {
Log.d(TAG, e.toString());
}
} else {
showHintDialog();
}
}
}
有三种情况:
- 单一凭证 – 成功 – 自动登录
- 多个凭证 – 解决并在对话框中显示所有可用凭证
- 没有凭据 – 显示包含所有可用登录帐户的提示对话框
当状态码为RESOLUTION_REQUIRED时,意味着有多个需要解决的凭据。为此,我们调用startResolutionForResult方法,在onActivityResult方法中返回结果。我们使用一个布尔标志来防止多次解决。这将导致建立多个对话框。现在我们已经了解了SmartLock功能的要点,让我们完整实现它,包括保存和删除凭据的功能。
项目结构
代码
安卓谷歌智能锁实现(第2部分)
下面是activity_main.xml布局的代码:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="https://schemas.android.com/apk/res/android"
xmlns:app="https://schemas.android.com/apk/res-auto"
xmlns:tools="https://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:importantForAutofill="noExcludeDescendants">
<Button
android:id="@+id/btnLogin"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:layout_marginTop="24dp"
android:text="登录"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/inPassword" />
<EditText
android:id="@+id/inEmail"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:layout_marginTop="32dp"
android:ems="10"
android:hint="邮箱"
android:inputType="textEmailAddress"
app:layout_constraintHorizontal_bias="0.503"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent" />
<EditText
android:id="@+id/inPassword"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:layout_marginTop="8dp"
android:ems="10"
android:hint="密码"
android:inputType="textPassword"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/inEmail" />
</android.support.constraint.ConstraintLayout>
android:importantForAutofill=”noExcludeDescendants”表示用于禁用EditText字段上的自动填充。关于自动填充API的讨论将在另一个教程中进行。下面是MainActivity.java类的代码。
package com.Olivia.androidgooglesmartlock;
import android.app.PendingIntent;
import android.content.Intent;
import android.content.IntentSender;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import android.util.Patterns;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
import com.google.android.gms.auth.api.Auth;
import com.google.android.gms.auth.api.credentials.Credential;
import com.google.android.gms.auth.api.credentials.CredentialPickerConfig;
import com.google.android.gms.auth.api.credentials.CredentialRequest;
import com.google.android.gms.auth.api.credentials.CredentialRequestResponse;
import com.google.android.gms.auth.api.credentials.CredentialRequestResult;
import com.google.android.gms.auth.api.credentials.Credentials;
import com.google.android.gms.auth.api.credentials.CredentialsClient;
import com.google.android.gms.auth.api.credentials.CredentialsOptions;
import com.google.android.gms.auth.api.credentials.HintRequest;
import com.google.android.gms.auth.api.credentials.IdentityProviders;
import com.google.android.gms.auth.api.signin.GoogleSignIn;
import com.google.android.gms.auth.api.signin.GoogleSignInAccount;
import com.google.android.gms.auth.api.signin.GoogleSignInClient;
import com.google.android.gms.auth.api.signin.GoogleSignInOptions;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.ApiException;
import com.google.android.gms.common.api.CommonStatusCodes;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.ResolvableApiException;
import com.google.android.gms.common.api.ResultCallback;
import com.google.android.gms.common.api.Status;
import com.google.android.gms.tasks.OnCompleteListener;
import com.google.android.gms.tasks.Task;
import java.util.regex.Pattern;
public class MainActivity extends AppCompatActivity implements GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener, ResultCallback<CredentialRequestResult> {
private GoogleApiClient mGoogleApiClient;
CredentialsClient mCredentialsApiClient;
CredentialRequest mCredentialRequest;
public static final String TAG = "API123";
private static final int RC_READ = 3;
private static final int RC_SAVE = 1;
private static final int RC_HINT = 2;
boolean isResolving;
Button btnLogin;
EditText inEmail, inPassword;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
setUpGoogleApiClient();
// Android Oreo系统需要
CredentialsOptions options = new CredentialsOptions.Builder()
.forceEnableSaveDialog()
.build();
mCredentialsApiClient = Credentials.getClient(this, options);
createCredentialRequest();
btnLogin = findViewById(R.id.btnLogin);
inEmail = findViewById(R.id.inEmail);
inPassword = findViewById(R.id.inPassword);
btnLogin.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
String email = inEmail.getText().toString();
String password = inPassword.getText().toString();
if (TextUtils.isEmpty(email) || TextUtils.isEmpty(password) || !Patterns.EMAIL_ADDRESS.matcher(email).matches())
showToast("请输入有效的邮箱和密码");
else {
Credential credential = new Credential.Builder(email)
.setPassword(password)
.build();
saveCredentials(credential);
}
}
});
}
// 设置Google API客户端
public void setUpGoogleApiClient() {
mGoogleApiClient = new GoogleApiClient.Builder(this)
.addConnectionCallbacks(this)
.addApi(Auth.CREDENTIALS_API)
.enableAutoManage(this, this)
.build();
}
// 创建凭据请求
public void createCredentialRequest() {
mCredentialRequest = new CredentialRequest.Builder()
.setPasswordLoginSupported(true)
.setAccountTypes(IdentityProviders.GOOGLE)
.build();
}
// 请求凭据
public void requestCredentials() {
Auth.CredentialsApi.request(mGoogleApiClient, mCredentialRequest).setResultCallback(this);
}
// 当凭据被检索到时调用
private void onCredentialRetrieved(Credential credential) {
String accountType = credential.getAccountType();
if (accountType == null) {
// 使用凭据中的信息登录用户
gotoNext();
} else if (accountType.equals(IdentityProviders.GOOGLE)) {
GoogleSignInOptions gso = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
.requestEmail()
.build();
GoogleSignInClient signInClient = GoogleSignIn.getClient(this, gso);
Task<GoogleSignInAccount> task = signInClient.silentSignIn();
task.addOnCompleteListener(new OnCompleteListener<GoogleSignInAccount>() {
@Override
public void onComplete(@NonNull Task<GoogleSignInAccount> task) {
if (task.isSuccessful()) {
// 参见"处理成功的凭据请求"
populateLoginFields(task.getResult().getEmail(), null);
} else {
showToast("无法进行谷歌登录");
}
}
});
}
}
// 跳转到下一个活动
public void gotoNext() {
startActivity(new Intent(this, SecondActivity.class));
finish();
}
// 显示提示信息
public void showToast(String s) {
Toast.makeText(getApplicationContext(), s, Toast.LENGTH_SHORT).show();
}
@Override
public void onConnected(@Nullable Bundle bundle) {
Log.d("API123", "已连接");
requestCredentials();
}
@Override
public void onConnectionSuspended(int i) {
}
@Override
public void onConnectionFailed(@NonNull ConnectionResult connectionResult) {
}
@Override
protected void onDestroy() {
mGoogleApiClient.disconnect();
super.onDestroy();
}
@Override
public void onResult(@NonNull CredentialRequestResult credentialRequestResult) {
Status status = credentialRequestResult.getStatus();
if (status.isSuccess()) {
onCredentialRetrieved(credentialRequestResult.getCredential());
} else {
if (status.getStatusCode() == CommonStatusCodes.RESOLUTION_REQUIRED) {
try {
isResolving = true;
status.startResolutionForResult(this, RC_READ);
} catch (IntentSender.SendIntentException e) {
Log.d(TAG, e.toString());
}
} else {
showHintDialog();
}
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
Log.d(TAG, "活动结果");
if (requestCode == RC_READ) {
if (resultCode == RESULT_OK) {
Credential credential = data.getParcelableExtra(Credential.EXTRA_KEY);
onCredentialRetrieved(credential);
} else {
Log.d(TAG, "请求失败");
}
isResolving = false;
}
if (requestCode == RC_HINT) {
if (resultCode == RESULT_OK) {
Credential credential = data.getParcelableExtra(Credential.EXTRA_KEY);
populateLoginFields(credential.getId(), "");
} else {
showToast("提示对话框已关闭");
}
}
if (requestCode == RC_SAVE) {
if (resultCode == RESULT_OK) {
Log.d(TAG, "保存: 成功");
gotoNext();
showToast("凭据已保存");
}
}
}
// 填充登录字段
public void populateLoginFields(String email, String password) {
if (!TextUtils.isEmpty(email))
inEmail.setText(email);
if (!TextUtils.isEmpty(password))
inPassword.setText(password);
}
// 显示提示对话框
public void showHintDialog() {
HintRequest hintRequest = new HintRequest.Builder()
.setHintPickerConfig(new CredentialPickerConfig.Builder()
.setShowCancelButton(true)
.build())
.setEmailAddressIdentifierSupported(true)
.setAccountTypes(IdentityProviders.GOOGLE)
.build();
PendingIntent intent = mCredentialsApiClient.getHintPickerIntent(hintRequest);
try {
startIntentSenderForResult(intent.getIntentSender(), RC_HINT, null, 0, 0, 0);
} catch (IntentSender.SendIntentException e) {
Log.e(TAG, "无法启动提示选择器意图", e);
}
}
// 保存凭据
public void saveCredentials(Credential credential) {
mCredentialsApiClient.save(credential).addOnCompleteListener(new OnCompleteListener<Void>() {
@Override
public void onComplete(@NonNull Task<Void> task) {
if (task.isSuccessful()) {
Log.d(TAG, "保存: 成功");
showToast("凭据已保存");
return;
}
Exception e = task.getException();
if (e instanceof ResolvableApiException) {
// 尝试解决保存请求。如果凭据是新的,这将提示用户。
ResolvableApiException rae = (ResolvableApiException) e;
try {
rae.startResolutionForResult(MainActivity.this, RC_SAVE);
} catch (IntentSender.SendIntentException f) {
// 无法解决请求
Log.e(TAG, "发送解决方案失败。", f);
showToast("保存失败");
}
} else {
// 请求没有解决方案
showToast("保存失败");
}
}
});
}
}
在onConnected方法中,我们请求可用的凭据。这意味着一旦启动活动,如果有任何凭据,则会检索到。如果有一个凭据,它将自动登录并转到下一个活动。以下是activity_second.xml布局的代码:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="https://schemas.android.com/apk/res/android"
xmlns:app="https://schemas.android.com/apk/res-auto"
xmlns:tools="https://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".SecondActivity">
<Button
android:id="@+id/btnDeleteAccount"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="删除账户"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/btnSignOut" />
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="您已登录。"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/btnSignOut"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="退出登录"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView" />
<Button
android:id="@+id/btnSignOutDisableAutoSign"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="退出登录并禁用自动登录"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/btnDeleteAccount" />
</android.support.constraint.ConstraintLayout>
在SecondActivity中,我们将执行三个不同的操作 – 退出登录、退出登录并禁用下次自动登录、删除凭据。下面是SecondActivity.java类的代码。
package com.Olivia.androidgooglesmartlock;
import android.content.Intent;
import android.content.IntentSender;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
import com.google.android.gms.auth.api.Auth;
import com.google.android.gms.auth.api.credentials.Credential;
import com.google.android.gms.auth.api.credentials.CredentialRequest;
import com.google.android.gms.auth.api.credentials.CredentialRequestResult;
import com.google.android.gms.auth.api.credentials.Credentials;
import com.google.android.gms.auth.api.credentials.CredentialsClient;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.ResultCallback;
import com.google.android.gms.common.api.Status;
public class SecondActivity extends AppCompatActivity implements View.OnClickListener, GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener, ResultCallback<CredentialRequestResult> {
Button btnSignOut, btnSignOutDisableAuto, btnDelete;
private GoogleApiClient mGoogleApiClient;
CredentialsClient mCredentialsApiClient;
CredentialRequest mCredentialRequest;
public static final String TAG = "API123";
private static final int RC_REQUEST = 4;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
setUpGoogleApiClient();
mCredentialsApiClient = Credentials.getClient(this);
btnSignOut = findViewById(R.id.btnSignOut);
btnSignOutDisableAuto = findViewById(R.id.btnSignOutDisableAutoSign);
btnDelete = findViewById(R.id.btnDeleteAccount);
btnSignOut.setOnClickListener(this);
btnSignOutDisableAuto.setOnClickListener(this);
btnDelete.setOnClickListener(this);
}
@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.btnSignOut:
signOut(false);
break;
case R.id.btnSignOutDisableAutoSign:
signOut(true);
break;
case R.id.btnDeleteAccount:
requestCredentials();
break;
}
}
@Override
public void onConnected(@Nullable Bundle bundle) {
}
@Override
public void onConnectionSuspended(int i) {
}
@Override
public void onConnectionFailed(@NonNull ConnectionResult connectionResult) {
}
@Override
public void onResult(@NonNull CredentialRequestResult credentialRequestResult) {
Status status = credentialRequestResult.getStatus();
if (status.isSuccess()) {
onCredentialSuccess(credentialRequestResult.getCredential());
} else {
if (status.hasResolution()) {
try {
status.startResolutionForResult(this, RC_REQUEST);
} catch (IntentSender.SendIntentException e) {
Log.d(TAG, e.toString());
}
} else {
showToast("请求失败");
}
}
}
// 设置Google API客户端
public void setUpGoogleApiClient() {
mGoogleApiClient = new GoogleApiClient.Builder(this)
.addConnectionCallbacks(this)
.addApi(Auth.CREDENTIALS_API)
.enableAutoManage(this, this)
.build();
}
// 请求凭据
private void requestCredentials() {
mCredentialRequest = new CredentialRequest.Builder()
.setPasswordLoginSupported(true)
.build();
Auth.CredentialsApi.request(mGoogleApiClient, mCredentialRequest).setResultCallback(this);
}
@Override
protected void onDestroy() {
mGoogleApiClient.disconnect();
super.onDestroy();
}
// 凭据成功处理
private void onCredentialSuccess(Credential credential) {
Auth.CredentialsApi.delete(mGoogleApiClient, credential).setResultCallback(new ResultCallback<Status>() {
@Override
public void onResult(@NonNull Status status) {
if (status.isSuccess()) {
signOut(false);
} else {
showToast("账户删除失败");
}
}
});
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == RC_REQUEST) {
if (resultCode == RESULT_OK) {
showToast("已删除");
Credential credential = data.getParcelableExtra(Credential.EXTRA_KEY);
onCredentialSuccess(credential);
} else {
Log.d(TAG, "请求失败");
}
}
}
// 显示提示信息
public void showToast(String s) {
Toast.makeText(getApplicationContext(), s, Toast.LENGTH_SHORT).show();
}
// 退出登录
private void signOut(boolean disableAutoSignIn) {
if (disableAutoSignIn)
Auth.CredentialsApi.disableAutoSignIn(mGoogleApiClient);
startActivity(new Intent(this, MainActivity.class));
finish();
}
}
以上应用的输出如下:我们创建了第一个账户,并发现每次打开应用时,它会自动登录。除非我们禁用自动登录。然后它会在登录之前请求权限。我们又创建了另一个账户。这次当我们删除它时,在删除后应用会自动登录到第一个账户。教程到此结束。可以在下面的链接中下载这个项目: