我的Android应用程序同时作为免费和付费版本。我创建了一个库项目和两个额外的应用程序项目,一个是“免费”和一个“付费”版本(当然用相同的密钥签名)。请注意,这些应用程序项目非常空,没有设置等。因此,库包含99%的代码。
我的应用会创建一个SQLite
数据库和一个包含用户数据的SharedPreferences
文件。是否可以在免费和付费版本之间复制这些文件? (首选项比数据库更重要。)
E.g。
答案 0 :(得分:8)
当然,这只适用于您使用相同证书(最理智的人)签署免费和付费应用程序时。
答案 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.
}
安全强>
这种方法的缺点是任何人都可以注册一个接收器来捕获两个应用程序之间的通信。上面的代码规避了这个:
通过使用密码使请求广播难以伪造。这个答案是讨论如何使密码安全的问题,但重要的是要意识到在创建密码时无法存储数据以便以后检查密码 - 它是不同的将要检查的应用程序。
每次都使用唯一的操作代码,使回复更难捕获。
这些都不是万无一失的。如果您只是简单地传递最喜欢的应用颜色,那么您可能不需要任何安全措施。如果您传递更多敏感信息,则需要两者兼顾,并且需要考虑使密码安全。
其他改进
答案 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()
,然后才能在您的应用中获取偏好设置。