我有一个想要作为付费应用程序投放市场的应用程序。我想使用其他版本,将其作为“试用”版本,时限为5天?
我该怎么做呢?
我有一个想要作为付费应用程序投放市场的应用程序。我想使用其他版本,将其作为“试用”版本,时限为5天?
我该怎么做呢?
Answers:
当前,大多数开发人员使用以下3种技术之一来完成此任务。
可以轻松规避第一种方法,第一次运行应用程序时,将日期/时间保存到文件,数据库或共享首选项中,并且每次运行该应用程序后都要检查试用期是否已结束。这很容易规避,因为卸载和重新安装将使用户拥有另一个试用期。
第二种方法更难规避,但仍然可以规避。使用硬编码的定时炸弹。基本上,通过这种方法,您将很难确定试用的结束日期,并且所有下载和使用该应用程序的用户都将无法同时使用该应用程序。我之所以使用这种方法,是因为它易于实现,并且在大多数情况下,我只是不想经历第三种技术的麻烦。用户可以通过手动更改手机上的日期来规避此问题,但是大多数用户不会遇到麻烦。
第三种技术是我听说过的真正能够完成您想要做的事情的唯一方法。您将必须设置服务器,然后无论何时启动应用程序,您的应用程序都会将电话的唯一标识符发送到服务器。如果服务器没有该电话ID的条目,则它将创建一个新的ID并记录时间。如果服务器确实具有电话ID条目,则它将进行简单检查以查看试用期是否已到期。然后,它将试用期满检查的结果传达回您的应用程序。这种方法不应该被规避,但是确实需要设置一个Web服务器等。
在onCreate中进行这些检查始终是一个好习惯。如果到期已结束,则弹出带有市场链接的AlertDialog,该链接指向应用程序的完整版本。仅包括一个“确定”按钮,并且一旦用户单击“确定”,就调用“ finish()”以结束活动。
我已经开发了一个Android试用版SDK,您可以将其轻松放入Android Studio项目中,它将为您处理所有服务器端管理(包括离线宽限期)。
要使用它,只需
将库添加到主模块的 build.gradle
dependencies {
compile 'io.trialy.library:trialy:1.0.2'
}
用您的主要活动的onCreate()
方法初始化库
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//Initialize the library and check the current trial status on every launch
Trialy mTrialy = new Trialy(mContext, "YOUR_TRIALY_APP_KEY");
mTrialy.checkTrial(TRIALY_SKU, mTrialyCallback);
}
添加一个回调处理程序:
private TrialyCallback mTrialyCallback = new TrialyCallback() {
@Override
public void onResult(int status, long timeRemaining, String sku) {
switch (status){
case STATUS_TRIAL_JUST_STARTED:
//The trial has just started - enable the premium features for the user
break;
case STATUS_TRIAL_RUNNING:
//The trial is currently running - enable the premium features for the user
break;
case STATUS_TRIAL_JUST_ENDED:
//The trial has just ended - block access to the premium features
break;
case STATUS_TRIAL_NOT_YET_STARTED:
//The user hasn't requested a trial yet - no need to do anything
break;
case STATUS_TRIAL_OVER:
//The trial is over
break;
}
Log.i("TRIALY", "Trialy response: " + Trialy.getStatusMessage(status));
}
};
要开始试用,请致电mTrialy.startTrial("YOUR_TRIAL_SKU", mTrialyCallback);
您的应用密钥,并在Trialy开发人员仪表板中找到试用SKU 。
这是一个古老的问题,但是无论如何,也许这将对某人有所帮助。
如果您想采用最简单的方法(如果卸载/重新安装应用程序或用户手动更改设备日期,该方法将失败),可能是这样的:
private final SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
private final long ONE_DAY = 24 * 60 * 60 * 1000;
@Override
protected void onCreate(Bundle state){
SharedPreferences preferences = getPreferences(MODE_PRIVATE);
String installDate = preferences.getString("InstallDate", null);
if(installDate == null) {
// First run, so save the current date
SharedPreferences.Editor editor = preferences.edit();
Date now = new Date();
String dateString = formatter.format(now);
editor.putString("InstallDate", dateString);
// Commit the edits!
editor.commit();
}
else {
// This is not the 1st run, check install date
Date before = (Date)formatter.parse(installDate);
Date now = new Date();
long diff = now.getTime() - before.getTime();
long days = diff / ONE_DAY;
if(days > 30) { // More than 30 days?
// Expired !!!
}
}
...
}
getTime
不是getTimeInMillis
。
这个问题和snctln的答案激发了我研究基于方法3的解决方案的经验。我知道目前的状态并不用于生产用途,但是我很想听听您对此的看法!您会使用这样的系统吗?您是否希望将其视为云服务(在配置服务器时不会遇到麻烦)?关注安全性问题或稳定性原因?
完成学士程序后,我想继续使用该软件。所以现在是时候我需要您的反馈了!
源代码托管在GitHub https://github.com/MaChristmann/mobile-trial
有关该系统的一些信息:-该系统包含三个部分,一个Android库,一个node.js服务器和一个配置器,用于管理多个试用应用程序和发布者/开发者帐户。
它仅支持基于时间的试用,并且使用您的(游戏商店或其他)帐户而不是电话ID。
对于Android库,它基于Google Play许可验证库。我对其进行了修改,以连接到node.js服务器,此外,该库还尝试识别用户是否更改了系统日期。它还将检索到的试用许可证缓存在AES加密的“共享首选项”中。您可以使用配置器配置缓存的有效时间。如果用户“清除数据”,该库将强制服务器端检查。
服务器正在使用https,并且还在数字签名许可证检查响应。它还具有针对CRUD试用版应用程序和用户(发布者和开发者)的API。模拟许可库开发人员可以在测试应用程序中用测试结果测试其行为实现。因此,您可以在配置器中将许可证响应显式设置为“已许可”,“未许可”或“服务器错误”。
如果您使用具有吸引人的新功能更新您的应用,则可能希望每个人都可以再次尝试。在配置器中,您可以通过设置应触发该许可证的版本代码来为许可证已过期的用户续订试用许可证。例如,用户在版本码3上运行您的应用程序,而您希望他尝试版本码4的功能。如果他更新或重新安装该应用程序,则他可以再次使用完整的试用期,因为服务器知道他上次尝试使用哪个版本时间。
一切都在Apache 2.0许可下
实现此目的最简单,最佳的方法是实施BackupSharedPreferences。
即使卸载并重新安装了应用程序,首选项也会保留。
只需将安装日期保存为首选项,就可以了。
理论是这样的:http : //developer.android.com/reference/android/app/backup/SharedPreferencesBackupHelper.html
方法4:使用应用程序安装时间。
自API级别9(Android 2.3.2、2.3.1,Android 2.3,GINGERBREAD)以来,中存在firstInstallTime和lastUpdateTimePackageInfo
。
阅读更多信息: 如何从android获取应用安装时间
现在,已添加了最新版本的android免费试用版订阅,只有在免费试用期内在应用程序内购买了订阅后,才能解锁应用程序的所有功能。这将使用户可以在试用期内使用您的应用程序,如果在试用期之后仍卸载了该应用程序,则订阅资金将转移给您。我没有尝试过,只是分享一个想法。
我认为,执行此操作的最佳方法是仅使用Firebase实时数据库:
1)在您的应用中添加Firebase支持
2)选择“匿名身份验证”,以便用户无需注册甚至不知道您在做什么。保证可以链接到当前经过身份验证的用户帐户,因此可以跨设备使用。
3)使用Realtime Database API设置“ installed_date”的值。在启动时,只需检索此值并使用它。
我也做过,而且效果很好。我能够在卸载/重新安装之间进行测试,并且实时数据库中的值保持不变。这样,您的试用期可在多个用户设备上使用。您甚至可以对install_date进行版本控制,以使该应用“重置”每个新的主要版本的试用日期。
更新:经过更多测试之后,匿名Firebase似乎分配了一个不同的ID,以防万一您拥有不同的设备,并且在两次重新安装之间无法保证:/唯一保证的方法是使用Firebase并将其绑定到他们的Google帐户。这应该可以,但是需要额外的步骤,用户首先需要登录/注册。
到目前为止,我以一种稍微不太优雅的方法结束了,它只是简单地检查备份的首选项和安装时存储在首选项中的日期。这适用于以数据为中心的应用程序,对于一个人来说,重新安装该应用程序并重新输入之前添加的所有数据毫无意义,但不适用于简单的游戏。
在查看了本主题和其他主题的所有选项之后,这些是我的发现
共享的首选项,数据库 可以在android设置中清除,重新安装应用后丢失。可以使用android的备份机制进行备份,重新安装后即可恢复。备份可能并不总是可用,但是应该在大多数设备上都可以
外部存储(写入文件), 如果我们不写入应用程序的私有目录,则不受清除设置或重新安装的影响。但是:在新的android版本中,要求您在运行时询问用户的权限,因此这仅在您仍然需要此权限时才可行。也可以备份。
PackageInfo.firstInstallTime 在重新安装后重置,但在更新中稳定
登录某个帐户 不管是通过Firebase的Google帐户还是您自己的服务器中的一个帐户:试用版均绑定到该帐户。开设新帐户将重置试用版。
Firebase匿名登录 您可以匿名登录用户并将其数据存储在Firebase中。但是显然,重新安装该应用程序以及可能出现其他未记录的事件可能会为用户提供一个新的匿名ID,从而重置其试用时间。(Google本身并没有提供太多文档)
ANDROID_ID 可能不可用,在某些情况下可能会更改,例如恢复出厂设置。关于使用它来识别设备是否是一个好主意的观点似乎有所不同。
播放广告ID 可由用户重置。用户可以选择退出广告跟踪来禁用它。
InstanceID 在重新安装时重置。发生安全事件时重置。可以由您的应用重置。
哪种(哪种)方法适合您,取决于您的应用程序以及您认为普通John将获得另一个试用期的平均工作量。我会建议转向明确使用仅匿名火力地堡和广告ID,由于其不稳定性。多因素方法似乎将产生最佳结果。哪些因素可供您使用取决于您的应用及其权限。
对于我自己的应用程序,我发现共享首选项+ firstInstallTime +首选项的备份是侵入性最小但也足够有效的方法。您必须确保仅在将试用开始时间检查并存储在共享首选项中后才请求备份。共享Prefs中的值必须优先于firstInstallTime。然后,用户必须重新安装该应用程序,运行一次,然后清除该应用程序的数据以重置试用版,这是很多工作。但是,在没有备份传输的设备上,用户可以通过简单地重新安装来重置试用版。
我已经将该方法用作可扩展库。
我在寻找相同问题时遇到了这个问题,我认为我们可以利用http://www.timeapi.org/utc/now之类的免费日期api 或其他日期api来检查Trail应用程序的到期时间。如果您希望交付演示并担心付款并需要固定使用期限演示,则此方法非常有效。:)
在下面找到代码
public class ValidationActivity extends BaseMainActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@Override
protected void onResume() {
processCurrentTime();
super.onResume();
}
private void processCurrentTime() {
if (!isDataConnectionAvailable(ValidationActivity.this)) {
showerrorDialog("No Network coverage!");
} else {
String urlString = "http://api.timezonedb.com/?zone=Europe/London&key=OY8PYBIG2IM9";
new CallAPI().execute(urlString);
}
}
private void showerrorDialog(String data) {
Dialog d = new Dialog(ValidationActivity.this);
d.setTitle("LS14");
TextView tv = new TextView(ValidationActivity.this);
tv.setText(data);
tv.setPadding(20, 30, 20, 50);
d.setContentView(tv);
d.setOnDismissListener(new OnDismissListener() {
@Override
public void onDismiss(DialogInterface dialog) {
finish();
}
});
d.show();
}
private void checkExpiry(int isError, long timestampinMillies) {
long base_date = 1392878740000l;// feb_19 13:8 in GMT;
// long expiryInMillies=1000*60*60*24*5;
long expiryInMillies = 1000 * 60 * 10;
if (isError == 1) {
showerrorDialog("Server error, please try again after few seconds");
} else {
System.out.println("fetched time " + timestampinMillies);
System.out.println("system time -" + (base_date + expiryInMillies));
if (timestampinMillies > (base_date + expiryInMillies)) {
showerrorDialog("Demo version expired please contact vendor support");
System.out.println("expired");
}
}
}
private class CallAPI extends AsyncTask<String, String, String> {
@Override
protected void onPreExecute() {
// TODO Auto-generated method stub
super.onPreExecute();
}
@Override
protected String doInBackground(String... params) {
String urlString = params[0]; // URL to call
String resultToDisplay = "";
InputStream in = null;
// HTTP Get
try {
URL url = new URL(urlString);
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
in = new BufferedInputStream(urlConnection.getInputStream());
resultToDisplay = convertStreamToString(in);
} catch (Exception e) {
System.out.println(e.getMessage());
return e.getMessage();
}
return resultToDisplay;
}
protected void onPostExecute(String result) {
int isError = 1;
long timestamp = 0;
if (result == null || result.length() == 0 || result.indexOf("<timestamp>") == -1 || result.indexOf("</timestamp>") == -1) {
System.out.println("Error $$$$$$$$$");
} else {
String strTime = result.substring(result.indexOf("<timestamp>") + 11, result.indexOf("</timestamp>"));
System.out.println(strTime);
try {
timestamp = Long.parseLong(strTime) * 1000;
isError = 0;
} catch (NumberFormatException ne) {
}
}
checkExpiry(isError, timestamp);
}
} // end CallAPI
public static boolean isDataConnectionAvailable(Context context) {
ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo info = connectivityManager.getActiveNetworkInfo();
if (info == null)
return false;
return connectivityManager.getActiveNetworkInfo().isConnected();
}
public String convertStreamToString(InputStream is) throws IOException {
if (is != null) {
Writer writer = new StringWriter();
char[] buffer = new char[1024];
try {
Reader reader = new BufferedReader(new InputStreamReader(is, "UTF-8"));
int n;
while ((n = reader.read(buffer)) != -1) {
writer.write(buffer, 0, n);
}
} finally {
is.close();
}
return writer.toString();
} else {
return "";
}
}
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
}
}
它的工作解决方案.....
这是我的工作方式,我创建了2个应用,其中一个具有试用活动,另一个没有,
我上传了一个没有试用活动的应用,将其作为付费应用播放商店,
以及具有免费试用版活动的应用程序。
首次启动时免费的应用程序具有试用和商店购买的选项,如果用户选择商店购买,它将重定向到商店供用户购买,但是如果用户单击试用,则将其带入试用活动。
注意:我使用了@snctln之类的选项3,但进行了修改
首先,我不依赖于设备时间,我的时间来自进行向数据库进行注册的php文件,
其次,我使用设备序列号来唯一标识每个设备,
最后,该应用程序依赖于从服务器连接返回的时间值,而不是其自己的时间,因此,仅当设备序列号发生更改时,才可以绕开系统,这对用户来说是一个很大的压力。
这是我的代码(用于“试用”活动):
package com.example.mypackage.my_app.Start_Activity.activity;
import android.Manifest;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.graphics.Color;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v7.app.AppCompatActivity;
import android.telephony.TelephonyManager;
import android.view.KeyEvent;
import android.widget.TextView;
import com.android.volley.Request;
import com.android.volley.RequestQueue;
import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.JsonObjectRequest;
import com.android.volley.toolbox.Volley;
import com.example.onlinewisdom.cbn_app.R;
import com.example.mypackage.my_app.Start_Activity.app.Config;
import com.example.mypackage.my_app.Start_Activity.data.TrialData;
import com.example.mypackage.my_app.Start_Activity.helper.connection.Connection;
import com.google.gson.Gson;
import org.json.JSONObject;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import cn.pedant.SweetAlert.SweetAlertDialog;
public class Trial extends AppCompatActivity {
Connection check;
SweetAlertDialog pDialog;
TextView tvPleaseWait;
private static final int MY_PERMISSIONS_REQUEST_READ_PHONE_STATE = 0;
String BASE_URL = Config.BASE_URL;
String BASE_URL2 = BASE_URL+ "/register_trial/"; //http://ur link to ur API
//KEY
public static final String KEY_IMEI = "IMEINumber";
private final SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
private final long ONE_DAY = 24 * 60 * 60 * 1000;
SharedPreferences preferences;
String installDate;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_trial);
preferences = getPreferences(MODE_PRIVATE);
installDate = preferences.getString("InstallDate", null);
pDialog = new SweetAlertDialog(this, SweetAlertDialog.PROGRESS_TYPE);
pDialog.getProgressHelper().setBarColor(Color.parseColor("#008753"));
pDialog.setTitleText("Loading...");
pDialog.setCancelable(false);
tvPleaseWait = (TextView) findViewById(R.id.tvPleaseWait);
tvPleaseWait.setText("");
if(installDate == null) {
//register app for trial
animateLoader(true);
CheckConnection();
} else {
//go to main activity and verify there if trial period is over
Intent i = new Intent(Trial.this, MainActivity.class);
startActivity(i);
// close this activity
finish();
}
}
public void CheckConnection() {
check = new Connection(this);
if (check.isConnected()) {
//trigger 'loadIMEI'
loadIMEI();
} else {
errorAlert("Check Connection", "Network is not detected");
tvPleaseWait.setText("Network is not detected");
animateLoader(false);
}
}
public boolean onKeyDown(int keyCode, KeyEvent event) {
//Changes 'back' button action
if (keyCode == KeyEvent.KEYCODE_BACK) {
finish();
}
return true;
}
public void animateLoader(boolean visibility) {
if (visibility)
pDialog.show();
else
pDialog.hide();
}
public void errorAlert(String title, String msg) {
new SweetAlertDialog(this, SweetAlertDialog.ERROR_TYPE)
.setTitleText(title)
.setContentText(msg)
.show();
}
/**
* Called when the 'loadIMEI' function is triggered.
*/
public void loadIMEI() {
// Check if the READ_PHONE_STATE permission is already available.
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_PHONE_STATE)
!= PackageManager.PERMISSION_GRANTED) {
// READ_PHONE_STATE permission has not been granted.
requestReadPhoneStatePermission();
} else {
// READ_PHONE_STATE permission is already been granted.
doPermissionGrantedStuffs();
}
}
/**
* Requests the READ_PHONE_STATE permission.
* If the permission has been denied previously, a dialog will prompt the user to grant the
* permission, otherwise it is requested directly.
*/
private void requestReadPhoneStatePermission() {
if (ActivityCompat.shouldShowRequestPermissionRationale(this,
Manifest.permission.READ_PHONE_STATE)) {
// Provide an additional rationale to the user if the permission was not granted
// and the user would benefit from additional context for the use of the permission.
// For example if the user has previously denied the permission.
new AlertDialog.Builder(Trial.this)
.setTitle("Permission Request")
.setMessage(getString(R.string.permission_read_phone_state_rationale))
.setCancelable(false)
.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
//re-request
ActivityCompat.requestPermissions(Trial.this,
new String[]{Manifest.permission.READ_PHONE_STATE},
MY_PERMISSIONS_REQUEST_READ_PHONE_STATE);
}
})
.setIcon(R.drawable.warning_sigh)
.show();
} else {
// READ_PHONE_STATE permission has not been granted yet. Request it directly.
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_PHONE_STATE},
MY_PERMISSIONS_REQUEST_READ_PHONE_STATE);
}
}
/**
* Callback received when a permissions request has been completed.
*/
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
if (requestCode == MY_PERMISSIONS_REQUEST_READ_PHONE_STATE) {
// Received permission result for READ_PHONE_STATE permission.est.");
// Check if the only required permission has been granted
if (grantResults.length == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// READ_PHONE_STATE permission has been granted, proceed with displaying IMEI Number
//alertAlert(getString(R.string.permision_available_read_phone_state));
doPermissionGrantedStuffs();
} else {
alertAlert(getString(R.string.permissions_not_granted_read_phone_state));
}
}
}
private void alertAlert(String msg) {
new AlertDialog.Builder(Trial.this)
.setTitle("Permission Request")
.setMessage(msg)
.setCancelable(false)
.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
// do somthing here
}
})
.setIcon(R.drawable.warning_sigh)
.show();
}
private void successAlert(String msg) {
new SweetAlertDialog(this, SweetAlertDialog.SUCCESS_TYPE)
.setTitleText("Success")
.setContentText(msg)
.setConfirmText("Ok")
.setConfirmClickListener(new SweetAlertDialog.OnSweetClickListener() {
@Override
public void onClick(SweetAlertDialog sDialog) {
sDialog.dismissWithAnimation();
// Prepare intent which is to be triggered
//Intent i = new Intent(Trial.this, MainActivity.class);
//startActivity(i);
}
})
.show();
}
public void doPermissionGrantedStuffs() {
//Have an object of TelephonyManager
TelephonyManager tm =(TelephonyManager)getSystemService(Context.TELEPHONY_SERVICE);
//Get IMEI Number of Phone //////////////// for this example i only need the IMEI
String IMEINumber = tm.getDeviceId();
/************************************************
* **********************************************
* This is just an icing on the cake
* the following are other children of TELEPHONY_SERVICE
*
//Get Subscriber ID
String subscriberID=tm.getDeviceId();
//Get SIM Serial Number
String SIMSerialNumber=tm.getSimSerialNumber();
//Get Network Country ISO Code
String networkCountryISO=tm.getNetworkCountryIso();
//Get SIM Country ISO Code
String SIMCountryISO=tm.getSimCountryIso();
//Get the device software version
String softwareVersion=tm.getDeviceSoftwareVersion()
//Get the Voice mail number
String voiceMailNumber=tm.getVoiceMailNumber();
//Get the Phone Type CDMA/GSM/NONE
int phoneType=tm.getPhoneType();
switch (phoneType)
{
case (TelephonyManager.PHONE_TYPE_CDMA):
// your code
break;
case (TelephonyManager.PHONE_TYPE_GSM)
// your code
break;
case (TelephonyManager.PHONE_TYPE_NONE):
// your code
break;
}
//Find whether the Phone is in Roaming, returns true if in roaming
boolean isRoaming=tm.isNetworkRoaming();
if(isRoaming)
phoneDetails+="\nIs In Roaming : "+"YES";
else
phoneDetails+="\nIs In Roaming : "+"NO";
//Get the SIM state
int SIMState=tm.getSimState();
switch(SIMState)
{
case TelephonyManager.SIM_STATE_ABSENT :
// your code
break;
case TelephonyManager.SIM_STATE_NETWORK_LOCKED :
// your code
break;
case TelephonyManager.SIM_STATE_PIN_REQUIRED :
// your code
break;
case TelephonyManager.SIM_STATE_PUK_REQUIRED :
// your code
break;
case TelephonyManager.SIM_STATE_READY :
// your code
break;
case TelephonyManager.SIM_STATE_UNKNOWN :
// your code
break;
}
*/
// Now read the desired content to a textview.
//tvPleaseWait.setText(IMEINumber);
UserTrialRegistrationTask(IMEINumber);
}
/**
* Represents an asynchronous login task used to authenticate
* the user.
*/
private void UserTrialRegistrationTask(final String IMEINumber) {
JsonObjectRequest jsonObjectRequest = new JsonObjectRequest(Request.Method.GET, BASE_URL2+IMEINumber, null,
new Response.Listener<JSONObject>() {
@Override
public void onResponse(JSONObject response) {
Gson gson = new Gson();
TrialData result = gson.fromJson(String.valueOf(response), TrialData.class);
animateLoader(false);
if ("true".equals(result.getError())) {
errorAlert("Error", result.getResult());
tvPleaseWait.setText("Unknown Error");
} else if ("false".equals(result.getError())) {
//already created install/trial_start date using the server
// so just getting the date called back
Date before = null;
try {
before = (Date)formatter.parse(result.getResult());
} catch (ParseException e) {
e.printStackTrace();
}
Date now = new Date();
assert before != null;
long diff = now.getTime() - before.getTime();
long days = diff / ONE_DAY;
// save the date received
SharedPreferences.Editor editor = preferences.edit();
editor.putString("InstallDate", String.valueOf(days));
// Commit the edits!
editor.apply();
//go to main activity and verify there if trial period is over
Intent i = new Intent(Trial.this, MainActivity.class);
startActivity(i);
// close this activity
finish();
//successAlert(String.valueOf(days));
//if(days > 5) { // More than 5 days?
// Expired !!!
//}
}
}
},
new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
animateLoader(false);
//errorAlert(error.toString());
errorAlert("Check Connection", "Could not establish a network connection.");
tvPleaseWait.setText("Network is not detected");
}
})
{
protected Map<String, String> getParams() {
Map<String, String> params = new HashMap<String, String>();
params.put(KEY_IMEI, IMEINumber);
return params;
}
};
RequestQueue requestQueue = Volley.newRequestQueue(this);
requestQueue.add(jsonObjectRequest);
}
}
我的php文件看起来像这样(它是REST-slim技术):
/**
* registerTrial
*/
public function registerTrial($IMEINumber) {
//check if $IMEINumber already exist
// Instantiate DBH
$DBH = new PDO_Wrapper();
$DBH->query("SELECT date_reg FROM trials WHERE device_id = :IMEINumber");
$DBH->bind(':IMEINumber', $IMEINumber);
// DETERMINE HOW MANY ROWS OF RESULTS WE GOT
$totalRows_registered = $DBH->rowCount();
// DETERMINE HOW MANY ROWS OF RESULTS WE GOT
$results = $DBH->resultset();
if (!$IMEINumber) {
return 'Device serial number could not be determined.';
} else if ($totalRows_registered > 0) {
$results = $results[0];
$results = $results['date_reg'];
return $results;
} else {
// Instantiate variables
$trial_unique_id = es_generate_guid(60);
$time_reg = date('H:i:s');
$date_reg = date('Y-m-d');
$DBH->beginTransaction();
// opening db connection
//NOW Insert INTO DB
$DBH->query("INSERT INTO trials (time_reg, date_reg, date_time, device_id, trial_unique_id) VALUES (:time_reg, :date_reg, NOW(), :device_id, :trial_unique_id)");
$arrayValue = array(':time_reg' => $time_reg, ':date_reg' => $date_reg, ':device_id' => $IMEINumber, ':trial_unique_id' => $trial_unique_id);
$DBH->bindArray($arrayValue);
$subscribe = $DBH->execute();
$DBH->endTransaction();
return $date_reg;
}
}
然后在主要活动上,我使用共享首选项(在试用活动中创建的installDate)来监视剩余天数,如果天数已满,我会阻止主活动UI,并显示一条消息,将其带到商店进行购买。
我在这里看到的唯一缺点是,如果Rogue用户购买了付费应用程序并决定与Zender等应用程序共享,文件共享,甚至直接将apk文件托管在服务器上,人们可以免费下载。但可以肯定的是,我很快会用一个解决方案或该解决方案的链接来编辑此答案。
希望这可以拯救灵魂...有一天
快乐编码...
@snctln选项3可以很容易地完成,将php文件添加到安装了php和mysql的web服务器上,就像安装了许多文件一样。
从Android方面来看,使用HttpURLConnection将标识符(设备ID,Google帐户或您想要的任何名称)作为URL中的参数传递,并且php返回首次安装的日期(如果它存在于表中)或插入新行并它返回当前日期。
这对我来说可以。
如果有时间,我会发布一些代码!
祝好运 !