有料アプリとして市場に出したいアプリケーションがあります。5 日間などの期限付きの「試用」バージョンになるような他のバージョンが欲しいですか?
どうすればこれを行うことができますか?
有料アプリとして市場に出したいアプリケーションがあります。5 日間などの期限付きの「試用」バージョンになるような他のバージョンが欲しいですか?
どうすればこれを行うことができますか?
現在、ほとんどの開発者は、次の3つの手法のいずれかを使用してこれを実現しています。
最初のアプローチは簡単に回避できます。アプリを初めて実行するときに、日付/時刻をファイル、データベース、または共有設定に保存し、その後アプリを実行するたびに、試用期間が終了したかどうかを確認します。アンインストールして再インストールすると、ユーザーは別の試用期間を持つことができるため、これは簡単に回避できます。
2番目のアプローチは回避するのが難しいですが、それでも回避できます。ハードコードされた時限爆弾を使用します。基本的に、このアプローチでは、試用の終了日をハードコーディングし、アプリをダウンロードして使用するすべてのユーザーが同時にアプリを使用できなくなります。このアプローチを使用したのは、実装が簡単で、ほとんどの場合、3番目の手法の問題を経験する気がしなかったためです。ユーザーは携帯電話の日付を手動で変更することでこれを回避できますが、ほとんどのユーザーはそのようなことをするのに苦労することはありません。
3番目のテクニックは、あなたがやりたいことを本当に達成できるようにするために私が聞いた唯一の方法です。サーバーをセットアップする必要があります。そうすると、アプリケーションが起動するたびに、アプリは電話の一意の識別子をサーバーに送信します。サーバーにその電話IDのエントリがない場合は、新しいものを作成して時刻を記録します。サーバーに電話IDのエントリがある場合は、試用期間が終了したかどうかを確認するための簡単なチェックを実行します。次に、試用期間の有効期限チェックの結果をアプリケーションに通知します。このアプローチは回避できないはずですが、Webサーバーなどを設定する必要があります。
onCreateでこれらのチェックを行うことは常に良い習慣です。有効期限が終了した場合は、アプリのフルバージョンへのマーケットリンクを含むAlertDialogがポップアップ表示されます。「OK」ボタンのみを含め、ユーザーが「OK」をクリックしたら、「finish()」を呼び出してアクティビティを終了します。
これは古い質問ですが、とにかく、これは誰かを助けるでしょう.
最も単純なアプローチ(アプリがアンインストール/再インストールされた場合、またはユーザーがデバイスの日付を手動で変更した場合に失敗します) を使用する場合は、次のようになります。
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 !!!
}
}
...
}
この質問とsnctlnの答えは、学士論文として方法 3 に基づく解決策に取り組むきっかけになりました。現在の状況が生産的な使用法ではないことは承知していますが、それについてどう思うか楽しみにしています! あなたはそのような制度を利用しますか。クラウド サービスとして表示しますか (サーバーの構成に問題はありません)。セキュリティの問題や安定性の理由が気になりますか?
学士課程を修了したらすぐに、ソフトウェアの作業を続けたいと思っています。だから今こそ、あなたのフィードバックが必要な時です!
ソースコードは GitHub でホストされています https://github.com/MaChristmann/mobile-trial
システムに関する情報: - システムには、Android ライブラリ、node.js サーバー、および複数の試用アプリとパブリッシャー/開発者アカウントを管理するためのコンフィギュレーターの 3 つの部分があります。
時間ベースの試用のみをサポートし、電話 ID ではなく (プレイ ストアまたはその他の) アカウントを使用します。
Android ライブラリの場合、Google Play ライセンス検証ライブラリに基づいています。node.js サーバーに接続するように変更しました。さらに、ライブラリは、ユーザーがシステム日付を変更したかどうかを認識しようとします。また、取得したトライアル ライセンスを AES 暗号化された共有設定にキャッシュします。コンフィギュレーターでキャッシュの有効時間を設定できます。ユーザーが「データを消去」すると、ライブラリはサーバー側のチェックを強制します。
サーバーは https を使用しており、ライセンス チェック応答のデジタル署名も使用しています。また、CRUD トライアル アプリとユーザー (パブリッシャーと開発者) 用の API もあります。Licensing Verfication Library と同様に、開発者はテスト結果を使用して試用アプリで動作の実装をテストできます。したがって、コンフィギュレーターで、ライセンス応答を「ライセンスあり」、「ライセンスなし」、または「サーバー エラー」に明示的に設定できます。
とんでもない新機能でアプリを更新した場合、誰もがもう一度試してみたいと思うかもしれません。コンフィギュレーターでは、これをトリガーするバージョンコードを設定することにより、期限切れのライセンスを持つユーザーの試用ライセンスを更新できます。たとえば、ユーザーがバージョンコード 3 でアプリを実行していて、バージョンコード 4 の機能を試してほしいとします。ユーザーがアプリを更新または再インストールすると、サーバーはユーザーが最後に試したバージョンを認識しているため、試用期間全体を再度使用できます。時間。
すべてがApache 2.0ライセンスの下にあります
これを行う最も簡単で最良の方法は、BackupSharedPreferences を実装することです。
アプリをアンインストールして再インストールしても、設定は保持されます。
インストール日を設定として保存するだけで、準備完了です。
理論は次のとおりです。 http://developer.android.com/reference/android/app/backup/SharedPreferencesBackupHelper.html
例は次のとおりです: Android SharedPreferences バックアップが機能しない
定義上、市場に出回っているすべての有料Androidアプリは、購入後24時間評価できます。
24時間後に「アンインストール」に変わる「アンインストールと返金」ボタンがあります。
このボタンはあまりにも目立つと思います!
同じ問題を探しているときにこの質問に出くわしました。http: //www.timeapi.org/utc/now などの無料の日付 APIやその他の日付 API を利用して、トレイル アプリの有効期限を確認できると思います。この方法は、デモを配信したいが、支払いが心配で、修正期間のデモが必要な場合に効率的です。:)
以下のコードを見つけます
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
}
}
その実用的なソリューション.....