在付费/免费版Android应用之间复制/共享配置?

时间:2012-01-30 20:59:50

标签: android sharedpreferences share

我的Android应用程序同时作为免费和付费版本。我创建了一个库项目和两个额外的应用程序项目,一个是“免费”和一个“付费”版本(当然用相同的密钥签名)。请注意,这些应用程序项目非常空,没有设置等。因此,库包含99%的代码。

我的应用会创建一个SQLite数据库和一个包含用户数据的SharedPreferences文件。是否可以在免费和付费版本之间复制这些文件? (首选项比数据库更重要。)

E.g。

  1. 用户运行免费版本。将创建一个数据库和配置文件。
  2. 用户安装付费版本并运行它。
  3. 付费版本会检查任何免费版本数据并将其复制。 这就是我想要的!

3 个答案:

答案 0 :(得分:8)

  1. 实施ContentProvider以免费版本公开存储的数据。
  2. 确保导出提供程序(android:exported =“true”)
  3. 在您的客户端应用程序中声明权限。保护级别应为“签名”。
  4. 要求(3)中声明的权限为提供者的readPermission。
  5. 在付费应用中,为您的免费应用中声明的权限添加使用权限。
  6. 检查是否存在提供商&将数据加载到付费应用中。
  7. 当然,这只适用于您使用相同证书(最理智的人)签署免费和付费应用程序时。

答案 1 :(得分:1)

如果您不希望遇到实施ContentProvider的问题,或者两个应用程序可能仍然安装和使用,则会有不同的解决方案。

代码和用法

让我们假设有问题的数据属于一个类:

class DataToBeShared() {
    // Data etc in here
}

然后,按如下方式向两个应用添加一个类:

public class StoredInfoManager {
    public static String codeAppType   = "apptype";
    public static String codeTimestamp = "timestamp";
    public static String codeData      = "data";
    public static String codeResponseActionString = "arstring";

    public static String responseActionString = "com.me.my.app.DATA_RESPONSE";

    private static int APP_UNKNOWN = 0;
    private static int APP_FREE    = 1;
    private static int APP_PAID    = 2;

    private static String freeSharedPrefName = "com.me.my.app.free.data";
    private static String paidSharedPrefName = "com.me.my.app.paid.data";

    // Use only one pair of the next lines depending on which app this is:
    private static String prefName = freeSharedPrefName;
    private static int    appType  = APP_FREE;

    //private static String prefName = paidSharedPrefName;
    //private static int    appType  = APP_PAID;

    private static String codeActionResponseString = "response";

    // Provide access points for the apps to store the data
    public static void storeDataToPhone(Context context, DataToBeShared data) {
        SharedPreferences settings = context.getSharedPreferences(prefName, Context.MODE_PRIVATE);
        SharedPreferences.Editor editor = settings.edit();

        // Put the data in the shared preferences using standard commends.
        // See the android developer page for SharedPreferences.Editor for details.
        // Code for that here

        // And store it
        editor.commit();
    }

到目前为止,这是一个相当标准的共享首选项存储系统。现在是乐趣开始的地方。首先,确保存在用于获取上面存储的数据的私有方法,以及用于广播它的私有方法。

    private static DataToBeshared getData(Context context) {
        SharedPreferences settings = context.getSharedPreferences(prefName, Context.MODE_PRIVATE);
        DataToBeShared result = new DataToBeShared();

        // Your code here to fill out result from Shared preferences.
        // See the developer page for SharedPreferences for details.

        // And return the result.
        return result;
    }

    private static void broadcastData(Context context, DataToBeShared data, String intentActionName) {
        Bundle bundle = new Bundle();
        bundle.putInt(codeAppType, appType);
        bundle.putParcelable(codeData, data);

        Intent intent = new Intext(intentActionString);
        intent.putEXtras(bundle);
        context.sendBroadcast(intent);
    }

创建一个BroadcastReceiver类来捕获来自其他应用程序的数据响应:

static class CatchData extends BroadcastReceiver {
    DataToBeShared data = null;
    Long           timestamp = 0L;
    int            versionListeningFor = Version.VERSION_UNKNOWN;
    Timeout        timeout = null;

    // We will need a timeout in case the other app isn't actually there.
    class Timeout extends CountDownTimer {
        Context _context;
        public Timeout(Context context, long millisInFuture, long countDownInterval) {
            super(millisInFuture, countDownInterval);
            _context = context;
        }

        @Override
        public void onFinish() {
            broadcastAndCloseThisBRdown(_context);
        }

        @Override
        public void onTick(long millisUntilFinished) {}
    }

    // Constructor for the catching class
    // Set the timeout as you see fit, but make sure that
    // the tick length is longer than the timeout.
    CatchDPupdate(Context context, DataToBeShared dptsKnown, Long timeKnown, int otherVersion) {
        data                = dptsKnown;
        timestamp           = timeKnown;
        versionListeningFor = otherVersion;

        timeout = new Timeout(context, 5000, 1000000);
        timeout.start();
    }

    @Override
    public void onReceive(Context context, Intent intent) {
        Bundle extras = intent.getExtras();
        if (extras == null) return;

        // Check it's the data we want
        int sendingVersion = extras.getInt(codeAppType, APP_UNKNOWN);
        if (sendingVersion != versionListeningFor) return;

        // This receiver has served its purpose, so unregister it.
        context.unregisterReceiver(this);

        // We've got the data we want, so drop the timeout.
        if (timeout != null) {
            timeout.cancel();
            timeout = null;
        }

        Long            tsInc  = extras.getLong(codeTimestamp, 0L);
        DataToBeShared  dataInc = extras.getParcelable(codeData);

        // Now, you need to decide which set of data is better.
        // You may wish to use a timestamp system incorporated in DataToBeStored.
        if (/* Incoming data best*/) {
            data        = dpInc;
            // Make it ours for the future
            storeDataToPhone(context, data);
        }

        // Send the data out
        broadcastAndCloseThisBRdown(context);
    }

    private void broadcastAndCloseThisBRdown(Context context) {
        broadcastData(context, data, responseActionString);
    }
}

现在,为要使用的应用提供静态访问功能。请注意,它不会返回任何内容,而是由上面的响应捕获程序完成的。

    public static void geDataFromPhone(Context context) {
        DataToBeStored myData = getData(context);
        // See security discussion point 2 for this next line
        String internalResponseActionString = "com.me.my.app.blah.hohum." + UUID.randomUUID();

        // Instantiate a receiver to catch the response from the other app
        int otherAppType = (appType == APP_PAID ? APP_FREE : APP_PAID);
        CatchData catchData = new CatchData(context, mydata, otherAppType);
        context.registerReceiver(catchData, new IntentFilter(internalResponseActionString));

        // Send out a request for the data from the other app.
        Bundle bundle = new Bundle();
        bundle.putInt(codeAppType, otherAppType);
        bundle.putString(codeResponseActionString, internalResponseActionString);
        bundle.putString(CatchDataRequest.code_password, CatchDataRequest.getPassword());
        Intent intent = new Intent(responseActionString);
        context.sendBroadcast(intent);
    }

这是它的核心。我们需要另一个类,并对清单进行调整。该类(用于捕获来自其他应用程序的数据请求:

public class CatchDataRequest extends BroadcastReceiver {
    // See security discussion point 1 below
    public static String code_password = "com.newtsoft.android.groupmessenger.dir.p";

    public static String getPassword() {
        return calcPassword();
    }

    private static String calcPassword() {
        return "password";
    }

    private static boolean verifyPassword(String p) {
        if (p == null) return false;
        if (calcPassword().equals(p)) return true;
        return false;
    }

    @Override
    public void onReceive(Context context, Intent intent) {

        Bundle bundle = intent.getExtras();
        if (bundle == null) return;
        String passwordSent = bundle.getString(code_password);
        if (!verifyPassword(passwordSent)) return;

        int versionRequested             = bundle.getInt(StoredInfoManager.codeAppType);
        String actionStringToRespondWith = bundle.getString(StoredInfoManager.codeResponseActionString);

        // Only respond if we can offer what's asked for
        if (versionRequested != StoredInfoManager.appType) return;

        // Get the data and respond     
        DataToBrStored data = StoredInfoManager.getData(context);       
        StoredInfoManager.broadcastData(context, data, actionStringToRespondWith);
    }
}

在清单中,请务必将此类声明为具有与StoredInfoManager.responseActionString匹配的操作名称的Receiver

<receiver android:name="com.me.my.app.CatchDataRequest" android:enabled="true">
    <intent-filter>
        <action android:name="com.me.my.app.DATA_RESPONSE"/>
    </intent-filter>
</receiver>

使用它相对简单。您正在使用数据的类必须扩展BroadcastReceiver:

public class MyActivity extends Activity {
    // Lots of your activity code ...

    // You'll need a class to receive the data:
    MyReceiver receiver= new MyReceiver();
    class MyReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            Bundle extras = intent.getExtras();
            if (extras == null) return;
            // Do stuff with the data
        }
    }

    // But be sure to add the receiver lines to the following methods:
    @Override
    public void onPause() {
        super.onPause();
        this.unregisterReceiver(receiver);
    }


    @Override
    public void onResume() {
        super.onResume();
        this.registerReceiver(receiver, new IntentFilter(StoredInfoManager.receiver_action_string));
        }
    }

    // To store the data
    StoredInfoManager.storeDataToPhone(contextOfApp, data);

    // To retrieve the data is a two step process. Ask for the data:
    StoredInfoManager.getData(contextOfApp);
    // It will arrive in receiver, above.
}

安全

这种方法的缺点是任何人都可以注册一个接收器来捕获两个应用程序之间的通信。上面的代码规避了这个:

  1. 通过使用密码使请求广播难以伪造。这个答案是讨论如何使密码安全的问题,但重要的是要意识到在创建密码时无法存储数据以便以后检查密码 - 它是不同的将要检查的应用程序。

  2. 每次都使用唯一的操作代码,使回复更难捕获。

  3. 这些都不是万无一失的。如果您只是简单地传递最喜欢的应用颜色,那么您可能不需要任何安全措施。如果您传递更多敏感信息,则需要两者兼顾,并且需要考虑使密码安全。

    其他改进

答案 2 :(得分:0)

我从许多stackoverflow答案中收集了信息,以提供将所有SharedPreference数据从一个应用程序复制到另一个应用程序的方法。在我的特殊情况下,我使用产品口味的免费和专业应用程序,我想从免费复制到专业版。

注意:这仅在您尚未在Play商店中发布任何版本时才有效。如果您在应用商店中添加(或移除)sharedUserId后,您的用户将无法在不卸载的情况下进行更新。我很难学到这一点。谢谢谷歌..

将sharedUserId添加到两个应用中的清单中。请注意,只有在两个应用都使用相同的证书签名时才能使用此功能。

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="my.package.name.free"
android:sharedUserId="my.package.name">

然后在第一次初步推荐专业版应用时调用此方法。

private void getSettingsFromFreeApp() {
    // This is a build config constant to check which build flavour this is
    if (BuildConfig.IS_PRO) {
        try {
            Context otherAppContext = this.createPackageContext("my.package.name.free", Context.MODE_PRIVATE);
            SharedPreferences otherAppPrefs = PreferenceManager.getDefaultSharedPreferences(otherAppContext);

            Map<String, ?> keys = otherAppPrefs.getAll();
            SharedPreferences.Editor editor = prefs.edit();
            for(Map.Entry<String, ?> entry : keys.entrySet()){

                Object value = getWildCardType(entry.getValue());

                Log.d("map values", entry.getKey() + ": " + entry.getValue());
                if (entry.getValue() instanceof Boolean) {
                    editor.putBoolean(entry.getKey(), (boolean) value);
                    editor.apply();
                } else if (value instanceof Long) {
                    editor.putLong(entry.getKey(), (long) value);
                    editor.apply();
                } else if (value instanceof Float) {
                    editor.putFloat(entry.getKey(), (float) value);
                    editor.apply();
                } else if (value instanceof Integer) {
                    editor.putInt(entry.getKey(), (int) value);
                    editor.apply();
                } else if (value instanceof String) {
                    editor.putString(entry.getKey(), String.valueOf(value));
                    editor.apply();
                }
            }


        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }
    }
}

private Object getWildCardType(Object value) {
   return value;
}

此外,根据this answer,您需要先致电getSettingsFromFreeApp(),然后才能在您的应用中获取偏好设置。