与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中的断点,并且该应用程序在启动时不会执行同步,也不会引发任何错误。希望您能够帮助我。我试图一遍又一遍地纠正它,但没有成功。