Vì Android 4.2.2 có thể chạy các Dịch vụ của Google trên Trình giả lập Android. Tôi hiện đang tạo một ứng dụng Android và đã thực hiện một dự án thử nghiệm để xem liệu tôi có thể đăng nhập và đăng xuất Google+ để hoạt động không.Trình giả lập Android: Ứng dụng này sẽ không chạy nếu không có các dịch vụ của Google Play
Tôi đã theo các hướng dẫn sau: http://www.androidhive.info/2014/02/android-login-with-google-plus-account-1/
Với thông tin thêm được sử dụng từ các hướng dẫn/trang web sau:
- https://developers.google.com/+/mobile/android/getting-started
- https://developers.google.com/+/mobile/android/sign-in
- http://developer.android.com/google/play-services/setup.html#Setup
này tạo ra đoạn mã sau:
AndroidManifest.xml:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.testproject_gmaillogin"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="9"
android:targetSdkVersion="19" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<uses-permission android:name="android.permission.USE_CREDENTIALS" />
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<meta-data android:name="com.google.android.gms.version" android:value="@integer/google_play_services_version" />
<activity
android:name="com.example.testproject_gmaillogin.MainActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
strings.xml:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">TestProject_GmailLogin</string>
<string name="action_settings">Settings</string>
<string name="profile_pic_description">Google Profile Picture</string>
<string name="btn_logout_from_google">Logout from Google</string>
<string name="btn_revoke_access">Revoke Access</string>
</resources>
activity_main.xml:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp"
tools:context=".MainActivity" >
<LinearLayout
android:id="@+id/profile_layout"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="20dp"
android:orientation="horizontal"
android:weightSum="3"
android:visibility="gone">
<ImageView
android:id="@+id/img_profile_pic"
android:contentDescription="@string/profile_pic_description"
android:layout_width="80dp"
android:layout_height="wrap_content"
android:layout_weight="1"/>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:orientation="vertical"
android:layout_weight="2" >
<TextView
android:id="@+id/txt_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="5dp"
android:textSize="20sp" />
<TextView
android:id="@+id/txt_email"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="5dp"
android:textSize="18sp" />
</LinearLayout>
</LinearLayout>
<com.google.android.gms.common.SignInButton
android:id="@+id/btn_sign_in"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="20dp"/>
<Button
android:id="@+id/btn_sign_out"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/btn_logout_from_google"
android:visibility="gone"
android:layout_marginBottom="10dp"/>
<Button
android:id="@+id/btn_revoke_access"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/btn_revoke_access"
android:visibility="gone" />
</LinearLayout>
MainActivity.java:
package com.example.testproject_gmaillogin;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GooglePlayServicesUtil;
import com.google.android.gms.common.SignInButton;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.GoogleApiClient.ConnectionCallbacks;
import com.google.android.gms.common.api.GoogleApiClient.OnConnectionFailedListener;
import com.google.android.gms.common.api.ResultCallback;
import com.google.android.gms.common.api.Status;
import com.google.android.gms.plus.Plus;
import com.google.android.gms.plus.model.people.Person;
import android.support.v7.app.ActionBarActivity;
import android.content.Intent;
import android.content.IntentSender.SendIntentException;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
public class MainActivity extends ActionBarActivity implements ConnectionCallbacks, OnConnectionFailedListener, OnClickListener
{
// Logcat tag
private static final String TAG = "MainActivity";
// Profile pix image size in pixels
private static final int PROFILE_PIC_SIZE = 400;
// Request code used to invoke sign in user interactions
private static final int RC_SIGN_IN = 0;
// Client used to interact with Google APIs
private GoogleApiClient mGoogleApiClient;
// A flag indicating that a PendingIntent is in progress and prevents
// us from starting further intents
private boolean mIntentInProgress;
// Track whether the sign-in button has been clicked so that we know to resolve
// all issues preventing sign-in without waiting
private boolean mSignInClicked;
// Store the connection result from onConnectionFailed callbacks so that we can
// resolve them when the user clicks sign-in
private ConnectionResult mConnectionResult;
// The used UI-elements
private SignInButton btnSignIn;
private Button btnSignOut, btnRevokeAccess;
private ImageView imgProfilePic;
private TextView txtName, txtEmail;
private LinearLayout profileLayout;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Get the UI-elements
btnSignIn = (SignInButton) findViewById(R.id.btn_sign_in);
btnSignOut = (Button) findViewById(R.id.btn_sign_out);
btnRevokeAccess = (Button) findViewById(R.id.btn_revoke_access);
imgProfilePic = (ImageView) findViewById(R.id.img_profile_pic);
txtName = (TextView) findViewById(R.id.txt_name);
txtEmail = (TextView) findViewById(R.id.txt_email);
profileLayout = (LinearLayout) findViewById(R.id.profile_layout);
// Set the Button onClick-listeners
btnSignIn.setOnClickListener(this);
btnSignOut.setOnClickListener(this);
btnRevokeAccess.setOnClickListener(this);
mGoogleApiClient = new GoogleApiClient.Builder(this)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.addApi(Plus.API, null)
.addScope(Plus.SCOPE_PLUS_LOGIN)
.build();
}
@Override
protected void onStart(){
super.onStart();
mGoogleApiClient.connect();
}
@Override
protected void onStop(){
super.onStop();
if(mGoogleApiClient.isConnected())
mGoogleApiClient.disconnect();
}
@Override
public void onClick(View view){
switch(view.getId()){
case R.id.btn_sign_in:
signInWithGPlus();
break;
case R.id.btn_sign_out:
signOutFromGPlus();
break;
case R.id.btn_revoke_access:
revokeGPlusAccess();
break;
}
}
@Override
public void onConnectionFailed(ConnectionResult result) {
if(!result.hasResolution()){
GooglePlayServicesUtil.getErrorDialog(result.getErrorCode(), this, 0).show();
return;
}
if(!mIntentInProgress){
// Store the ConnectionResult so that we can use it later when the user clicks 'sign-in'
mConnectionResult = result;
if(mSignInClicked)
// The user has already clicked 'sign-in' so we attempt to resolve all
// errors until the user is signed in, or they cancel
resolveSignInErrors();
}
}
@Override
protected void onActivityResult(int requestCode, int responseCode, Intent intent){
if(requestCode == RC_SIGN_IN && responseCode == RESULT_OK)
SignInClicked = true;
mIntentInProgress = false;
if(!mGoogleApiClient.isConnecting())
mGoogleApiClient.connect();
}
}
@Override
public void onConnected(Bundle connectionHint) {
mSignInClicked = false;
Toast.makeText(this, "User is connected!", Toast.LENGTH_LONG).show();
// Get all the user's information
getProfileInformation();
// Update the UI after sign-in
updateUI(true);
}
@Override
public void onConnectionSuspended(int cause){
mGoogleApiClient.connect();
updateUI(false);
}
// Updating the UI, showing/hiding buttons and profile layout
private void updateUI(boolean isSignedIn){
if(isSignedIn){
btnSignIn.setVisibility(View.GONE);
btnSignOut.setVisibility(View.VISIBLE);
btnRevokeAccess.setVisibility(View.VISIBLE);
profileLayout.setVisibility(View.VISIBLE);
}
else{
btnSignIn.setVisibility(View.VISIBLE);
btnSignOut.setVisibility(View.GONE);
btnRevokeAccess.setVisibility(View.GONE);
profileLayout.setVisibility(View.GONE);
}
}
// Sign-in into Google
private void signInWithGPlus(){
if(!mGoogleApiClient.isConnecting()){
mSignInClicked = true;
resolveSignInErrors();
}
}
// Method to resolve any sign-in errors
private void resolveSignInErrors(){
if(mConnectionResult.hasResolution()){
try{
mIntentInProgress = true;
//Toast.makeText(this, "Resolving Sign-in Errors", Toast.LENGTH_SHORT).show();
mConnectionResult.startResolutionForResult(this, RC_SIGN_IN);
}
catch(SendIntentException e){
// The intent was cancelled before it was sent. Return to the default
// state and attempt to connect to get an updated ConnectionResult
mIntentInProgress = false;
mGoogleApiClient.connect();
}
}
}
// Fetching the user's infromation name, email, profile pic
private void getProfileInformation(){
try{
if(Plus.PeopleApi.getCurrentPerson(mGoogleApiClient) != null){
Person currentPerson = Plus.PeopleApi.getCurrentPerson(mGoogleApiClient);
String personName = currentPerson.getDisplayName();
String personPhotoUrl = currentPerson.getImage().getUrl();
String personGooglePlusProfile = currentPerson.getUrl();
String personEmail = Plus.AccountApi.getAccountName(mGoogleApiClient);
Log.e(TAG, "Name: " + personName + ", "
+ "plusProfile: " + personGooglePlusProfile + ", "
+ "email: " + personEmail + ", "
+ "image: " + personPhotoUrl);
txtName.setText(personName);
txtEmail.setText(personEmail);
// by default the profile url gives 50x50 px image,
// but we can replace the value with whatever dimension we
// want by replacing sz=X
personPhotoUrl = personPhotoUrl.substring(0, personPhotoUrl.length() - 2)
+ PROFILE_PIC_SIZE;
new LoadProfileImage(imgProfilePic).execute(personPhotoUrl);
}
else{
Toast.makeText(getApplicationContext(), "Person information is null", Toast.LENGTH_LONG).show();
}
}
catch(Exception ex){
ex.printStackTrace();
}
}
// Sign-out from Google
private void signOutFromGPlus(){
if(mGoogleApiClient.isConnected()){
Plus.AccountApi.clearDefaultAccount(mGoogleApiClient);
mGoogleApiClient.disconnect();
mGoogleApiClient.connect();
updateUI(false);
}
}
// Revoking access from Google
private void revokeGPlusAccess(){
if(mGoogleApiClient.isConnected()){
Plus.AccountApi.clearDefaultAccount(mGoogleApiClient);
Plus.AccountApi.revokeAccessAndDisconnect(mGoogleApiClient)
.setResultCallback(new ResultCallback<Status>(){
@Override
public void onResult(Status s){
Log.e(TAG, "User access revoked!");
mGoogleApiClient.connect();
updateUI(false);
}
});
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
if (id == R.id.action_settings)
return true;
return super.onOptionsItemSelected(item);
}
}
LoadProfileImage.java:
package com.example.testproject_gmaillogin;
import java.io.InputStream;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.AsyncTask;
import android.util.Log;
import android.widget.ImageView;
/**
* Background async task to load user profile picture from url
**/
public class LoadProfileImage extends AsyncTask<String, Void, Bitmap> {
private ImageView bmImage;
public LoadProfileImage(ImageView bmImage){
this.bmImage = bmImage;
}
@Override
protected Bitmap doInBackground(String... urls){
String urlDisplay = urls[0];
Bitmap mIcon11 = null;
try{
InputStream in = new java.net.URL(urlDisplay).openStream();
mIcon11 = BitmapFactory.decodeStream(in);
}
catch(Exception ex){
Log.e("Error", ex.getMessage());
ex.printStackTrace();
}
return mIcon11;
}
@Override
protected void onPostExecute(Bitmap result){
bmImage.setImageBitmap(result);
}
}
Các bước khác mà tôi đã làm là:
Tại https://console.developers.google.com/project tôi đã tạo ra một dự án với:
Google+ API trên:
Và ID khách hàng được tạo bằng đúng SHA1 và không gian tên chính xác giống như dự án:
Tại Eclipse:
Tôi đã cài đặt google-play-dịch vụ thư viện:
Và thêm nó vào dự án:
Tôi cũng đã tạo Trình giả lập với phiên bản 4.4.2:
Nhưng khi tôi chạy ứng dụng tôi nhận được lỗi sau, và lỗi này giữ nảy lên khi tôi nhấp vào nút:
Bất cứ ai có bất kỳ ý tưởng nơi nó đi sai? Cảm ơn trước cho các phản ứng.
đẹp câu hỏi :) – shkschneider