6

I am looking into using the new Backup API that available since Android 2.2, but need to maintain backwards compatibility (to 1.5 to be exact).

The docs state:

The backup service and the APIs you must use are available only on devices running API Level 8 (Android 2.2) or greater, so you should also set your android:minSdkVersion attribute to "8". However, if you implement proper backward compatibility in your application, you can support this feature for devices running API Level 8 or greater, while remaining compatible with older devices.

I indeed build against the level 8 targetSdkVersion with level 3 minSdkVersion and try to use a wrapper class (with reflection) to overcome the problem that the application will not run if you implement a class that extends an nonexisting class.

Here is the problem: since we don't make actual calls to the BackupHelper class ourselves, we can't check upfront if the class indeed exists. (As is explained in the Android Backwards Compatibility documentation with a checkAvailable() method.) The class will therefore be instantiated and cast to a BackupAgent. But since we use reflection, it doesn't actually override BackupAgent and an exception occurs at runtime when the backup is requested:

java.lang.RuntimeException: Unable to create BackupAgent org.transdroid.service.BackupAgent: java.lang.ClassCastException: org.transdroid.service.BackupAgent

Here is my approach to a backwards compatible BackupAgent: http://code.google.com/p/transdroid/source/browse/#svn/trunk/src/org/transdroid/service where the BackupAgent.java is the 'regular' BackupAgentHelper-extending class and BackupAgentHelperWrapper is the reflection-based wrapper class.

Anyone successfull in implementing a BackupAgent with backwards compatibility?

HitOdessit
  • 7,198
  • 4
  • 36
  • 59
Eric Kok
  • 2,042
  • 3
  • 22
  • 32
  • I believe there was a talk about doing this at this year's Barcamp at Droidcon. Can't remember the guy's name, but worth having a search for. – Christopher Orr Jun 22 '10 at 11:02

6 Answers6

10

As an alternative, you can just use pure reflection to talk to the BackupManager:

public void scheduleBackup() {
    Log.d(TAG, "Scheduling backup");
    try {
        Class managerClass = Class.forName("android.app.backup.BackupManager");
        Constructor managerConstructor = managerClass.getConstructor(Context.class);
        Object manager = managerConstructor.newInstance(context);
        Method m = managerClass.getMethod("dataChanged");
        m.invoke(manager);
        Log.d(TAG, "Backup requested");
    } catch(ClassNotFoundException e) {
        Log.d(TAG, "No backup manager found");
    } catch(Throwable t) {
        Log.d(TAG, "Scheduling backup failed " + t);
        t.printStackTrace();
    }
}

Point the android:backupAgent straight at a v2.2 class; it will never be loaded on a pre-v2.2 VM, so there won't be any linkage problems.

Miki Habryn
  • 150
  • 1
  • 6
  • A great example of how to do backwards compatibility in Android with reflection. Thanks for posting this. – Zulaxia Jun 17 '11 at 16:40
7

I don't see why you run into this problem.

I have the same issue: I want to support backup with a app that supports also 1.5 (API 3).

There is no problem in creating my BackupAgentHelper class, since that class is never called from my own code, but from the BackupManager i.e. the system itself. Therefore I don't need to wrap it, and I don't see why you should be doing that:

 public class MyBackupAgentHelper extends BackupAgentHelper {
 @override onCreate()
 { 
       \\do something usefull
 }

However, you do want to get a backup running, to do that you need to call on BackupManager.dataChanged() whenever your data changes and you want to inform the system to backup it (using your BackupAgent or BackupAgentHelper).

You do need to wrap that class, since you call it from you application code.


public class WrapBackupManager {
private BackupManager wrappedInstance;

static 
{
    try
    {
        Class.forName("android.app.backup.BackupManager");
    }
    catch (Exception e)
    {
        throw new RuntimeException(e);
    }
}
public static void checkAvailable() {}

public void dataChanged()
{
    wrappedInstance.dataChanged();
}

public WrapBackupManager(Context context)
{
    wrappedInstance = new BackupManager(context);
}

}

You then call it from your code when you change a preference or save some data. Some code from my app:


private static Boolean backupManagerAvailable = null;

    private static void postCommitAction() {


        if (backupManagerAvailable == null) {
            try {
                WrapBackupManager.checkAvailable();
                backupManagerAvailable = true;
            } catch (Throwable t) {
                backupManagerAvailable = false;
            }
        }

        if (backupManagerAvailable == true) {
            Log.d("Fretter", "Backup Manager available, using it now.");
            WrapBackupManager wrapBackupManager = new WrapBackupManager(
                    FretterApplication.getApplication());
            wrapBackupManager.dataChanged();
        } else {
            Log.d("Fretter", "Backup Manager not available, not using it now.");
        }

So, hopefully this works for you!

(If you call adb shell bmgr run every time you want to emulate the actual system initiated backupprocess it should properly backup and restore when you reinstall the app.)

Peterdk
  • 15,625
  • 20
  • 101
  • 140
  • Looks promising and I'll give it a try. – Eric Kok Oct 18 '10 at 14:32
  • You are totally right: this did the trick! The backup agent can be a regular class extending BackupAgentHelper and it is the call to dataChanged to wrap. Hence, we make a wrapper around BackupManager. Mine can be found at http://code.google.com/p/transdroid/source/browse/trunk/src/org/transdroid/service/BackupManagerWrapper.java – Eric Kok Nov 05 '10 at 19:54
  • Dead link -- the risks of posting links to live projects on code.google.com =( – Cory Trese Dec 16 '11 at 18:32
  • 1
    Beware of some 1.5 phones that get ValidationError upon launch because your class extends a class that doesn't exist in Dalvik in 1.5. This was fixed in 1.6 – Philippe Girolami Jan 25 '12 at 18:22
  • Instead of WrapBackupManager.checkAvailable(), couldn't we just check the api version? – Simon May 02 '21 at 02:10
1

How about

    if (android.os.Build.VERSION.SDK_INT >= 8) 
    {
        BackupManager bm = new BackupManager(this);
        bm.dataChanged();
    }
AndroidCoder
  • 302
  • 1
  • 3
  • 13
  • Why this solution has no vote? Isn't checking the api version better then using reflection? – Simon May 02 '21 at 02:13
1

You need to set the minSDK version to the following:

<uses-sdk android:minSdkVersion="3" android:targetSdkVersion="8"/>

and setting the build target to sdk 8 (project properties in eclipse '.default.properties'):

# Project target.
target=android-8

Now to call new stuff added in SDK 8 you have to use reflection: http://developer.android.com/resources/articles/backward-compatibility.html

Moss
  • 6,002
  • 1
  • 35
  • 40
  • I possibly wasn't clear enough (I edited my answer to state it explicitly), but that is exactly what I was trying. The problem is that the wrapper class cannot extend BackupAgent (this will not compile against 1.5) but is being cast to a BackupAgent by the Android backup mechanism. – Eric Kok Jun 22 '10 at 08:51
  • Ok, I think you will be able to extend a class using reflection in java: http://stackoverflow.com/questions/1886785/how-do-i-extend-java-classes-by-reflection – Moss Jun 22 '10 at 12:53
1

I ran into the same problem and here's what I did to work it out.

You don't extend BackupAgent with the wrapper, you extend it with the wrapped class. So you make your real backup class:

public class MyBackup extends BackupAgent {

@Override
public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
        ParcelFileDescriptor newState) throws IOException {
    // TODO Auto-generated method stub

}

@Override
public void onRestore(BackupDataInput data, int appVersionCode,
        ParcelFileDescriptor newState) throws IOException {
    // TODO Auto-generated method stub

}

Okay, and then you make a wrapper like the android developer backwards compatibility article said to do. Note that this class does not extend BackupAgent:

public class WrapMyBackup {
private MyBackup wb;

static {
    try {
        Class.forName("MyBackup");
    }
    catch (Exception ex) {
        throw new RuntimeException(ex);
    }
}

/** call this wrapped in a try/catch to see if we can instantiate **/
public static void checkAvailable() {}

public WrapMyBackup() {
    wb = new MyBackup();
}

public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
        ParcelFileDescriptor newState) throws IOException {
    wb.onBackup(oldState, data, newState);

}

public void onRestore(BackupDataInput data, int appVersionCode,
        ParcelFileDescriptor newState) throws IOException {
    wb.onRestore(data, appVersionCode, newState);

}

public void onCreate() {
    wb.onCreate();
}

public void onDestroy() {
    wb.onDestroy();
}

}

Finally, in your manifest, you declare the wrapper as your backup agent:

    <application 
    android:label="@string/app_name"
    android:icon="@drawable/ic_launch_scale"
    android:backupAgent="WrapMyBackup"
    >

Since your wrapper has the proper methods defined you won't run into a problem when the backup manager casts it to a BackupAgent. Since lower API levels won't have a BackupManager the code will never get called, so you won't run into any runtime exceptions there either.

Keith Twombley
  • 1,666
  • 1
  • 17
  • 21
  • Thanks for your suggestion. It is essentially what I have done myself. My wrapper has the needed methods defined as well. Since debugging the process on an emulator or real device doesn't seem to work with the bmgr tool, it is hard to establish what/when it doesn't work. I think I had a bug in my wrapper, since I was missing the proper constructor. Hopefully it works now, but I will report back soon on this. – Eric Kok Jun 28 '10 at 09:09
0

Insted of just calling BackupManager.dataChanged, check if the class exists first.

   try {
            Class.forName("android.app.backup.BackupManager");
            BackupManager.dataChanged(context.getPackageName());
        } catch (ClassNotFoundException e) {
        }
DKIT
  • 3,471
  • 2
  • 20
  • 24