Android与SyncAdapter框架的同步不起作用

时间:2018-08-05 22:05:40

标签: android synchronization android-syncadapter

与SyncAdapter框架的同步无法正常工作。我创建了android开发指南中提到的所有内容。问题是,当我在SyncAdapter的onperformSync()方法或SyncService中放置断点并调试应用程序时,不会执行断点的行。

这是我为实现同步而创建的所有类的概述:

存根验证器:

package com.appexample.app.common.synchronisation;

import android.accounts.AbstractAccountAuthenticator;
import android.accounts.Account;
import android.accounts.AccountAuthenticatorResponse;
import android.accounts.NetworkErrorException;
import android.content.Context;
import android.os.Bundle;

/*
 * Implement AbstractAccountAuthenticator and stub out all
 * of its methods
 */
public class Authenticator extends AbstractAccountAuthenticator {
    // Simple constructor
    public Authenticator(Context context) {
        super(context);
    }
    // Editing properties is not supported
    @Override
    public Bundle editProperties(
            AccountAuthenticatorResponse r, String s) {
        throw new UnsupportedOperationException();
    }
    // Don't add additional accounts
    @Override
    public Bundle addAccount(
            AccountAuthenticatorResponse r,
            String s,
            String s2,
            String[] strings,
            Bundle bundle) throws NetworkErrorException {
        return null;
    }
    // Ignore attempts to confirm credentials
    @Override
    public Bundle confirmCredentials(
            AccountAuthenticatorResponse r,
            Account account,
            Bundle bundle) throws NetworkErrorException {
        return null;
    }
    // Getting an authentication token is not supported
    @Override
    public Bundle getAuthToken(
            AccountAuthenticatorResponse r,
            Account account,
            String s,
            Bundle bundle) throws NetworkErrorException {
        throw new UnsupportedOperationException();
    }
    // Getting a label for the auth token is not supported
    @Override
    public String getAuthTokenLabel(String s) {
        throw new UnsupportedOperationException();
    }
    // Updating user credentials is not supported
    @Override
    public Bundle updateCredentials(
            AccountAuthenticatorResponse r,
            Account account,
            String s, Bundle bundle) throws NetworkErrorException {
        throw new UnsupportedOperationException();
    }
    // Checking features for the account is not supported
    @Override
    public Bundle hasFeatures(
            AccountAuthenticatorResponse r,
            Account account, String[] strings) throws NetworkErrorException {
        throw new UnsupportedOperationException();
    }
}

AuthenticatorService:

package com.appexample.app.common.synchronisation;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;

/**
 * A bound Service that instantiates the authenticator
 * when started.
 */
public class AuthenticatorService extends Service {

    // Instance field that stores the authenticator object
    private Authenticator mAuthenticator;

    @Override
    public void onCreate() {
        // Create a new authenticator object
        mAuthenticator = new Authenticator(this);
    }

    /*
     * When the system binds to this Service to make the RPC call
     * return the authenticator's IBinder.
     */
    @Override
    public IBinder onBind(Intent intent) {
        return mAuthenticator.getIBinder();
    }
}
xml路径中的

authenticator.xml:

<?xml version="1.0" encoding="utf-8"?>
<account-authenticator
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:accountType="com.app"
    android:icon="@drawable/ic_launcher_background"
    android:smallIcon="@drawable/ic_launcher_background"
    android:label="@string/app_name"/>

AppProvider哪些方法包含对会议室数据库的访问权限(因为我不使用ContenProvider来存储应用程序数据):

package com.appexample.app.common.synchronisation;

import ...

/*
 * Define an implementation of ContentProvider that stubs out
 * all methods
 */
public class AppProvider extends ContentProvider {

    /** The authority of this content provider. */
    public static final String AUTHORITY = "com.appexample.app.common.synchronisation.provider";

    /** The URI for the Example table. */
    public static final Uri URI_Example = Uri.parse(
            "content://" + AUTHORITY + "/" + ExampleEntity.TABLE_NAME);

    /** The match code for some items in the Example table. */
    private static final int CODE_Example_DIR = 1;

    /** The match code for an item in the Example table. */
    private static final int CODE_Example_ITEM = 2;

    /** The URI matcher. */
    private static final UriMatcher MATCHER = new UriMatcher(UriMatcher.NO_MATCH);

    static {
        MATCHER.addURI(AUTHORITY, ExampleEntity.TABLE_NAME, CODE_Example_DIR);
        MATCHER.addURI(AUTHORITY, ExampleEntity.TABLE_NAME + "/*", CODE_Example_ITEM);
    }

    /*
     * Always return true, indicating that the
     * provider loaded correctly.
     */
    @Override
    public boolean onCreate() {
        return true;
    }

    /*
     * Return no type for MIME type
     */
    @Nullable
    @Override
    public String getType(@NonNull Uri uri) {
        switch (MATCHER.match(uri)) {
            case CODE_Example_DIR:
                return "vnd.android.cursor.dir/" + AUTHORITY + "." + ExampleEntity.TABLE_NAME;
            case CODE_Example_ITEM:
                return "vnd.android.cursor.item/" + AUTHORITY + "." + ExampleEntity.TABLE_NAME;
            default:
                throw new IllegalArgumentException("Unknown URI: " + uri);
        }
    }

    /*
     * query() always returns no results
     *
     */
    @Nullable
    @Override
    public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection,
                        @Nullable String[] selectionArgs, @Nullable String sortOrder) {
        final int code = MATCHER.match(uri);
        if (code == CODE_Example_DIR || code == CODE_Example_ITEM) {
            final Context context = getContext();
            if (context == null) {
                return null;
            }
            ExampleDAO Example = AppDatabase.getInstance(context, new AppExecutors()).ExampleDao();
            final Cursor cursor;
            if (code == CODE_Example_DIR) {
                cursor = Example.fetchAllExamples();
            } else {
                //
                cursor = Example.fetchExampleByExampleId(Long.toString(ContentUris.parseId(uri)));
                //cursor = Example.selectById(ContentUris.parseId(uri));
            }
            cursor.setNotificationUri(context.getContentResolver(), uri);
            return cursor;
        } else {
            throw new IllegalArgumentException("Unknown URI: " + uri);
        }
    }

    /*
     * insert() always returns null (no URI)
     */
    @Nullable
    @Override
    public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
        switch (MATCHER.match(uri)) {
            case CODE_Example_DIR:
                final Context context = getContext();
                if (context == null) {
                    return null;
                }
                final long id = AppDatabase.getInstance(context, new AppExecutors()).ExampleDao()
                        .insertId(ExampleEntity.fromContentValues(values));
                context.getContentResolver().notifyChange(uri, null);
                return ContentUris.withAppendedId(uri, id);
            case CODE_Example_ITEM:
                throw new IllegalArgumentException("Invalid URI, cannot insert with ID: " + uri);
            default:
                throw new IllegalArgumentException("Unknown URI: " + uri);
        }
    }

    /*
     * delete() always returns "no rows affected" (0)
     */
    @Override
    public int delete(@NonNull Uri uri, @Nullable String selection,
                      @Nullable String[] selectionArgs) {
        switch (MATCHER.match(uri)) {
            case CODE_Example_DIR:
                throw new IllegalArgumentException("Invalid URI, cannot update without ID" + uri);
            case CODE_Example_ITEM:
                final Context context = getContext();
                if (context == null) {
                    return 0;
                }
                final int count = AppDatabase.getInstance(context, new AppExecutors()).ExampleDao()
                        .deleteExampleById(Long.toString(ContentUris.parseId(uri)));
                context.getContentResolver().notifyChange(uri, null);
                return count;
            default:
                throw new IllegalArgumentException("Unknown URI: " + uri);
        }
    }

    /*
     * update() always returns "no rows affected" (0)
     */
    @Override
    public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection,
                      @Nullable String[] selectionArgs) {
        switch (MATCHER.match(uri)) {
            case CODE_Example_DIR:
                throw new IllegalArgumentException("Invalid URI, cannot update without ID" + uri);
            case CODE_Example_ITEM:
                final Context context = getContext();
                if (context == null) {
                    return 0;
                }
                final ExampleEntity Example = ExampleEntity.fromContentValues(values);
                Example.setExampleId(ContentUris.parseId(uri));
                final int count = AppDatabase.getInstance(context, new AppExecutors()).ExampleDao()
                        .update(Example);
                context.getContentResolver().notifyChange(uri, null);
                return count;
            default:
                throw new IllegalArgumentException("Unknown URI: " + uri);
        }
    }
}

SyncAdapter:

package com.appexample.app.common.synchronisation;

import ...

public class SyncAdapter extends AbstractThreadedSyncAdapter {

    // Global variables
    // Define a variable to contain a content resolver instance
    ContentResolver mContentResolver;

    /**
     * Set up the sync adapter
     */
    public SyncAdapter(Context context, boolean autoInitialize) {
        super(context, autoInitialize);
        /*
         * If your app uses a content resolver, get an instance of it
         * from the incoming Context
         */
        mContentResolver = context.getContentResolver();
    }

    /**
     * Set up the sync adapter. This form of the
     * constructor maintains compatibility with Android 3.0
     * and later platform versions
     */
    public SyncAdapter(
            Context context,
            boolean autoInitialize,
            boolean allowParallelSyncs) {
        super(context, autoInitialize, allowParallelSyncs);
        /*
         * If your app uses a content resolver, get an instance of it
         * from the incoming Context
         */
        mContentResolver = context.getContentResolver();
    }

    /*
     * Specify the code you want to run in the sync adapter. The entire
     * sync adapter runs in a background thread, so you don't have to set
     * up your own background processing.
     */
    @Override
    public void onPerformSync(
            Account account,
            Bundle extras,
            String authority,
            ContentProviderClient provider,
            SyncResult syncResult) {

        Log.d("Test","Test");
    }
}
xml路径中的

syncadapter.xml:

<?xml version="1.0" encoding="utf-8"?>
<sync-adapter
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:contentAuthority="com.appexample.app.common.synchronisation.provider"
    android:accountType="com.app"
    android:userVisible="false"
    android:supportsUploading="false"
    android:allowParallelSyncs="true"
    android:isAlwaysSyncable="true"/>

SyncService:

package com.appexample.app.common.synchronisation;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;

/**
 * Define a Service that returns an IBinder for the
 * sync adapter class, allowing the sync adapter framework to call
 * onPerformSync().
 */
public class SyncService extends Service {
    // Storage for an instance of the sync adapter
    private static SyncAdapter sSyncAdapter = null;
    // Object to use as a thread-safe lock
    private static final Object sSyncAdapterLock = new Object();

    /*
     * Instantiate the sync adapter object.
     */
    @Override
    public void onCreate() {
        /*
         * Create the sync adapter as a singleton.
         * Set the sync adapter as syncable
         * Disallow parallel syncs
         */
        synchronized (sSyncAdapterLock) {
            if (sSyncAdapter == null) {
                sSyncAdapter = new SyncAdapter(getApplicationContext(), true);
            }
        }
    }

    /**
     * Return an object that allows the system to invoke
     * the sync adapter.
     *
     */
    @Override
    public IBinder onBind(Intent intent) {
        /*
         * Get the object that allows external processes
         * to call onPerformSync(). The object is created
         * in the base class code when the SyncAdapter
         * constructors call super()
         */
        return sSyncAdapter.getSyncAdapterBinder();
    }
}

此外,我在AdnroidManifest中添加了以下条目:

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

    <!-- Set needed permissions -->
    <!-- ......... -->
    <uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS" />
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.READ_SYNC_SETTINGS" />
    <uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">

        <!-- ....... -->

        <provider
            android:name="com.appexample.app.common.synchronisation.AppProvider"
            android:authorities="com.appexample.app.common.synchronisation.provider"
            android:exported="false"
            android:syncable="true"
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/file_paths" />
        </provider>

        <!-- Service for authenticator for sync -->
        <service
            android:name="com.appexample.app.common.synchronisation.AuthenticatorService">
            <intent-filter>
                <action android:name="android.accounts.AccountAuthenticator"/>
            </intent-filter>
            <meta-data
                android:name="android.accounts.AccountAuthenticator"
                android:resource="@xml/authenticator" />
        </service>

        <!-- Service for for syncadapter -->
        <service
            android:name="com.appexample.app.common.synchronisation.SyncService"
            android:exported="true"
            android:process=":sync">
            <intent-filter>
                <action android:name="com.appexample.app.common.synchronisation.SyncAdapter"/>
            </intent-filter>
            <meta-data android:name="com.appexample.app.common.synchronisation.SyncAdapter"
                android:resource="@xml/syncadapter" />
        </service>
    </application>
</manifest>

然后我在MainActivity中添加了一些代码来测试同步:

package com.appexample.app.main;

import ...

public class MainActivity extends AppCompatActivity implements FeedFragment.OnListFragmentInteractionListener, FavoritesFragment.OnFragmentInteractionListener, DiscoveryMapFragment.OnFragmentInteractionListener {

    /**
     * The {@link android.support.v4.view.PagerAdapter} that will provide
     * fragments for each of the sections. We use a
     * {@link FragmentPagerAdapter} derivative, which will keep every
     * loaded fragment in memory. If this becomes too memory intensive, it
     * may be best to switch to a
     * {@link android.support.v4.app.FragmentStatePagerAdapter}.
     */

    // ...

    /*
    ** Sync specific constants
     */
    // Content provider authority
    public static final String AUTHORITY = "com.appexample.app.common.synchronisation.provider";
    // Account
    public static final String ACCOUNT = ("Dummyacc");
    // An account type, in the form of a domain name
    public static final String ACCOUNT_TYPE = "com.app";
    // Instance fields
    Account mAccount;
    // A content resolver for accessing the provider
    ContentResolver mResolver;


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

        // ...

        // Get the content resolver
        mResolver = getContentResolver();

        // Create the dummy account
        mAccount = CreateSyncAccount(this);

        // Pass the settings flags by inserting them in a bundle
        Bundle settingsBundle = new Bundle();
        settingsBundle.putBoolean(
                ContentResolver.SYNC_EXTRAS_MANUAL, true);
        settingsBundle.putBoolean(
                ContentResolver.SYNC_EXTRAS_EXPEDITED, true);
        /*
         * Request the sync for the default account, authority, and
         * manual sync settings
         */
        ContentResolver.requestSync(mAccount, AUTHORITY, settingsBundle);
    }

    // ...

    /**
     * Create a new dummy account for the sync adapter
     *
     * @param context The application context
     */
    public static Account CreateSyncAccount(Context context) {
        // Create the account type and default account
        Account account = new Account(
                ACCOUNT, ACCOUNT_TYPE);
        // Get an instance of the Android account manager
        AccountManager accountManager =
                (AccountManager) context.getSystemService(
                        ACCOUNT_SERVICE);
        /*
         * Add the account and account type, no password or user data
         * If successful, return the Account object, otherwise report an error.
         */
        if (accountManager.addAccountExplicitly(account, null, null)) {
            /*
             * If you don't set android:syncable="true" in
             * in your <provider> element in the manifest,
             * then call context.setIsSyncable(account, AUTHORITY, 1)
             * here.
             */
            //context.setIsSyncable(account, AUTHORITY, 1);
        } else {
            /*
             * The account exists or some other error occurred. Log this, report it,
             * or handle it internally.
             */
        }

        return account;
    }
}

断点在调用requestSync方法的MainActivity中,在SyncAdapter的onCreate方法中创建SyncAdapter的SyncService中以及在创建测试日志的SyncAdapter的onperformSync方法中的行中。但是,将完全忽略SyncService和SyncAdapter中的断点,并且该应用程序在启动时不会执行同步,也不会引发任何错误。希望您能够帮助我。我试图一遍又一遍地纠正它,但没有成功。

0 个答案:

没有答案