AccountAuthenticatorActivity 未在 AccountManager 中首次添加帐户时被触发

AccountAuthenticatorActivity not getting triggered for adding account for first time in AccountManager

我正在开发基于 XMPP 的 Android 聊天客户端。它在 Socket 连接上工作,并且需要定期 ping 到服务器以保持连接处于活动状态。为此,我正在使用后台 运行 android 服务。

在此服务中创建了一个连接实例。此服务永远存在于后台并执行 ping 和接收消息的工作。

一个问题是当聊天客户端从任务管理器中被终止时,后台服务 RESTARTS(它仍然保持 运行 因为我已经声明它 START-STICKY)。

一旦服务重新启动,它就会清除连接实例。这对我来说是个问题,因为连接实例被清除相当于连接丢失。

如果服务的 onStartCommand 上的连接为空,我将重新创建连接。

但是要建立连接,我需要用户名和密码。我想将这些凭证保存在 Android 的 AccountManager 中。 (我觉得将密码存储在 AccountManager 中比使用 SharedPreferrence 或 SQLite 或 FileSystem 更安全)

1.由于我没有存储任何身份验证令牌,在我的情况下使用 AccountManager 是正确的选择吗?真的比其他存储更安全吗?

2。有什么方法可以像在帐户管理器中存储身份验证令牌那样存储连接实例本身吗?

根据我对客户管理器的阅读,我实现了 AuthenticationService、AccountAuthenticator 和 AccountAuthenticator Activity 如下-

身份验证服务:

public class AuthenticatationService extends Service {

@Override
public IBinder onBind(Intent intent) {
    // TODO Auto-generated method stub
    return new AccountAuthenticator(this).getIBinder();
}
}

帐户验证器:

public class AccountAuthenticator extends AbstractAccountAuthenticator {
Context mContext;
public AccountAuthenticator(Context context) {
    super(context);
    mContext=context;
}

@Override
public Bundle editProperties(AccountAuthenticatorResponse response, String accountType) {
    return null;
}

@Override
public Bundle addAccount(AccountAuthenticatorResponse response, String accountType, String authTokenType, String[] requiredFeatures, Bundle options) throws NetworkErrorException {

    Log.d("Mymsg", "AccountAuthenticator > addAccount() called with " + "response = [" + response + "], accountType = [" + accountType + "], authTokenType = [" + authTokenType + "], requiredFeatures = [" + requiredFeatures + "], options = [" + options + "]" + "---->START");

    final Intent intent = new Intent(mContext, LoginActivity.class);

    intent.putExtra(mContext.getString(R.string.intentdatakey_accounttype), accountType);
    intent.putExtra(mContext.getString(R.string.intentdatakey_authtype), authTokenType);
    intent.putExtra(mContext.getString(R.string.intentdatakey_isaddingnewaccount), true);

    intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response);
    final Bundle bundle = new Bundle();
    bundle.putParcelable(AccountManager.KEY_INTENT, intent);

    Log.d("Mymsg", "AccountAuthenticator > addAccount() returned [bundle: " + bundle + "----> STOP");
    return bundle;
}

@Override
public Bundle confirmCredentials(AccountAuthenticatorResponse response, Account account, Bundle options) throws NetworkErrorException {
    return null;
}

@Override
public Bundle getAuthToken(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options) throws NetworkErrorException {

    Log.d("Mymsg", "AccountAuthenticator > getAuthToken() called with " + "response = [" + response + "], account = [" + account + "], authTokenType = [" + authTokenType + "], options = [" + options + "]" + "---->START");
    // Extract the username and password from the Account Manager, and ask
    // the server for an appropriate AuthToken.
    final AccountManager am = AccountManager.get(mContext);

    String authToken = am.peekAuthToken(account, authTokenType);

    // Lets give another try to authenticate the user
    if (TextUtils.isEmpty(authToken)) {
        final String password = am.getPassword(account);
        if (password != null) {
            authToken = password;
        }
    }

    // If we get an authToken - we return it
    if (!TextUtils.isEmpty(authToken)) {
        final Bundle result = new Bundle();
        result.putString(AccountManager.KEY_ACCOUNT_NAME, account.name);
        result.putString(AccountManager.KEY_ACCOUNT_TYPE, account.type);
        result.putString(AccountManager.KEY_AUTHTOKEN, authToken);
        return result;
    }

    // If we get here, then we couldn't access the user's password - so we
    // need to re-prompt them for their credentials. We do that by creating
    // an intent to display our AuthenticatorActivity.
    final Intent intent = new Intent(mContext, LoginActivity.class);
    intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response);
    intent.putExtra(mContext.getString(R.string.intentdatakey_accounttype), account.type);
    intent.putExtra(mContext.getString(R.string.intentdatakey_authtype), authTokenType);
    final Bundle bundle = new Bundle();
    bundle.putParcelable(AccountManager.KEY_INTENT, intent);

    Log.d("Mymsg", "AccountAuthenticator > getAuthToken() returned [bundle: " + bundle + "----> STOP");
    return bundle;
}

@Override
public String getAuthTokenLabel(String authTokenType) {
    return null;
}

@Override
public Bundle updateCredentials(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options) throws NetworkErrorException {
    return null;
}

@Override
public Bundle hasFeatures(AccountAuthenticatorResponse response, Account account, String[] features) throws NetworkErrorException {
    return null;
}
}

登录Activity:

public class LoginActivity extends AccountAuthenticatorActivity implements View.OnClickListener {

private EditText txtUsername;
private Button btnLogin;
private String username;

private ProgressDialog progressDialog;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_login);

    txtUsername = (EditText) findViewById(R.id.txtUsername);

    btnLogin = (Button) findViewById(R.id.btnLogin);
    btnLogin.setOnClickListener(this);

    progressDialog = new ProgressDialog(this);

    // Connection and Login Response events
    LocalBroadcastManager.getInstance(this).registerReceiver(connectionSuccessfulReceiver,
            new IntentFilter(getString(R.string.broadcastmessage_connectionsuccessful)));
    LocalBroadcastManager.getInstance(this).registerReceiver(connectionFailureReceiver,
            new IntentFilter(getString(R.string.broadcastmessage_connectionfailure)));
}

@Override
public boolean onCreateOptionsMenu(Menu menu) {
    // Inflate the menu; this adds items to the action bar if it is present.
    getMenuInflater().inflate(R.menu.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();

    //noinspection SimplifiableIfStatement
    if (id == R.id.action_settings) {
        return true;
    }

    return super.onOptionsItemSelected(item);
}

@Override
public void onClick(View v) {
    switch (v.getId()) {
        case R.id.btnLogin:

            Log.d("Mymsg", "LoginActivity > onClick() called with " + "v = [" + v + "]" + "---->START");

            username = txtUsername.getText().toString();

            if (username.isEmpty()) {
                Toast.makeText(this, "Please enter username", Toast.LENGTH_LONG);
                return;
            }

            progressDialog.setMessage("Logging In...");
            progressDialog.setCancelable(false);
            progressDialog.show();

            final Intent res = new Intent();
            res.putExtra(AccountManager.KEY_ACCOUNT_NAME, username);
            res.putExtra(AccountManager.KEY_ACCOUNT_TYPE, getString(R.string.accounttype));
            res.putExtra(AccountManager.KEY_AUTHTOKEN, username);
            res.putExtra(AccountManager.KEY_PASSWORD, username);

            final Account account = new Account(username, getString(R.string.accounttype));
            if (getIntent().getBooleanExtra(getString(R.string.intentdatakey_isaddingnewaccount), false)) {
                String authtoken = username;
                String authtokenType = getString(R.string.authtype);
                // Creating the account on the device and setting the auth token we got
                // (Not setting the auth token will cause another call to the server to authenticate the user)
                AccountManager.get(this).addAccountExplicitly(account, username, null);
                AccountManager.get(this).setAuthToken(account, authtokenType, authtoken);
            } else {
                AccountManager.get(this).setPassword(account, username);
            }
            setAccountAuthenticatorResult(res.getExtras());
            setResult(RESULT_OK, res);

            Log.d("Mymsg", "LoginActivity > onClick() ----> STOP");

            break;
        default:
            break;
    }
}

/**
 * On Connection Successful
 */
public void onConnectionSuccessful() {
    Log.d("Mymsg", "LoginActivity > onConnectionSuccessful() called with " + "" + "---->START");

    runOnUiThread(new Runnable() {
        @Override
        public void run() {
            if (progressDialog.isShowing()) {
                progressDialog.dismiss();
            }
        }
    });

    finish();
    Log.d("Mymsg", "LoginActivity > onConnectionSuccessful() ----> STOP");
}

/**
 * On Connection Failure
 *
 * @param message- error message
 */
public void onConnectionFailure(final String message) {
    Log.d("Mymsg", "LoginActivity > onConnectionFailure() called with " + "message = [" + message + "]" + "---->START");
    runOnUiThread(new Runnable() {
        @Override
        public void run() {

            if (progressDialog.isShowing()) {
                progressDialog.dismiss();
            }

            Toast.makeText(LoginActivity.this, "Failed connecting: " + message, Toast.LENGTH_LONG).show();
        }
    });

    Log.d("Mymsg", "LoginActivity > onConnectionFailure() ----> STOP");
}


/*
* Recievers for Connection Events
 */
private BroadcastReceiver connectionSuccessfulReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        Log.d("Mymsg", "LoginActivity > connectionSuccessfulReceiver > onReceive() called with " + "context = [" + context + "], intent = [" + intent + "]" + "---->START");
        onConnectionSuccessful();
        Log.d("Mymsg", "LoginActivity > connectionSuccessfulReceiver > onReceive() returned void ----> STOP");
    }
};

private BroadcastReceiver connectionFailureReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        Log.d("Mymsg", "LoginActivity > connectionFailureReceiver > onReceive() called with " + "context = [" + context + "], intent = [" + intent + "]" + "---->START");
        onConnectionFailure(intent.getStringExtra(getString(R.string.intentdatakey_failuremessage)));
        Log.d("Mymsg", "LoginActivity > connectionFailureReceiver > onReceive() ----> STOP");
    }
};
}

在每个 activity 的简历上,我检查了连接是否为空,如果是,我已经启动了后台服务(我之前提到过它以定期保持连接和 ping)。如果 AccountManager 中存在 Account,此服务将建立连接并向服务提供回调。

3。我面临的问题是用户名和密码尚未出现在 AccountManager 中。根据我的理解,getAuthToken 是 Authenticator 中的入口点,如果不存在身份验证令牌,则调用登录 activity。但是,如果用户名不存在,如何调用 getAuthToken。需要建议。

以下是我的后台服务-

 public class XMPPListener extends Service implements ChatMessageListener, ChatManagerListener, ConnectionListener {

private static XMPPListener instance;
private NotificationCompat.Builder mBuilder;
private NotificationManager mNotificationManager;
private Context mContext;
private AbstractXMPPConnection connection;
private static boolean isConnected = false;
private static boolean isLoggedIn = false;
private User user;

public XMPPListener() {
    Log.d("Mymsg", "XMPPListener > XMPPListener() called with " + "" + "---->START");
    mContext = this;
    instance = this;
    Log.d("Mymsg", "XMPPListener > XMPPListener() > this: " + this);

    //To show notification when app is not foreground
    mBuilder = new NotificationCompat.Builder(mContext);

    Log.d("Mymsg", "XMPPListener > XMPPListener() ----> STOP");
}

/**
 * Get Singleton instance
 *
 * @return instance
 */
public static XMPPListener getInstance(Context context) {

    return instance;
}

@Override
public void processMessage(Chat chat, Message message) {
    Log.d("Mymsg", "XMPPListener > processMessage() called with " + "message = [" + message + "]" + "---->START");

    try {
        // Make entry of message in local database
        makeEntryOfMessageInLocalDatabase(chat, message);

        // Show Notification
        showNotification(chat, message);

        // Broadcast message that new message received
        LocalBroadcastManager.getInstance(mContext).sendBroadcast(new Intent(getString(R.string.localbroadcastaction_messagereceived)));
    } catch (SQLException e) {
        e.printStackTrace();
    }

    Log.d("Mymsg", "XMPPListener > processMessage() returned void ----> STOP");
}

/**
 * Show Notification
 *
 * @param chat
 * @param message
 */
private void showNotification(Chat chat, Message message) {
    mBuilder.setSmallIcon(R.drawable.notification_icon);
    mBuilder.setContentTitle("@" + chat.getParticipant() + " messaged");
    mBuilder.setContentText(message.getBody());

    Intent resultIntent = new Intent(mContext, ChatWindowActivity.class);
    TaskStackBuilder stackBuilder = TaskStackBuilder.create(mContext);
    stackBuilder.addParentStack(ChatWindowActivity.class);

    // Adds the Intent that starts the Activity to the top of the stack
    stackBuilder.addNextIntent(resultIntent);
    PendingIntent resultPendingIntent = stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
    mBuilder.setContentIntent(resultPendingIntent);

    // notificationID allows you to update the notification later on.
    mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
    mNotificationManager.notify(1, mBuilder.build());
}

/**
 * Make entry of message in Local Database
 *
 * @param chat
 * @param message
 * @throws SQLException
 */
private void makeEntryOfMessageInLocalDatabase(Chat chat, Message message) throws SQLException {

    Log.d("Mymsg", "XMPPListener > makeEntryOfMessageInLocalDatabase() called with " + "chat = [" + chat + "], message = [" + message + "]" + "---->START");

    ChatMessage chatMessage = new ChatMessage();

    // This is received message
    chatMessage.setSent(false);

    chatMessage.setThreadId(chat.getThreadID());
    chatMessage.setChatmessage(message.getBody());
    chatMessage.setTimeStamp(new Date());
    chatMessage.setIsRead(false);

    ChatThread chatThread = new ChatThread();
    chatThread.setThreadId(chat.getThreadID());
    chatThread.setUsername(chat.getParticipant());
    chatThread.setLastChatmessage(message.getBody());
    chatThread.setLastMessageTimeStamp(new Date());
    chatThread.setLastMessageSent(false);
    chatThread.setUnreadMessageCount(SampleChatClientAppDataOperations.getInstance(mContext).getUnReadMessageCountInThread(chat.getThreadID()));

    // Make database entry
    SampleChatClientAppData sampleChatClientAppData = new SampleChatClientAppData(mContext);

    // Update messages table
    sampleChatClientAppData.getChatMessageDao().create(chatMessage);

    // Update Chat list table
    sampleChatClientAppData.getChatThreadDao().update(chatThread);

    Log.d("Mymsg", "XMPPListener > makeEntryOfMessageInLocalDatabase() returned void ----> STOP");
}

@Override
public IBinder onBind(Intent intent) {
    return null;
}

@Override
public void onCreate() {
    Log.d("Mymsg", "XMPPListener > onCreate() called with " + "" + "---->START");
    super.onCreate();

    Log.d("Mymsg", "XMPPListener > onCreate() ----> STOP");
}

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    Log.d("Mymsg", "XMPPListener > onStartCommand() called with " + "intent = [" + intent + "], flags = [" + flags + "], startId = [" + startId + "]" + "---->START");

    if (connection == null || !connection.isConnected() || !connection.isAuthenticated()) {
        new GetAuthToken().execute();
    }

    int returnValue = super.onStartCommand(intent, flags, startId);
    Log.d("Mymsg", "XMPPListener > onStartCommand() returned [returnValue: " + returnValue + "----> STOP");
    return returnValue;
}

@Override
public void onTaskRemoved(Intent rootIntent) {
    Log.d("Mymsg", "XMPPListener > onTaskRemoved() called with " + "rootIntent = [" + rootIntent + "]" + "---->START");
    super.onTaskRemoved(rootIntent);
    Log.d("Mymsg", "XMPPListener > onTaskRemoved() ----> STOP");
}

@Override
public void onDestroy() {
    Log.d("Mymsg", "XMPPListener > onDestroy() called with " + "" + "---->START");
    super.onDestroy();
    Log.d("Mymsg", "XMPPListener > onDestroy() ----> STOP");
}

/**
 * Getter for connection
 *
 * @return instance of connection
 */
public AbstractXMPPConnection getConnection() {
    return connection;
}

@Override
public void chatCreated(final Chat chat, boolean createdLocally) {

    Log.d("Mymsg", "XMPPListener > chatCreated() called with " + "chat = [" + chat + "], createdLocally = [" + createdLocally + "]" + "---->START");

    if (!createdLocally) {

        // Register listener
        chat.addMessageListener(XMPPListener.getInstance(this));


        checkIfChatThreadExistForUser(chat.getParticipant(), new Handler() {
            @Override
            public void handleMessage(android.os.Message msg) {
                Log.d("Mymsg", "XMPPListener > checkIfChatThreadExistForUser > handleMessage() called with " + "msg = [" + msg + "]" + "---->START");
                super.handleMessage(msg);
                ArrayList<ChatThread> chatThread;
                try {
                    chatThread = (ArrayList<ChatThread>) msg.obj;

                    Log.d("Mymsg", "chatThread: " + chatThread);

                    if (chatThread.isEmpty()) {
                        // If first time chatting with this user create new thread;
                        createChatThreadInLocalDb(chat);
                    }

                } catch (ClassCastException e) {
                    e.printStackTrace();
                }

                Log.d("Mymsg", "XMPPListener > checkIfChatThreadExistForUser > handleMessage() returned null----> STOP");
            }
        }, new Handler() {
            @Override
            public void handleMessage(android.os.Message msg) {
                super.handleMessage(msg);
            }
        });
    }


    Log.d("Mymsg", "XMPPListener > chatCreated() returned void----> STOP");
}

/**
 * Create chat thread in local db
 *
 * @param chat
 */
public void createChatThreadInLocalDb(Chat chat) {

    Log.d("Mymsg", "XMPPListener > createChatThreadInLocalDb() called with " + "chat = [" + chat + "]" + "---->START");

    ChatThread newChatThread = new ChatThread();
    newChatThread.setUsername(chat.getParticipant());
    newChatThread.setThreadId(chat.getThreadID());

    Datasource.addChatThread(this, newChatThread, new Handler() {
        @Override
        public void handleMessage(android.os.Message msg) {

            Log.d("Mymsg", "XMPPListener > addChatThread > createChatThreadInLocalDb > handleMessage() called with " + "msg = [" + msg + "]" + "---->START");
            super.handleMessage(msg);

            Integer newChatThreadCreated = 0;

            try {
                newChatThreadCreated = (Integer) msg.obj;

                if (newChatThreadCreated > 0) {

                    // Broadcast that chat is created
                    LocalBroadcastManager.getInstance(XMPPListener.this).sendBroadcast(new Intent(getString(R.string.localbroadcastaction_chatcreated)));
                }
            } catch (ClassCastException e) {
                e.printStackTrace();
            }

            Log.d("Mymsg", "XMPPListener > addChatThread > handleMessage() ----> STOP");
        }
    }, new Handler() {
        @Override
        public void handleMessage(android.os.Message msg) {
            super.handleMessage(msg);
        }
    });

    Log.d("Mymsg", "XMPPListener > createChatThreadInLocalDb() returned null----> STOP");
}

/**
 * Checks if thread exist for the username else
 * create new one
 *
 * @param fullUsername
 */
public void checkIfChatThreadExistForUser(final String fullUsername, final Handler successHandler, final Handler failureHandler) {
    Datasource.getChatThreadOfUser(this, fullUsername, successHandler, failureHandler);
}

// Internal sync task for connecting to XMPP server
class ConnectXMPP extends AsyncTask<User, Void, Exception> {

    @Override
    protected Exception doInBackground(User... params) {

        Log.d("Mymsg", "ConnectXMPP > doInBackground() called with " + "params = [" + params + "]" + "---->START");

        User user = params[0];

        Log.d("Mymsg", "user: " + user);

        XMPPTCPConnectionConfiguration config = XMPPTCPConnectionConfiguration.builder()
                .setUsernameAndPassword(user.getUsername(), user.getPassword())
                .setServiceName(mContext.getString(R.string.openfire_host))
                .setSecurityMode(ConnectionConfiguration.SecurityMode.disabled)
                .setDebuggerEnabled(true)
                .build();

        SASLAuthentication.blacklistSASLMechanism("SCRAM-SHA-1");
        SASLAuthentication.blacklistSASLMechanism("DIGEST-MD5");
        SASLAuthentication.unBlacklistSASLMechanism("PLAIN");

        connection = new XMPPTCPConnection(config);

        try {
            connection.connect();
            isConnected = connection.isConnected();
            Log.d("Mymsg", "isConnected: " + isConnected);

            connection.login();
            isLoggedIn = connection.isAuthenticated();
            Log.d("Mymsg", "isLoggedIn: " + isLoggedIn);

            connection.addConnectionListener(XMPPListener.this);

            // Broadcast message that new message received
            LocalBroadcastManager.getInstance(mContext).sendBroadcast(new Intent(getString(R.string.broadcastmessage_connectionsuccessful)));

        } catch (SmackException e) {
            e.printStackTrace();
            Intent broadcastIntent = new Intent(getString(R.string.broadcastmessage_connectionfailure));
            broadcastIntent.putExtra(getString(R.string.intentdatakey_failuremessage), e.getMessage());
            LocalBroadcastManager.getInstance(mContext).sendBroadcast(broadcastIntent);
        } catch (IOException e) {
            e.printStackTrace();
            Intent broadcastIntent = new Intent(getString(R.string.broadcastmessage_connectionfailure));
            broadcastIntent.putExtra(getString(R.string.intentdatakey_failuremessage), e.getMessage());
            LocalBroadcastManager.getInstance(mContext).sendBroadcast(broadcastIntent);
        } catch (XMPPException e) {
            e.printStackTrace();
            Intent broadcastIntent = new Intent(getString(R.string.broadcastmessage_connectionfailure));
            broadcastIntent.putExtra(getString(R.string.intentdatakey_failuremessage), e.getMessage());
            LocalBroadcastManager.getInstance(mContext).sendBroadcast(broadcastIntent);
        }

        return null;
    }

    @Override
    protected void onPostExecute(Exception e) {
        super.onPostExecute(e);
    }
}

class GetAuthToken extends AsyncTask<Void, Void, Exception> {

    private User user;

    @Override
    protected Exception doInBackground(Void... params) {

        String username = "";
        String password = "";

        AccountManager accountManager = AccountManager.get(XMPPListener.this);
        final Account[] accounts = accountManager.getAccountsByType(getString(R.string.accounttype));

        Account account;
        if (accounts.length <= 0) {
            account = new Account(username, getString(R.string.accounttype));

        } else {
            account = accounts[0];
        }

        try {
            Bundle bundle = accountManager.getAuthToken(account, mContext.getString(R.string.authtype), true, null, null).getResult();
            password = (String) bundle.get(AccountManager.KEY_AUTHTOKEN);
            Log.d("Mymsg", "password: " + password);
            username = password;
        } catch (OperationCanceledException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (AuthenticatorException e) {
            e.printStackTrace();
        }

        if (username.isEmpty() || password.isEmpty()) {
            Intent broadCastIntent = new Intent(getString(R.string.broadcastmessage_connectionfailure));
            broadCastIntent.putExtra(getString(R.string.intentdatakey_failuremessage), getString(R.string.errormessage_usercredentialmissing));
            LocalBroadcastManager.getInstance(mContext).sendBroadcast(broadCastIntent);
            return null;
        }

        user = new User();
        user.setUsername(username);
        user.setPassword(password);

        return null;
    }

    @Override
    protected void onPostExecute(Exception e) {
        super.onPostExecute(e);

        if (user != null) {
            // Connect XMPP
            new ConnectXMPP().execute(user);
        }
    }
}
}

1. Since I am not storing any authentication token, is using AccountManager is right choice in my case? Is it really safer than other storages?

它本身并不安全。客户经理只是将用户名、密码和授权令牌存储在 SQLite 数据库的 "plaintext" 中。 因此,如果您拥有 root 访问权限,通常可以轻松访问这些值。

如果您打算使用 sync-adapter 机制,或者如果您打算同步到某些内容提供商(如联系人提供商或日历提供商),则必须拥有身份验证器。

除此之外,它还提供了对多个帐户的简单支持,并提供了一个干净的界面来检索身份验证凭据。后者很好地将帐户 management/authentication 部分与应用程序的其余部分分离。

回答第一个问题:视情况而定。如果您不熟悉 Authenticator 并且只是寻求更好的安全性,那么最好不要使用 Authenticator。

3. Issue I am facing is when Username and Password is not already present in AccountManager. True to my understanding getAuthToken is entry point in Authenticator, if auth token in not present then login activity is called. However if username is not present how to call getAuthToken. Need suggestions.

事情不是这样的。您只能为现有帐户调用 getAuthToken。如果您的身份验证器 return 是 IntentgetAuthToken 只会调用 activity。这样做的一个用例是在使用 OAuth2 并且用户撤消了对您的应用程序的访问权限时。在这种情况下,您需要提示用户再次授予访问权限。

请注意,其他应用也可能会请求 auth-tokens(前提是它们具有所需的权限),因此请确保您没有 return 纯文本密码作为 auth-token 或确保其他应用不会从您的身份验证器收到令牌。

因此,在调用 getAuthToken 时从您的身份验证器 return 发送 Intent 的另一个用例是,如果您想提示用户获得许可。

所以这是如何完成的:

您的应用应首先检查是否存在现有帐户(使用 getAccountsByType). If there is one you can call getAuthToken, if there are multiple accounts you usually ask the user which one to use if he didn't select any before, then you call getAuthToken for that account, otherwise call addAccount 让用户先添加一个帐户。

如果您不想让其他应用访问您的纯文本密码,请考虑使用 setPassword and getPassword 而不是使用 authtoken。密码对您的应用程序是私有的,其他应用程序无法检索(好吧,至少这更难)。或者考虑根本不使用 Authenticator。在您的情况下增加复杂性可能不值得。