Копировать / делиться конфигурациями между платными / бесплатными версиями приложения для Android? - PullRequest
7 голосов
/ 31 января 2012

Мое приложение для Android поставляется как в бесплатной, так и в платной версии.Я создал библиотечный проект и два дополнительных проекта приложений, одну «бесплатную» и одну «платную» (конечно же, подписанную тем же ключом).Обратите внимание, что эти проекты приложений в основном пусты, нет настроек и т. Д. Следовательно, библиотека содержит 99% кода.

Мое приложение создает базу данных SQLite и файл SharedPreferences с пользовательскими данными.Можно ли скопировать эти файлы между бесплатной и платной версиями?(Предпочтения важнее базы данных.)

Например,

  1. Пользователь запускает бесплатную версию.База данных и файл конфигурации созданы.
  2. Пользователь устанавливает платную версию и запускает ее.
  3. Платная версия проверяет любые данные бесплатной версии и копирует их. Это то, что я хочу!

Ответы [ 3 ]

8 голосов
/ 31 января 2012
  1. Реализация ContentProvider для предоставления сохраненных данных в бесплатной версии.
  2. Убедитесь, что поставщик экспортирован (android: exported = "true")
  3. Объявите разрешение в вашем клиентском приложении. Уровень защиты должен быть «подпись».
  4. Требуется разрешение, объявленное в (3) как разрешение на чтение для провайдера.
  5. В вашем платном приложении добавьте разрешение на использование для разрешения, объявленного в вашем бесплатном приложении.
  6. Проверьте наличие поставщика и загрузите данные в платное приложение.

Это, конечно, работает, только если вы подписываете бесплатные и платные приложения одним и тем же сертификатом (что делает большинство здравомыслящих людей).

1 голос
/ 31 октября 2014

Если вы не хотите заниматься реализацией 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);
    }
}

В манифесте обязательно объявите этот класс как Receiver с именем действия, совпадающим с StoredInfoManager.responseActionString

<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. Сложнее поймать ответ, используя каждый раз уникальный код действия.

Ни один из них не является доказательством дурака. Если вы просто передаете любимые цвета приложений, вам, вероятно, не нужны какие-либо меры безопасности. Если вы передаете более конфиденциальную информацию, вам нужно и то, и другое, и вам нужно подумать о том, чтобы сделать пароль соответствующим образом безопасным.

Другие улучшения

0 голосов
/ 10 сентября 2017

Я собрал информацию из ряда ответов stackoverflow, чтобы обеспечить способ копирования всех данных SharedPreference из одного приложения в другое.В моем конкретном случае я использую различные варианты продуктов для бесплатного и профессионального приложения и хочу скопировать их из бесплатного в профессиональный.

ВНИМАНИЕ: Это работает, только если вы не выпустили ни одну из версий в магазине воспроизведения.Если вы добавите (или удалите) sharedUserId в свое приложение после того, как оно появится в магазине воспроизведения, ваши пользователи не смогут обновиться без удаления.Я научился этому нелегко.Спасибо Google ..

Добавьте 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;
}

Кроме того, в соответствии с этот ответ вам нужно будет позвонить getSettingsFromFreeApp() перед любым другим вызовом, чтобы получить настройки в вашем приложении.

...