16

我正在开发一个应该与 Facebook 集成的 Android 应用程序。我已经阅读了官方指南和其他几个指南,我认为我已经使用 Facebook 登录框架正确实现了所有内容。

但是,SDK 仅在第一次执行成功登录。如果我注销并重试,应用程序将毫无例外地关闭。如果我杀死我的应用程序并从应用程序列表启动它,也会发生同样的情况。

我可以在模拟器和真实设备(Nexus 7)上重现它。

登录活动.java:

package com.everporter.everporter;

import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;

import org.json.JSONException;
import org.json.JSONObject;

import com.everporter.everporter.FBLoginFragment.OnFBAccessTokenPass;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.annotation.TargetApi;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import android.telephony.PhoneNumberUtils;
import android.text.TextUtils;
import android.util.Log;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.View;
import android.view.inputmethod.EditorInfo;
import android.widget.EditText;
import android.widget.TextView;

/**
 * Activity which displays a login screen to the user, offering registration as
 * well.
 */
public class LoginActivity extends FragmentActivity
    implements OnFBAccessTokenPass {

    /**
     * The default email to populate the email field with.
     */
    public static final String EXTRA_EMAIL = "com.example.android.authenticatordemo.extra.EMAIL";
    private static final String REST_METHOD_NAME = "/login/";
    private static final String SOCIAL_REST_METHOD_NAME = "/validation/";
    private static final String TAG = "UserLoginTask";
    private static final String FB_TAG = "UserFacebookLoginTask";

    /**
     * Keep track of the login task to ensure we can cancel it if requested.
     */
    private UserLoginTask mAuthTask = null;
    private UserFacebookLoginTask mFacebookAuthTask = null;
    private String mFacebookAccessToken;
    private String mFacebookUserId;

    // API URL read from the settings (set in main activity)
    private String mRestApiUrl;
    private int mConnectionTimeout;
    private int mReadTimeout;
    private SharedPreferences mPrefs;

    // Error message set by the async task, if the error does happen indeed
    private String mAsyncTaskErrorMsg;

    // Values for email and password at the time of the login attempt.
    private String mEmail;
    private String mPassword;
    private String mSessionCookie; // our session

    // UI references.
    private EditText mEmailView;
    private EditText mPasswordView;
    private View mLoginFormView;
    private View mLoginStatusView;
    private TextView mLoginStatusMessageView;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);     

        setContentView(R.layout.activity_login);

        mPrefs = getSharedPreferences("settings", MODE_PRIVATE);
        mRestApiUrl = mPrefs.getString("api_url", "");
        mConnectionTimeout = mPrefs.getInt("connection_timeout", 3000);
        mReadTimeout = mPrefs.getInt("read_timeout", 3000);

        // Set up the login form.
        mEmail = getIntent().getStringExtra(EXTRA_EMAIL);
        mEmailView = (EditText) findViewById(R.id.email);
        mEmailView.setText(mEmail);

        mPasswordView = (EditText) findViewById(R.id.password);
        mPasswordView
                .setOnEditorActionListener(new TextView.OnEditorActionListener() {
                    @Override
                    public boolean onEditorAction(TextView textView, int id,
                            KeyEvent keyEvent) {
                        if (id == R.id.login || id == EditorInfo.IME_NULL) {
                            attemptLogin();
                            return true;
                        }
                        return false;
                    }
                });

        mLoginFormView = findViewById(R.id.login_form);
        mLoginStatusView = findViewById(R.id.login_status);
        mLoginStatusMessageView = (TextView) findViewById(R.id.login_status_message);

        findViewById(R.id.sign_in_button).setOnClickListener(
                new View.OnClickListener() {
                    @Override
                    public void onClick(View view) {
                        attemptLogin();
                    }
                });
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        super.onCreateOptionsMenu(menu);
        getMenuInflater().inflate(R.menu.login, menu);
        return true;
    }

    /**
     * Handle Facebook login
     */
    @Override
    public void onFBAccessTokenPass(String accessToken, String uid) {
        // we are passed Facebook access token, successful login
        Log.i(TAG, "Facebook access token: " + accessToken);
        mFacebookAccessToken = accessToken;
        mFacebookUserId = uid;

        // Show a progress spinner, and kick off a background task to
        // sent the FB token to the server.
        mLoginStatusMessageView.setText(R.string.login_progress_signing_in);
        showProgress(true);
        mFacebookAuthTask = new UserFacebookLoginTask(this);
        mFacebookAuthTask.execute((Void) null);

    }

    /**
     * Attempts to sign in or register the account specified by the login form.
     * If there are form errors (invalid email, missing fields, etc.), the
     * errors are presented and no actual login attempt is made.
     */
    public void attemptLogin() {
        if (mAuthTask != null) {
            return;
        }

        // Reset errors.
        mEmailView.setError(null);
        mPasswordView.setError(null);
        mAsyncTaskErrorMsg = "";

        // Store values at the time of the login attempt.
        mEmail = mEmailView.getText().toString();
        mPassword = mPasswordView.getText().toString();

        boolean cancel = false;
        View focusView = null;

        // Check for a valid password.
        if (TextUtils.isEmpty(mPassword)) {
            mPasswordView.setError(getString(R.string.error_field_required));
            focusView = mPasswordView;
            cancel = true;
        } else if (mPassword.length() < 4) {
            mPasswordView.setError(getString(R.string.error_invalid_password));
            focusView = mPasswordView;
            cancel = true;
        }

        // Check for a valid email address or phone number.
        if (TextUtils.isEmpty(mEmail)) {
            mEmailView.setError(getString(R.string.error_field_required));
            focusView = mEmailView;
            cancel = true;
        } else if (!android.util.Patterns.EMAIL_ADDRESS.matcher(mEmail).matches() &&
                !PhoneNumberUtils.isGlobalPhoneNumber(mEmail)) {
            mEmailView.setError(getString(R.string.error_invalid_email));
            focusView = mEmailView;
            cancel = true;
        }

        if (cancel) {
            // There was an error; don't attempt login and focus the first
            // form field with an error.
            focusView.requestFocus();
        } else {
            // Show a progress spinner, and kick off a background task to
            // perform the user login attempt.
            mLoginStatusMessageView.setText(R.string.login_progress_signing_in);
            showProgress(true);
            mAuthTask = new UserLoginTask(this);
            mAuthTask.execute((Void) null);
        }
    }

    /**
     * Shows the progress UI and hides the login form.
     */
    @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR2)
    private void showProgress(final boolean show) {
        // On Honeycomb MR2 we have the ViewPropertyAnimator APIs, which allow
        // for very easy animations. If available, use these APIs to fade-in
        // the progress spinner.
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) {
            int shortAnimTime = getResources().getInteger(
                    android.R.integer.config_shortAnimTime);

            mLoginStatusView.setVisibility(View.VISIBLE);
            mLoginStatusView.animate().setDuration(shortAnimTime)
                    .alpha(show ? 1 : 0)
                    .setListener(new AnimatorListenerAdapter() {
                        @Override
                        public void onAnimationEnd(Animator animation) {
                            mLoginStatusView.setVisibility(show ? View.VISIBLE
                                    : View.GONE);
                        }
                    });

            mLoginFormView.setVisibility(View.VISIBLE);
            mLoginFormView.animate().setDuration(shortAnimTime)
                    .alpha(show ? 0 : 1)
                    .setListener(new AnimatorListenerAdapter() {
                        @Override
                        public void onAnimationEnd(Animator animation) {
                            mLoginFormView.setVisibility(show ? View.GONE
                                    : View.VISIBLE);
                        }
                    });
        } else {
            // The ViewPropertyAnimator APIs are not available, so simply show
            // and hide the relevant UI components.
            mLoginStatusView.setVisibility(show ? View.VISIBLE : View.GONE);
            mLoginFormView.setVisibility(show ? View.GONE : View.VISIBLE);
        }
    }

    /**
     * Represents an asynchronous login/registration task used to authenticate
     * the user.
     */
    public class UserLoginTask extends AsyncTask<Void, Void, Boolean> {

        private Context mContext;

        public UserLoginTask(Context context) {
            this.mContext = context;
        }

        @Override
        protected Boolean doInBackground(Void... params) {
            Log.i(TAG, "Begin auth...");

            HttpURLConnection conn = null;
            StringBuilder sb = new StringBuilder();
            BufferedOutputStream printout;
            boolean signedInOk = false;
            try {

                URL url = new URL(mRestApiUrl + REST_METHOD_NAME);
                conn = (HttpURLConnection) url.openConnection();
                conn.setDoInput(true);
                conn.setDoOutput(true);
                conn.setUseCaches(false);
                conn.setConnectTimeout(mConnectionTimeout);
                conn.setReadTimeout(mReadTimeout);
                conn.setRequestMethod("POST");
                conn.setRequestProperty("Content-Type","application/json; charset=utf-8");   
                conn.connect();
                //Create JSONObject here
                JSONObject jsonParam = new JSONObject();
                jsonParam.put("password", mPassword);
                jsonParam.put("username", mEmail);
                // Send POST output.
                printout = new BufferedOutputStream(conn.getOutputStream());
                printout.write(jsonParam.toString().getBytes("UTF-8"));
                printout.flush();
                printout.close();

                int httpCode = conn.getResponseCode();  
                if (httpCode == HttpURLConnection.HTTP_OK) {
                    BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream(), "UTF-8"));  
                    String line = null;  
                    while ((line = br.readLine()) != null) {
                        sb.append(line + "\n");
                    }
                    br.close();  
                    Log.i(TAG, sb.toString());
                    JSONObject jsonResponse = new JSONObject(sb.toString());
                    int resultCode = jsonResponse.getInt("code");
                    if (resultCode == 0) {
                        // get the session id
                        String cookie = conn.getHeaderField("Set-Cookie");
                        if (cookie != null) {

                            Log.i(TAG, cookie);
                            signedInOk = true;
                            mSessionCookie = cookie;
                            Log.i(TAG, "Authenticated");
                        }
                    } else if (resultCode == 2 || resultCode == 3) {
                        signedInOk = false; // wrong login and password
                    } else {
                        // get the error message
                        if (!jsonResponse.isNull("message")) {
                            mAsyncTaskErrorMsg = jsonResponse.getString("message");
                        } else {
                            mAsyncTaskErrorMsg = getString(R.string.internal_error);
                        }
                    }
                } else{  
                   Log.e(TAG, conn.getResponseMessage());  
                }  
            } catch (JSONException e) {
                Log.e(TAG, e.getMessage());
                mAsyncTaskErrorMsg = e.getMessage();
            } catch (IOException e) {
                Log.e(TAG, e.getMessage());
                mAsyncTaskErrorMsg = e.getMessage();
            } finally {  
                if (conn != null)  
                    conn.disconnect();  
            }

            Log.i(TAG, "End auth...");
            return signedInOk;
        }

        @Override
        protected void onPostExecute(final Boolean success) {
            mAuthTask = null;
            showProgress(false);

            if (success) {
                // store the userId
                SharedPreferences.Editor ed = mPrefs.edit();
                ed.putString("session_cookie", mSessionCookie);
                ed.putLong("session_cookie_time", System.currentTimeMillis());
                ed.apply();
                // start main
                Intent intent = new Intent(mContext, MainActivity.class);
                startActivity(intent);
                finish();
            } else {
                if (!mAsyncTaskErrorMsg.isEmpty()) {
                    // show the error message
                    new AlertDialog.Builder(mContext)
                    .setTitle("Error occured")
                    .setMessage(mAsyncTaskErrorMsg)
                    .setNeutralButton(android.R.string.ok,
                    new DialogInterface.OnClickListener() {
                        public void onClick(DialogInterface dialog, int id) {
                            dialog.cancel();
                        }
                    }).show();
                } else {
                    mPasswordView
                            .setError(getString(R.string.error_incorrect_password));
                    mPasswordView.requestFocus();
                }
            }
        }

        @Override
        protected void onCancelled() {
            mAuthTask = null;
            showProgress(false);
        }
    }

    /**
     * Represents an asynchronous login task used to authenticate
     * the user via Facebook.
     */
    public class UserFacebookLoginTask extends AsyncTask<Void, Void, Boolean> {

        private Context mContext;

        public UserFacebookLoginTask(Context context) {
            this.mContext = context;
        }

        @Override
        protected Boolean doInBackground(Void... params) {
            Log.i(FB_TAG, "Begin Facebook validation...");

            HttpURLConnection conn = null;
            StringBuilder sb = new StringBuilder();
            BufferedOutputStream printout;
            boolean signedInOk = false;
            try {

                URL url = new URL(mRestApiUrl + SOCIAL_REST_METHOD_NAME);
                conn = (HttpURLConnection) url.openConnection();
                conn.setDoInput(true);
                conn.setDoOutput(true);
                conn.setUseCaches(false);
                conn.setConnectTimeout(mConnectionTimeout);
                conn.setReadTimeout(mReadTimeout);
                conn.setRequestMethod("POST");
                conn.setRequestProperty("Content-Type","application/json; charset=utf-8");   
                conn.connect();
                //Create JSONObject here
                JSONObject jsonParam = new JSONObject();
                jsonParam.put("provider", "facebook");
                jsonParam.put("token", mFacebookAccessToken);
                jsonParam.put("uid", mFacebookUserId);
                // Send POST output.
                printout = new BufferedOutputStream(conn.getOutputStream());
                printout.write(jsonParam.toString().getBytes("UTF-8"));
                printout.flush();
                printout.close();

                int httpCode = conn.getResponseCode();  
                if (httpCode == HttpURLConnection.HTTP_OK) {
                    BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream(), "UTF-8"));  
                    String line = null;  
                    while ((line = br.readLine()) != null) {
                        sb.append(line + "\n");
                    }
                    br.close();  
                    Log.i(FB_TAG, sb.toString());
                    JSONObject jsonResponse = new JSONObject(sb.toString());
                    int resultCode = jsonResponse.getInt("code");
                    if (resultCode == 0) {
                        // get the session id
                        String cookie = conn.getHeaderField("Set-Cookie");
                        if (cookie != null) {

                            Log.i(FB_TAG, cookie);
                            signedInOk = true;
                            mSessionCookie = cookie;
                            Log.i(FB_TAG, "Authenticated");
                        }
                    } else if (resultCode == 2 || resultCode == 3) {
                        signedInOk = false; // no such user in the database
                        mAsyncTaskErrorMsg = "";
                    } else {
                        // get the error message
                        if (!jsonResponse.isNull("message")) {
                            mAsyncTaskErrorMsg = jsonResponse.getString("message");
                        } else {
                            mAsyncTaskErrorMsg = getString(R.string.internal_error);
                        }
                    }
                } else{  
                   Log.e(FB_TAG, conn.getResponseMessage());  
                }  
            } catch (JSONException e) {
                Log.e(FB_TAG, e.getMessage());
                mAsyncTaskErrorMsg = e.getMessage();
            } catch (IOException e) {
                Log.e(FB_TAG, e.getMessage());
                mAsyncTaskErrorMsg = e.getMessage();
            } finally {  
                if (conn != null)  
                    conn.disconnect();  
            }

            Log.i(FB_TAG, "End auth...");
            return signedInOk;
        }

        @Override
        protected void onPostExecute(final Boolean success) {
            mFacebookAuthTask = null;
            showProgress(false);

            if (success) {
                // store the userId
                SharedPreferences.Editor ed = mPrefs.edit();
                ed.putString("session_cookie", mSessionCookie);
                ed.putLong("session_cookie_time", System.currentTimeMillis());
                ed.apply();
                // start main
                Intent intent = new Intent(mContext, MainActivity.class);
                startActivity(intent);
                finish();
            } else {
                if (!mAsyncTaskErrorMsg.isEmpty()) {
                    // show the error message
                    new AlertDialog.Builder(mContext)
                    .setTitle("Error occured")
                    .setMessage(mAsyncTaskErrorMsg)
                    .setNeutralButton(android.R.string.ok,
                    new DialogInterface.OnClickListener() {
                        public void onClick(DialogInterface dialog, int id) {
                            dialog.cancel();
                        }
                    }).show();
                }
            }
        }

        @Override
        protected void onCancelled() {
            mFacebookAuthTask = null;
            showProgress(false);
        }
    }
}

FBLoginFragment.java:

package com.everporter.everporter;

import java.util.Arrays;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import com.facebook.Request;
import com.facebook.Response;
import com.facebook.Session;
import com.facebook.SessionState;
import com.facebook.UiLifecycleHelper;
import com.facebook.model.GraphUser;
import com.facebook.widget.LoginButton;

public class FBLoginFragment extends Fragment {

    private static final String TAG = "FBLoginFragment";
    private UiLifecycleHelper mUiHelper;
    private OnFBAccessTokenPass mTokenPasser;
    private String mSessionToken;

    private Session.StatusCallback callback = new Session.StatusCallback() {
        @Override
        public void call(Session session, SessionState state, Exception exception) {
            onSessionStateChange(session, state, exception);
        }
    };

    private void onSessionStateChange(Session session, SessionState state, Exception exception) {
        if (state.isOpened()) {
            Log.i(TAG, "Logged in...");
            mSessionToken = session.getAccessToken();
            // Request user data and show the results
            Request.executeMeRequestAsync(session, new Request.GraphUserCallback() {

                @Override
                public void onCompleted(GraphUser user, Response response) {
                    if (user != null) {
                        // pass the access token to the activity
                        // with the user id
                        mTokenPasser.onFBAccessTokenPass(mSessionToken, user.getId());
                    }
                }
            });
        } else if (state.isClosed()) {
            Log.i(TAG, "Logged out...");
            mSessionToken = "";
        }
    }

    public interface OnFBAccessTokenPass {
        public void onFBAccessTokenPass(String accessToken, String uid);
    }

    @Override
    public void onAttach(Activity a) {
        super.onAttach(a);
        mTokenPasser = (OnFBAccessTokenPass) a;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mUiHelper = new UiLifecycleHelper(getActivity(), callback);
        mUiHelper.onCreate(savedInstanceState);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, 
            ViewGroup container, 
            Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fb_login_fragment, container, false);

        LoginButton authButton = (LoginButton) view.findViewById(R.id.authButton);
        authButton.setFragment(this);
        authButton.setReadPermissions(Arrays.asList("email"));

        return view;
    }

    @Override
    public void onResume() {
        super.onResume();

        // For scenarios where the main activity is launched and user
        // session is not null, the session state change notification
        // may not be triggered. Trigger it if it's open/closed.
        Session session = Session.getActiveSession();
        if (session != null &&
               (session.isOpened() || session.isClosed()) ) {
            onSessionStateChange(session, session.getState(), null);
        }

        mUiHelper.onResume();
    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        mUiHelper.onActivityResult(requestCode, resultCode, data);
    }

    @Override
    public void onPause() {
        super.onPause();
        mUiHelper.onPause();
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        mUiHelper.onDestroy();
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        mUiHelper.onSaveInstanceState(outState);
    }
}

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.everporter.everporter"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="10"
        android:targetSdkVersion="17" />
    <uses-permission android:name="android.permission.INTERNET"/>

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name="com.everporter.everporter.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>
        <activity
            android:name="com.everporter.everporter.LoginActivity"
            android:label="@string/title_activity_login"
            android:noHistory="true"
            android:excludeFromRecents="true"
            android:windowSoftInputMode="adjustResize|stateVisible" >
        </activity>
        <meta-data android:name="com.facebook.sdk.ApplicationId" android:value="@string/fb_app_id"/>
        <activity android:name="com.facebook.LoginActivity" android:label="@string/app_name"></activity>
    </application>

</manifest>
4

1 回答 1

81

在这个问题上花了一天时间后,我找到了原因:我的活动清单中有 android:noHistory="true" ,这阻止了 Facebook SDK 在会话打开时启动我的活动。呼!!

于 2013-06-26T15:44:36.253 回答