2012-01-30 2 views
7

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

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

E.g.

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

ответ

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

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

+0

Большое спасибо! Действительно аккуратное решение! – l33t

+2

Нельзя ли иметь один и тот же «android: sharedUserId» и каким-то образом прочитать приложение № 1 «SharedPreferences» из приложения № 2? – l33t

+0

Yup, это тоже работает - но если вы выберете (или забыли добавить) другой sharedUserId, вы довольно сильно напортачили - с разрешения вам нужно только сохранить ваши ключи (которые связаны с вашей учетной записью Market в любом случае). – Jens

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

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

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

  • Если вы хотите, чтобы проверить, если другая версия установлена ​​перед отправкой запроса и ожидания ответа, см Detect an application is installed or not?.
+0

Интересно! Благодаря! :) Гарантирован ли приемник вещания? Например. службы, как правило, убиваются ОС Android при нехватке ресурсов. Не уверен, что это относится и к приемникам. – l33t

+0

@ I33t Службы действительно убиты. Broadcastreceivers не убиваются, потому что они не всегда работают: в принципе, Broadcastreceiver, который зарегистрирован в манифесте, должен быть вызван (если он еще не существует), если выдается намерение, которое оно должно получить. –

0

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

ВНИМАНИЕ: Это работает только в том случае, если вы не выпустили ни одну из версий в магазине воспроизведения. Если вы добавите (или удалите) 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; 
} 

Кроме того, в соответствии с this answer вы хотите позвонить getSettingsFromFreeApp() перед любым другим вызовом, чтобы получить преференции в вашем приложении.

Смежные вопросы