Создание пробного приложения для Android, срок действия которого истекает через фиксированный период времени


У меня есть приложение, которое я хочу попасть на рынок в качестве платного приложения. Я хотел бы иметь другую версию, которая была бы "пробной" версией с ограничением по времени, скажем, 5 дней?

Как я могу это сделать?

13 100

13 ответов:

в настоящее время большинство разработчиков сделать это, используя один из 3 методов.

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

второй подход сложнее обойти, но все равно circumventable. Используйте жестко закодированную бомбу замедленного действия. В основном при таком подходе вы будете жестко кодировать дату окончания пробной версии, и все пользователи, которые загружают и используют приложение, перестанут использовать приложение одновременно. Я использовал этот подход, потому что его легко реализовать, и по большей части мне просто не хотелось проходить через проблемы третьей техники. Пользователи могут обойти это, вручную менять дату на своем телефоне, но большинство пользователей не пойду через беду сделать такую вещь.

третий метод-это единственный способ, о котором я слышал, чтобы действительно быть в состоянии выполнить то, что вы хотите сделать. Вам нужно будет настроить сервер, а затем, когда ваше приложение запускается, ваше приложение отправляет уникальный идентификатор телефона на сервер. Если сервер не имеет записи для этого идентификатора телефона, то он делает новый и отмечает время. Если сервер имеет запись для идентификатора телефона, то он делает простой проверьте, не истек ли пробный период. Затем он передает результаты пробной проверки истечения срока действия обратно в приложение. Этот подход не должен быть обходным, но требует настройки веб-сервера и т. д.

Это всегда хорошая практика, чтобы сделать эти проверки в метод onCreate. Если истечение закончилось всплывающее окно AlertDialog с market link в полной версии приложения. Только включите кнопку "ОК", и как только пользователь нажимает на кнопку "ОК", сделайте вызовите "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 !!!
        }
    }

    ...
}

Я разработал Android Trial 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); Ваш ключ приложения и пробный SKU можно найти в вашем Trialy разработчик приборной панели.

Эй, ребята, этот вопрос и ответ snctln вдохновил меня на работу над решением, основанным на методе 3, как моя бакалаврская диссертация. Я знаю, что текущее состояние не для продуктивного использования, но я хотел бы услышать, что вы думаете об этом! Вы бы использовали такую систему? Вы хотели бы видеть его как облачный сервис (не имея проблем с настройкой сервера)? Обеспокоены проблемами безопасности или причинами стабильности? А как только я закончу процедуру бакалавра я хочу продолжить работать на программное обеспечение. Так что теперь пришло время мне нужна ваша обратная связь!

исходный код размещен на GitHub https://github.com/MaChristmann/mobile-trial

информация о системе: - Система состоит из трех частей, библиотека Android, узел.JS-сервер и конфигуратор для управления несколькими пробными приложениями и учетными записями издателя/разработчика.

  • Он поддерживает только временные испытания и использует вашу учетную запись (play store или другую), а чем идентификатор телефона.

  • для библиотеки Android он основан на библиотеке проверки лицензирования Google Play. Я изменил его для подключения к узлу.JS-сервер и, кроме того, библиотека пытается распознать, изменил ли пользователь системную дату. Он также кэширует полученную пробную лицензию в зашифрованных общих настройках AES. Вы можете настроить допустимое время кэша с помощью конфигуратора. Если пользователь "очистит данные", библиотека принудительно выполнит проверку на стороне сервера.

  • сервер использует https, а также цифровую подпись ответа проверки лицензии. Он также имеет API для CRUD пробных приложений и пользователей (издатель и разработчик). По аналогии с лицензированием Верфикационных библиотек разработчики могут проверить свое поведение реализации в пробном приложении с результатом теста. Таким образом, вы в конфигураторе можете явно установить свой ответ лицензии на "лицензированный", "не лицензированный" или "ошибка сервера".

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

  • все под лицензией Apache 2.0

самый простой и лучшие способ сделать это-реализовать BackupSharedPreferences.

настройки сохраняются, даже если приложение удалить и переустановить.

просто сохраните дату установки в качестве предпочтения, и вы хорошо идти.

вот теория: http://developer.android.com/reference/android/app/backup/SharedPreferencesBackupHelper.html

вот пример: Android SharedPreferences Резервное Копирование Не Работает

подход 4: Используйте время установки приложения.

начиная с уровня API 9 (Android 2.3.2, 2.3.1, Android 2.3, GINGERBREAD) есть firstInstallTime и lastUpdateTime на PackageInfo.

Читать подробнее: как получить время установки приложения с android

теперь в последней версии android была добавлена бесплатная пробная подписка, вы можете разблокировать все функции вашего приложения только после покупки подписки в приложении на БЕСПЛАТНЫЙ ПРОБНЫЙ период. Это позволит пользователю использовать ваше приложение в течение пробного периода, если приложение все еще будет удалено после пробного периода, то деньги за подписку будут переданы вам. Я не пробовал, а просто делился идеей.

здесь

на мой взгляд, лучший способ сделать это-просто использовать базу данных военнослужащих в реальном времени:

1) добавьте поддержку Firebase в свое приложение

2) Выберите "анонимная аутентификация", чтобы пользователю не нужно было регистрироваться или даже знать, что вы делаете. Это гарантирует связь с текущей проверенной учетной записью пользователя и поэтому будет работать на разных устройствах.

3) используйте API базы данных реального времени, чтобы установить значение для 'installed_date'. Во время запуска, просто извлеките это значение и используйте его.

Я сделал то же самое и он отлично работает. Я смог проверить это через удаление / повторную установку, и значение в базе данных реального времени остается прежним. Таким образом, ваш пробный период работает на нескольких устройствах пользователя. Вы даже можете обновить свой каталог_установки, чтобы приложение "сбрасывало" дату пробной версии для каждого нового основного выпуска.

обновление: после тестирования немного больше, кажется, анонимный Firebase, кажется, выделить другой ID в случае, если у вас есть разные устройства и не гарантируется между переустановками :/ единственный гарантированный способ-использовать Firebase, но привязать его к своей учетной записи google. Это должно работать, но потребует дополнительного шага, когда пользователю сначала нужно Войти / зарегистрироваться.

Я до сих пор в конечном итоге с немного менее элегантным подходом просто проверки резервных копий предпочтений и даты, хранящиеся в настройках при установке. Это работает для приложений, ориентированных на данные, где это бессмысленно для человек, чтобы переустановить приложение и повторно ввести все данные, ранее добавленные, но не будет работать для простой игры.

после просмотра всех вариантов в этом и других потоках, это мои выводы

Общие настройки, базы данных Можно очистить в настройках android, потерянных после переустановки приложения. можно создать резервную копию с помощью механизма резервного копирования android и будет восстановлен после переустановки. Резервное копирование не всегда может быть доступно, хотя должно быть на большинстве устройств

внешняя память (запись в файл) Не повлиянный на ясным от настройки или переустановка, если мы не пишите в личный каталог приложения. Но:требует, чтобы вы попросили у пользователя разрешения во время выполнения в новых версиях android, так что это, вероятно, возможно только в том случае, если вам нужно это разрешение в любом случае. Также можно создать резервную копию.

PackageInfo.firstInstallTime Сбрасывается после переустановки, но стабилен во всех обновлениях

войти в некоторые аккаунт Не имеет значения, является ли это их учетная запись Google через Firebase или один на вашем собственном сервере: пробная версия привязана к учетной записи. Создание новой учетной записи приведет к сбросу пробной версии.

Firebase анонимный вход Вы можете войти в систему пользователя анонимно и хранить данные для них в Firebase. Но по-видимому, переустановка приложения и, возможно, другие недокументированные события могут дать пользователю новый анонимный идентификатор, со сбросом времени судебного разбирательства. (Гуглите сами не предоставьте много документации по этому вопросу)

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

Play Advertising ID Может быть сброшен пользователем. может быть отключен пользователем, отказавшись от отслеживания рекламы.

InstanceID сброс на переустановить. "сброс" в случае события в системе безопасности. Может быть сброшен вашим приложением.

какие (комбинации) методы работают для вас, зависит от вашего приложения и от того, сколько усилий, по вашему мнению, средний Джон вложит в получение другого пробного периода. Я бы рекомендовал рулевое управление очистить от использования только анонимный Firebase и рекламный идентификатор из-за их нестабильности. Многофакторный подход, похоже, даст наилучшие результаты. Какие факторы доступны зависит от приложения и разрешения.

для моего собственного приложения я нашел общие предпочтения + firstInstallTime + резервное копирование предпочтений, чтобы быть наименее навязчивым, но и достаточно эффективным методом. Вы должны убедиться, что вы запрашиваете резервную копию только после проверки и сохранения времени начала пробной версии в общих настройках. Значения в общих префах должны иметь приоритет над firstInstallTime. Затем пользователь должен переустановить приложение, запустить его один раз, а затем очистить данные приложения для сброса судебный процесс, в котором довольно много работы. Однако на устройствах без резервного транспорта пользователь может сбросить пробную версию, просто переустановив ее.

Я сделал этот подход доступен как расширяемая библиотека.

по определению все платные приложения для Android на рынке можно оценить в течение 24 часов после покупки.

есть кнопка "Удалить и вернуть", которая меняется на "Удалить" через 24 часа.

Я бы сказал, что эта кнопка слишком заметна!

я сталкиваюсь с этим вопросом при поиске той же проблемы, я думаю, что мы можем использовать бесплатный api даты, как http://www.timeapi.org/utc/now или какой-либо другой 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 приложения один с пробной деятельностью другой без,

Я загрузил один без пробной активности, чтобы играть в магазин как платное приложение,

и один с судебной деятельностью в качестве бесплатного приложения.

бесплатное приложение при первом запуске имеет варианты для пробной и покупки в магазине, если пользователь выбирает магазин покупки он перенаправляет в магазин для пользователя, чтобы купить но если пользователь нажимает пробную версию, он берет их на пробную версию активность

NB: я использовал вариант 3, как @snctln, но с изменениями

первый, я не зависел от времени устройства, я получил свое время из файла 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, созданное в пробном действии), чтобы отслеживать количество оставшихся дней, и если дни закончились, я блокирую основной пользовательский интерфейс действия с сообщением, которое принимает их магазин для покупки.

единственная обратная сторона, которую я вижу здесь, это то, что если нищебродов пользователей покупает платное приложение и решает поделиться с приложениями, такими как Zender, file share или даже разместить файл apk непосредственно на сервере для людей, чтобы скачать бесплатно. Но я уверен, что скоро отредактирую этот ответ с решением для этого или ссылкой на решение.

надеюсь, это спасет душу...когда-нибудь

Удачи В Кодировании...

@snctln вариант 3 можно легко сделать добавление файла php на веб-сервер с php и mysql установлен, как многие из них имеют.

со стороны Android идентификатор (идентификатор устройства, учетная запись google o все, что вы хотите) передается в качестве аргумента в URL с помощью HttpURLConnection и php возвращает дату первой установки, если она существует в таблице или вставляет новую строку и возвращает текущую дату.

Он отлично работает для меня.

Если У меня есть время я выложу какой-то код !

Удачи !