web-dev-qa-db-fra.com

Android Emulator: cette application ne fonctionnera pas sans les services Google Play

Depuis Android 4.2.2, il est possible d'exécuter les services Google sur l'émulateur Android. Je crée actuellement un Android application et fait un projet de test pour voir si je peux obtenir la connexion et la déconnexion de Google+ au travail.

J'ai suivi le tutoriel suivant: http://www.androidhive.info/2014/02/Android-login-with-google-plus-account-1/

Avec des informations supplémentaires utilisées à partir des tutoriels/sites suivants:

Cela a généré le code suivant:

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);
    }
}

Les autres étapes que j'ai faites étaient:

À https://console.developers.google.com/project J'ai créé un projet avec:

API Google+ sur:

Google+ API on

Et un ID client créé avec le SHA1 correct et exactement le même espace de noms que le projet:

And a Client ID created with the correct SHA1

Chez Eclipse:

J'ai installé la bibliothèque google-play-services:

Google-play services installed

Et l'a ajouté au projet:

Google-play services library added (2)Google-play services library added (2)

J'ai également créé un émulateur avec la version 4.4.2:

I've also created an Emulator with version 4.4.2

Mais lorsque j'exécute l'application, j'obtiens l'erreur suivante, et cette erreur continue d'apparaître lorsque je clique sur le bouton:

Get Google Play services Error

Quelqu'un a une idée où ça va mal? Merci d'avance pour les réponses.

17
Kevin Cruijssen

Ok, après avoir essayé certaines choses, il s'est avéré que j'avais une dernière option non correctement vérifiée, qui n'était mentionnée nulle part dans le (s) tutoriel (s) ..

Au lieu de Android 4.4.2 comme cible de construction de projet et cible d'émulateur, j'ai sélectionné les API Google 4.4.2. Maintenant, je ne reçois plus l'erreur des services Google Play.

Je reçois une NullPointerException, mais au moins je peux continuer ..;)

EDIT: correction de l'exception NullPointerException et modification du code dans mon message d'origine. Tout fonctionne comme il se doit maintenant et j'espère que ce message aide d'autres personnes ayant les mêmes (ou d'autres) erreurs à utiliser la connexion aux services google play à l'aide d'un émulateur Android Android.

Solution ErrorSolution Error Emulator

15
Kevin Cruijssen

Essayez d'utiliser le niveau 19 de l'API Google API comme cible d'émulateur au lieu du niveau 19 normal de l'API.

8
Dr.Jukka