Создание пробного приложения Android, срок действия которого истекает через фиксированный период времени. - PullRequest
102 голосов
/ 15 июня 2009

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

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

Ответы [ 13 ]

183 голосов
/ 15 июня 2009

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

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

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

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

Это всегда хорошая практика делать эти проверки в onCreate. Если срок действия закончился, откройте AlertDialog с рыночной ссылкой на полную версию приложения. Включите только кнопку «ОК», и, как только пользователь нажмет «ОК», вызовите «finish ()», чтобы завершить действие.

17 голосов
/ 17 апреля 2017

Я разработал 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 .

15 голосов
/ 03 февраля 2012

Это старый вопрос, но в любом случае, может, это кому-нибудь поможет.

В случае, если вы хотите использовать самый упрощенный подход (который завершится неудачей, если приложение будет удалено / переустановлено или пользователь изменяет дату устройства вручную), вот как это может быть:

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 !!!
        }
    }

    ...
}
10 голосов
/ 21 марта 2013

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

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

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

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

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

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

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

  • Все находится под лицензией Apache 2.0

6 голосов
/ 25 июля 2013

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

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

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

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

Вот пример: Резервное копирование общих ресурсов Android не работает

5 голосов
/ 09 декабря 2013

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

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

Читать больше: Как узнать время установки приложения с Android

3 голосов
/ 05 сентября 2013

Теперь в последней версии бесплатной пробной подписки на Android вы можете разблокировать все функции своего приложения только после покупки подписки в приложении в течение бесплатного пробного периода. Это позволит пользователю использовать ваше приложение в течение пробного периода. Если приложение все еще будет удалено после испытательного периода, вам будут переведены деньги за подписку. Я не пробовал, а просто поделился идеей.

Вот документация

2 голосов
/ 19 февраля 2017

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

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

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

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

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

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

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

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

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

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

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

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

2 голосов
/ 05 октября 2016

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

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

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

3) Используйте API-интерфейс базы данных реального времени, чтобы установить значение для «instal_date». Во время запуска просто получите это значение и используйте его.

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

ОБНОВЛЕНИЕ : После небольшого тестирования кажется, что анонимный Firebase выделяет другой идентификатор в случае, если у вас разные устройства и между переустановками нет гарантии: использовать Firebase, но привязать его к своей учетной записи Google. Это должно работать, но потребует дополнительного шага, когда пользователь сначала должен войти / зарегистрироваться.

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

1 голос
/ 18 февраля 2014

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

}
}

его рабочий раствор .....

...