1. 程式人生 > >Android如何在賬戶設定中新增App的賬戶

Android如何在賬戶設定中新增App的賬戶

Android系統為外部服務提供了賬號登入的機制,用於同步資料等作用。
進入設定->賬戶->新增賬戶,即可看到目前手機上有哪些App提供了同步服務。
這裡寫圖片描述

接下來將會演示如何在App中定義登入服務並新增一個登入選項到這裡。

賬戶的授權和同步

賬戶功能主要有2個:授權和同步。

授權:即賬戶登入怎麼把關,可以通過驗證賬戶密碼的形式,也允許直接通過AccountManager的addAcount介面直接新增賬戶。
需要繼承AbstractAccountAuthenticator來實現我們自己的授權機制。

同步:以郵箱賬戶為例,在新增一個Exchange郵箱賬戶後,我們可以看到它提供了聯絡人,日曆,郵件等同步功能,這些同步服務每一項都需要App來繼承實現AbstractThreadedSyncAdapter,用於該項的同步。
這裡寫圖片描述

簡單的演示

如何新增登入入口

第一步:實現AbstractAccountAuthenticator

在res/xml下新增一個xml,用於定義account-authenticator的以下屬性:
sample_authenticator.xml

<account-authenticator
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:accountType="com.android.test" 
    android:icon="@drawable/ic_account_icon"
android:smallIcon="@drawable/ic_account_icon" android:label="@string/account_title"/>

accountType -> 自定義的一個串,一般為包名
icon -> 圖示
smallIcon -> 小圖示,可能用於顯示在狀態列等空間較小的地方
label -> 顯示的賬戶名

接下來實現一個實現一個AbstractAccountAuthenticator,重點是實現addAccount方法的行為,現在先忽略具體實現。


public class TestAuthenticator
extends AbstractAccountAuthenticator {
Context mContext; AccountManager mManager; public TestAuthenticator(Context context) { super(context); mContext = context; mManager = AccountManager.get(context); } @Override public Bundle editProperties(AccountAuthenticatorResponse accountAuthenticatorResponse, String s) { throw new UnsupportedOperationException(); } @Override public Bundle addAccount(AccountAuthenticatorResponse accountAuthenticatorResponse, String s, String s1, String[] strings, Bundle bundle) throws NetworkErrorException { //這裡可以返回包含Login介面Intent的Bunble,在點選新增這個賬戶後就能進入我們自定義的登入介面 return null; } @Override public Bundle confirmCredentials(AccountAuthenticatorResponse accountAuthenticatorResponse, Account account, Bundle bundle) throws NetworkErrorException { return null; } @Override public Bundle getAuthToken(AccountAuthenticatorResponse accountAuthenticatorResponse, Account account, String s, Bundle bundle) throws NetworkErrorException { return null; } @Override public String getAuthTokenLabel(String s) { return null; } @Override public Bundle updateCredentials(AccountAuthenticatorResponse accountAuthenticatorResponse, Account account, String s, Bundle bundle) throws NetworkErrorException { return null; } @Override public Bundle hasFeatures(AccountAuthenticatorResponse accountAuthenticatorResponse, Account account, String[] strings) throws NetworkErrorException { return null; } }

第二步:實現Service和宣告許可權

實現一個Service,AccountManagerService需要通過Service來獲取Authenticator中的的Binder,通過Binder回撥來獲取到我們自定義的登入行為。
在onBind中,我們需要返回TestAuthenticator中的IBinder:


public class TestAuthenticateService extends Service {
    TestAuthenticator mAuthenticator;

    @Override
    public void onCreate() {
        super.onCreate();
        mAuthenticator = new TestAuthenticator(this.getApplicationContext());
    }

    @Override
    public IBinder onBind(Intent intent) {
        //限制了只有在AccountManagerService繫結service時才返回Authenticator的binder
        if (AccountManager.ACTION_AUTHENTICATOR_INTENT.equals(intent.getAction())) {
            return mAuthenticator.getIBinder();
        } else {
            return null;
        }
    }
}

第三步:新增宣告和許可權到Manifest

既然實現了Service,就需要在Manifest中新增宣告,順便也將賬戶相關的許可權新增進來:

        <!--格式都是固定的,必須宣告intent-filter和meta-data-->
        <service android:name=".TestAuthenticateService"
            android:exported="true">
        <intent-filter>
            <action android:name="android.accounts.AccountAuthenticator"/>
        </intent-filter>
        <!--meta-data resource指向第一步中定義的sample_authenticator.xml-->
        <meta-data android:name="android.accounts.AccountAuthenticator"
            android:resource="@xml/sample_authenticator" />
        </service>

許可權:

<uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS" />

現在進入賬號設定介面,就能看到效果了:
這裡寫圖片描述

但是現在還沒有實現登入功能,並沒有什麼用。

第四步:實現登入介面

先實現一個普通的Activity,包含賬號密碼輸入框和登入按鈕。(這是普通的Activity,別忘了在Manifest中宣告)
因為這裡只是示例,所以點選登入按鈕後,直接呼叫AccountManager的介面將賬戶新增進來。

//LoginActivity.java
mBtnLogin.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                //指定Account的type和name
                Account account = new Account("TestAccount", "com.android.test");
                //新增賬戶
                AccountManager.get(getApplicationContext()).addAccountExplicitly(account, "", null);
            }
        });

那麼這個Activity啟動條件是什麼呢?
回顧第一步,在我們實現的AbstractAccountAuthenticator中有一個addAccount方法還沒有具體實現,在addAccount中返回包含啟動LoginActivity Intent的Bundle,就可以啟動我們的登入介面了:

@Override
    public Bundle addAccount(AccountAuthenticatorResponse accountAuthenticatorResponse, String s, String s1, String[] strings, Bundle bundle) throws NetworkErrorException {
        Bundle b = new Bundle();
        Intent i = new Intent(mContext, LoginActivity.class);
        b.putParcelable(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, accountAuthenticatorResponse);//回撥
        b.putParcelable(AccountManager.KEY_INTENT, i);
        return b;
    }

最終效果:
這裡寫圖片描述

新增同步功能

有了登入功能,如何新增同步功能呢?
與上面的步驟類似,這次我們需要繼承實現AbstractThreadedSyncAdapter,

第一步:實現AbstractThreadedSyncAdapter

在xml目錄下新建檔案並寫入sync-adapter的描述:
sync-adapter.xml

<sync-adapter xmlns:android="http://schemas.android.com/apk/res/android"
    android:accountType="com.android.test"
    android:contentAuthority="com.android.contacts"
    />

accountType ->賬戶的type,需要與前面定義的一致
contentAuthority ->需要同步的資料,必須是有提供Provider供外部訪問的模組,如聯絡人(com.android.contacts),瀏覽器(”com.android.browser”),日曆(”com.android.calendar”)等,也可以是自己實現的provider。

還有以下屬性,此處不一一深究。

     android:userVisible="true|false"
     android:supportsUploading="true|false"
     android:allowParallelSyncs="true|false"
     android:isAlwaysSyncable="true|false"
     android:syncAdapterSettingsAction="ACTION_OF_SETTINGS_ACTIVITY"
public class SyncAdapter extends AbstractThreadedSyncAdapter {
    private static final String TAG = "SyncAdapter";

    public SyncAdapter(Context context, boolean autoInitialize) {
        super(context, autoInitialize);
    }

    @Override
    public void onPerformSync(Account account, Bundle extras, String authority,
                              ContentProviderClient provider, SyncResult syncResult) {
        //實現同步的具體處理
    }
}

第二步:實現Service

實現一個普通的Service,負責建立SyncAdapter,並在onBind時返回Ibinder給AccountManagerService。

public class TestSyncService extends Service {

        private static final Object sLock = new Object();
        private static SyncAdapter sSyncAdapter = null;

        @Override
        public void onCreate() {
            Log.d(TAG, "onCreate");
            synchronized (sLock) {
                if (null == sSyncAdapter) {
                    sSyncAdapter = new SyncAdapter(this, true);
                }
            }
        }

        @Override
        public IBinder onBind(Intent intent) {
            return sSyncAdapter.getSyncAdapterBinder();
        }

}

第三部:Manifest宣告和許可權

宣告Service,固定格式:

        <service
            android:name=".TestSyncService"
            android:exported="true">
            <intent-filter>
                <action
                    android:name="android.content.SyncAdapter" />
            </intent-filter>
            <!--resource指向第一步定義的xml檔案-->
            <meta-data
                android:name="android.content.SyncAdapter"
                android:resource="@xml/sync_adapter" />
        </service>

新增讀取同步資料的許可權,本例為聯絡人:

    <uses-permission
            android:name="android.permission.READ_SYNC_SETTINGS" />
    <uses-permission
            android:name="android.permission.WRITE_SYNC_SETTINGS" />

效果如下:
這裡寫圖片描述

至於如何實現同步的具體功能,此處不再鋪開。

如何獲取Account

判斷是否已經添加了本APP的Account

    private boolean queryAccountStatus(){
        if(findAccount("TestAccount", "com.android.test") != null){
            return true;
        }
        return false;
    }


    private Account findAccount(String accountName, String accountType) {
        for (Account account : AccountManager.get(this).getAccounts()) {
            return account;
        }
        return null;
    }

查詢有哪些SyncAdapter

        final AccountManager am = AccountManager.get(this);
        final SyncAdapterType[] syncs = ContentResolver.getSyncAdapterTypes();

        for (SyncAdapterType sync : syncs) {
            Log.d(LOG_TAG, "syncs account:" + sync.accountType);

        }

AccountManager部分原始碼分析

Account資訊存在哪?

Account和SyncAdapter資訊都是以xml的形式,分別存放到/data/system/users/[user_id]/registered_services 目錄下面:

x0:/data/system/users/0/registered_services # ls
android.accounts.AccountAuthenticator.xml 
android.content.SyncAdapter.xml 

android.accounts.AccountAuthenticator.xml檔案用於儲存可登入的賬戶:

x0:/data/system/users/0/registered_services # cat android.accounts.AccountAuthenticator.xml

<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<services>
    <service uid="10063" type="com.android.exchange" />
    <service uid="10140" type="com.sina.weibo.account" />
    <service uid="10063" type="com.android.email" />
    <service uid="10162" type="com.qihoo.pctrl.keepalive.account" />
    <service uid="10152" type="ludashi.daemon" />
    <service uid="10159" type="com.icoolme.weather.authaccount" />
    <service uid="10063" type="com.android.email.pop3" />
</services>

android.content.SyncAdapter.xml儲存的是有宣告Sync Adatper的服務:

le_x10:/data/system/users/0/registered_services # cat android.content.SyncAdapter.xml     

<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<services>
    <service uid="10063" authority="com.android.calendar" accountType="com.android.exchange" />
    <service uid="10063" authority="com.android.contacts" accountType="com.android.exchange" />
    <service uid="10162" authority="com.qihoo.pctrl.keepalive.account.SyncProvider" accountType="com.qihoo.pctrl.keepalive.account" />
   ...
</services>

這些資料由實現了抽象類RegisteredServicesCacheAccountAuthenticatorCacheSyncAdaptersCache來管理:

public abstract class RegisteredServicesCache<V>

public class SyncAdaptersCache extends RegisteredServicesCache<SyncAdapterType>

class AccountAuthenticatorCache extends RegisteredServicesCache<AuthenticatorDescription> implements IAccountAuthenticatorCache

來看下RegisteredServicesCache中實現讀寫和解析xml的介面。
外部通過介面獲取SyncAdapters 或AccountAuthenticator服務時,RegisteredServicesCache內部都會呼叫findOrCreateUserLocked介面獲取xml中的Service記錄。

//RegisteredServicesCache.java
public abstract class RegisteredServicesCache<V> {
    //指定目錄名
    protected static final String REGISTERED_SERVICES_DIR = "registered_services";
    private final XmlSerializerAndParser<V> mSerializerAndParser;
    ...
    //獲取所有Service的介面
    private UserServices<V> findOrCreateUserLocked(int userId, boolean loadFromFileIfNew) {
        //從Cache中獲取
        UserServices<V> services = mUserServices.get(userId);
        if (services == null) { //如沒有Cache,則從xml檔案中獲取
            services = new UserServices<V>();
            mUserServices.put(userId, services);
            if (loadFromFileIfNew && mSerializerAndParser != null) {
                UserInfo user = getUser(userId);
                if (user != null) {
                    //1. 獲取xml檔案
                    AtomicFile file = createFileForUser(user.id);
                    if (file.getBaseFile().exists()) {
                        InputStream is = null;
                        try {
                            is = file.openRead();
                            //2. 讀取並解析xml檔案,將結果寫入UserServices Map中
                            readPersistentServicesLocked(is);
                        } catch (Exception e) {
                        } finally {
                            IoUtils.closeQuietly(is);
                        }
                    }
                }
            }
        }
        return services;
    }
}

來展開程式碼註釋中的第一點。
createFileForUser方法裡做了檔案路徑組裝並返回對應的File,可以注意到檔名mInterfaceName是變數,這個變數對應的就是xml的檔名,它由RegisteredServicesCache的子類AccountAuthenticatorCacheSyncAdaptersCache來決定,xml的名字也就是上面adb shell演示中的2個檔案。

    //RegisteredServicesCache.java
    private AtomicFile createFileForUser(int userId) {
        File userDir = getUserSystemDirectory(userId);
        //mInterfaceName 是變數
        File userFile = new File(userDir, REGISTERED_SERVICES_DIR + "/" + mInterfaceName + ".xml");
        return new AtomicFile(userFile);
    }

    protected File getUserSystemDirectory(int userId) {
        return Environment.getUserSystemDirectory(userId);
    }

SyncAdaptersCache對應的xml檔名為android.content.SyncAdapter

//SyncAdaptersCache.java
    private static final String SERVICE_INTERFACE = "android.content.SyncAdapter";
    private static final String SERVICE_META_DATA = "android.content.SyncAdapter";
    private static final String ATTRIBUTES_NAME = "sync-adapter";

    public SyncAdaptersCache(Context context) {
        super(context, SERVICE_INTERFACE, SERVICE_META_DATA, ATTRIBUTES_NAME, sSerializer);
    }

AccountAuthenticatorCache則為android.accounts.AccountAuthenticator

    //AccountAuthenticatorCache.java
    public static final String ACTION_AUTHENTICATOR_INTENT =
            "android.accounts.AccountAuthenticator";
    public static final String AUTHENTICATOR_META_DATA_NAME =
            "android.accounts.AccountAuthenticator";
    public static final String AUTHENTICATOR_ATTRIBUTES_NAME = "account-authenticator";

    public AccountAuthenticatorCache(Context context) {
        super(context, AccountManager.ACTION_AUTHENTICATOR_INTENT,
                AccountManager.AUTHENTICATOR_META_DATA_NAME,
                AccountManager.AUTHENTICATOR_ATTRIBUTES_NAME, sSerializer);
    }

xml檔案拿到之後,接下來就是xml的解析過程,因為xml本身標籤並不多,就不貼程式碼了,主要就是將uid和service name拿出來,組成UserServices物件的Map返回給呼叫者。

Account的其他“妙用”——提高程序存活率

利用Android程序回收策略對Account同步程序的“照顧”,可以通過新增Account使用同步機制來提高程序的存活率。(可能在AndroidN之後不再有用)
提高程序存活率,詳情可以參考這篇文章:
《一種提高Android應用程序存活率新方法》

一直納悶這兩兄弟躲在系統賬號裡的目的,現在猜到個大概了。。。
這裡寫圖片描述

相關推薦

Android如何在賬戶設定新增App賬戶

Android系統為外部服務提供了賬號登入的機制,用於同步資料等作用。 進入設定->賬戶->新增賬戶,即可看到目前手機上有哪些App提供了同步服務。 接下來將會演示如何在App中定義登入服務並新增一個登入選項到這裡。 賬戶的授權和同步

android系統原始碼新增app原始碼(原始碼部署移植)

涉及到系統定製,需要在系統中加入自己的apk工程,但是上網找了很多資料都是不夠全面的,或者看了還是沒搞懂,我自己也是一點點摸索過來的,花了不少的時間,也是踩了不少的坑,因此特開一文,幫助大家渡河。 申明,本文親測有效,如果有疑問,歡迎在下方留言。人人為我,我為

Android在activity新增背景

android:background="@drawable/ming" 這個是我們在佈局檔案中新增的,就是下面的其中一條 xmlns:tools="http://schemas.android.co

RK3288Android5.1系統在設定新增隱藏和顯示導航欄功能

1.需求 應客戶需求,在android系統設定中新增一個設定選項,該選項中新增一個開關功能,用於顯示和隱藏系統底部導航欄。 2.分析 首先當然是有系統原始碼了,RK3288,5.1系統原始碼一份。 然後就是修改系統設定app,即Settings.apk的原始碼。 再

如何向android手機通訊錄新增聯絡人

直接在手機的通訊錄的資料庫中新增列表 相關程式碼如下 package com.example.test; import android.support.v7.app.ActionBarActivity; import android.os.Bundle; import

Android應用:SurfaceView新增控制元件

上次說了 如何使用SurfaceView,文章連結: 但如何在SurfaceView中新增控制元件呢? 1、首先,將SurfaceView的建構函式修改為兩個引數的 public MyView(Context context, AttributeSet attrs)

android studio 選單app執行按鈕上有個叉號,原因與解決辦法

在android studio寫程式碼中,直接建立專案,寫程式碼然後執行是不會一般是不會出現這樣的問題的,但是一旦更改主Activity,而不跟著手動更改AndroidManifest.xml中的activity配置,則會出現這樣的情況,這就是提醒我們沒有了主Activit

android 專案開發遇到app主題無法指向style

今天換了主機,將原來程式碼遷移到新的主機上,通過行動硬碟開,不知道為何出現部分class亂碼的問題,通過github實現程式碼移植,直接打開出現app無法執行,類似app主題要加Base的這種問題,在清單檔案中發現,app主題無法指向style,解決方法,close proj

Android設定裡面新增新功能的方法

1./usr/smdt/self6000/android/packages/apps/Settings/res/xml/device_info_settings.xml 中增加節點: <!-- Detailed build version-->        

android 通過設定cookie解決app 登入後WebView還要重新登陸問題

問題描述:因為需要在app里加入html,所以使用了webView,但是第一次進入webview時,需要在webview頁面重新登陸,為了解決這一問題花了大量的時間,所以分享給遇到問題的朋友們。 入下的方法是通過設定cookie來解決 在 webView.l

android settings.db資料庫新增一項新的設定

 Settings資料存放在com.android.providers.settings/databases/settings.db 中   資料庫中資料的預設資料在frameworks/base/packages/SettingsProvider/res/values

AndroidApp設定NoActionBar/FullScreen

因為現在博主在學習安卓,有的時候難免會有設定整個程式無標題和全屏的要求,在百度上搜索有好幾個 解決辦法,但是有的時候的那些辦法好用,有的時候不好用。 現在找到一個辦法,博主自己測試時屢試不爽的,特別好用,現在分享出來,希望對和博主一樣的安卓初學者有幫助。

Android Calendar新增本地賬戶

在Android原生程式碼中,日曆App如要新增活動,需要先新增賬戶,不方便使用者的使用。反編譯某某系統的CalendarProvider.apk,從中提取了新增本地賬戶的程式碼,在此共享。 主要修改了/packages/providers/CalendarP

Android app新增facebook原生廣告,應該注意的坑

在app中新增facebook廣告,由於facebook廣告做了快取,為了讓廣告展示次數更高,可以在onDestroy方法中,將廣告物件銷燬,下次再請求廣告重新例項化。 @Override protected void onDestroy() {

在***專案,手機端使用賬戶A登入進入app,檢視模組B的內容XX,顯示正常,檢視模組C的內容XXX也顯示正常,然後進入模組D事件辦理,獲取事件列表,正常,但是選擇辦理的時候,呼叫介面E,一直提

最終解決方案: 經過排查,發現問題是,呼叫獲取事件列表介面,有個欄位為圖片,返回的為空字串,手機端未做判斷,強行載入圖片,導致PHPSESSID發生變化,服務端主動清空cookie,使用者資訊失效,TOKEN驗證失敗,解決辦法,手機端判斷,圖片欄位如果為空,則不載入。 解決

android studio 菜單app運行按鈕上有個叉號,原因與解決辦法(自己去百度)

代碼 問題 style post fontsize XML idm 出現 studio http://blog.csdn.net/sz0268/article/details/51706397 : 在Android studio寫代碼中,直接建立項目,寫代碼然後運行是不會

Android使App高速、簡單地支持新浪微博、微信、QQ、facebook等十幾個主流社交平臺的分享功能

分析 ont renren androidm mod 執行 xen 12px 操作 前言 在如今的APP或者遊戲中,分享功能差點兒已經成為標配。分享功能不但能夠滿足用戶的需求。也能夠為產品帶來很多其它的用戶,甚至能夠對用戶的行為、活躍度、年齡段等情況進行數據統計,使得軟

Android TV開發所有的遙控器按鍵監聽及註意事項,新增home鍵監聽

char 技術分享 ces num block eas article 分享 iou 原文:Android TV開發中所有的遙控器按鍵監聽及註意事項,新增home鍵監聽 簡單記錄

記錄Android開發一個小坑,佈局檔案TextView新增onClick後,點選無效問題

自己寫東西的時候,在TextView上添加了onClick去增加點選事件,去跳轉另一個Activity,執行後結果點選無效,新增Toast,Toast也不顯示,程式碼如下: <TextView android:layout_width="wrap_content"

tomcat8-管理員賬戶設定指南(解決許可權已設定仍然報403錯誤)

  新使用者新增: 修改 ${CATALINA_BASE}/conf/ 目錄下的 tomcat-users.xml 檔案,重啟tomcat後生效,例: <user username="test" password="chang3m3N#w" roles="admin-