Membuat aplikasi uji coba Android yang kedaluwarsa setelah jangka waktu tetap


103

Saya memiliki aplikasi yang ingin saya pasarkan sebagai aplikasi Berbayar. Saya ingin memiliki versi lain yang merupakan versi "percobaan" dengan batas waktu katakanlah, 5 hari?

Bagaimana saya bisa melakukan ini?


Google harus benar-benar mendukung ini di Layanan Play!
bedak366

@ powder366 sebenarnya Google mendukung ini, lihat developer.android.com/google/play/billing/…
Yazazzello

Jawaban:


186

Saat ini sebagian besar pengembang melakukannya dengan menggunakan salah satu dari 3 teknik berikut.

Pendekatan pertama mudah dielakkan, pertama kali Anda menjalankan aplikasi, simpan tanggal / waktu ke file, database, atau preferensi bersama dan setiap kali Anda menjalankan aplikasi setelah itu, periksa untuk melihat apakah masa uji coba telah berakhir. Ini mudah untuk dielakkan karena mencopot dan memasang ulang akan memungkinkan pengguna untuk menjalani masa percobaan lagi.

Pendekatan kedua lebih sulit untuk dielakkan, tetapi masih dapat dielakkan. Gunakan bom waktu berkode keras. Pada dasarnya dengan pendekatan ini Anda akan mendapatkan kode keras tanggal akhir untuk uji coba, dan semua pengguna yang mengunduh dan menggunakan aplikasi akan berhenti dapat menggunakan aplikasi pada saat yang bersamaan. Saya telah menggunakan pendekatan ini karena mudah diimplementasikan dan sebagian besar saya hanya tidak ingin mengalami masalah dengan teknik ketiga. Pengguna dapat menghindari ini dengan mengubah tanggal di ponsel mereka secara manual, tetapi sebagian besar pengguna tidak akan mengalami kesulitan untuk melakukan hal seperti itu.

Teknik ketiga adalah satu-satunya cara yang pernah saya dengar untuk benar-benar dapat mencapai apa yang ingin Anda lakukan. Anda harus menyiapkan server, dan kemudian kapan pun aplikasi Anda dimulai, aplikasi Anda mengirimkan pengenal unik ponsel ke server. Jika server tidak memiliki entri untuk id telepon itu maka itu membuat yang baru dan mencatat waktunya. Jika server memang memiliki entri untuk id telepon maka itu melakukan pemeriksaan sederhana untuk melihat apakah masa percobaan telah kedaluwarsa. Hasil uji coba kedaluwarsa akan dikomunikasikan kembali ke aplikasi Anda. Pendekatan ini seharusnya tidak dapat dielakkan, tetapi memerlukan penyiapan server web dan semacamnya.

Melakukan pemeriksaan ini di onCreate selalu merupakan praktik yang baik. Jika kedaluwarsa telah berakhir, popup AlertDialog dengan tautan pasar ke versi lengkap aplikasi. Hanya sertakan tombol "OK", dan setelah pengguna mengklik "OK", lakukan panggilan ke "finish ()" untuk mengakhiri aktivitas.


2
Jawaban yang fantastis. Seperti yang Anda katakan, saya merasa opsi kedua mungkin yang terbaik. Sayang sekali Google sendiri tidak menawarkan semacam sistem perizinan karena dapat mendorong pengembang merek kecil dan besar untuk menghasilkan lebih banyak aplikasi Android.
Tom

8
Juga, saya tidak akan memeriksa selama startup. Tujuan Anda adalah menjual aplikasi, bukan menghukum pengguna (itu hanya bonus;) Jika Anda menyetelnya untuk diperiksa setiap 2 menit saat berjalan, Anda membiarkan pengguna mulai melakukan sesuatu dan kemudian menyadari bahwa mereka harus membayar. Jika Anda membuatnya sangat mudah untuk membayar dan kembali bekerja (saya tidak yakin apakah Anda bisa di Android), saya pikir Anda akan menjual lebih banyak daripada memeriksa selama onCreate.
Whaledawg

4
@Whaledawg: Anda perlu menjalankan server Anda sendiri karena server menyimpan id telepon dan waktu pertama kali dijalankan dalam database yang kemudian dibandingkan dengan nanti Juga ketika Anda melakukan pemeriksaan murni preferensi pengembang, saya menggunakan hard kode bom waktu dalam game dengan hasil yang bagus. Seluruh aplikasi akan dimuat, tetapi pengguna hanya dapat berinteraksi dengan dialog yang terlihat, ada tombol di dialog itu yang membawa pengguna langsung ke halaman pembelian untuk game tersebut. Pengguna tampaknya tidak mempermasalahkan AFAIK karena game itu telah masuk dalam 10 besar aplikasi berbayar sejak pembukaan Android Market.
snctln

11
Bagi siapa pun yang enggan menggunakan opsi 3 karena penyiapan server tambahan, lihat Parse.com - ini selaras.
Joel Skrepnek

3
Apa yang dimaksud dengan tanggal akhir uji coba hardcode? Apakah itu berarti Anda akan terus merilis versi baru dari aplikasi uji coba selamanya dengan tanggal hardcode berbeda di masa mendatang?
Jasper

21

Saya telah mengembangkan Android Trial SDK yang cukup Anda masukkan ke dalam proyek Android Studio Anda dan itu akan mengurus semua manajemen sisi server untuk Anda (termasuk masa tenggang offline).

Untuk menggunakannya, cukup

Tambahkan pustaka ke modul utama Anda build.gradle

dependencies {
  compile 'io.trialy.library:trialy:1.0.2'
}

Inisialisasi pustaka dalam onCreate()metode aktivitas utama Anda

@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);
}

Tambahkan penangan panggilan balik:

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));
    }

};

Untuk memulai uji coba, panggil mTrialy.startTrial("YOUR_TRIAL_SKU", mTrialyCallback); Kunci aplikasi Anda dan SKU uji coba dapat ditemukan di dasbor pengembang Trialy Anda .


Ini membutuhkan data untuk diaktifkan?
Sivaram Boina

trialy tidak dapat diandalkan
Amir Dora

1
@AmirDe Hai Amir, bisakah Anda memberi tahu saya apa yang tidak berhasil untuk Anda? Saya senang membantu, support@trialy.io Trialy bekerja dengan baik untuk 1000+ pengguna
Nick

@Nick tidak tahu alasan perangkat saya menjalankan android lollipop, ketika saya mengatur hari dari dasbor, kemudian hari muncul dalam nilai negatif setelah beberapa menit, dikatakan uji coba telah kedaluwarsa, meskipun saya masih memiliki banyak hari di dasbor. Saya juga menguji pada perangkat nougat, tampaknya berfungsi dengan baik pada naugat. mungkin ada beberapa masalah kompatibilitas versi Android yang lebih lama
Amir Dora

1
Saya menggunakan layanan ini sejak 2016, berfungsi dengan baik setiap saat. Saya juga menggunakan ini dalam proyek resmi saya. Ini harus menjadi jawaban yang diterima.
Tariq Mahmood

17

Ini adalah pertanyaan lama tapi bagaimanapun, mungkin ini akan membantu seseorang.

Jika Anda ingin menggunakan pendekatan yang paling sederhana (yang akan gagal jika aplikasi di-uninstal / diinstal ulang atau pengguna mengubah tanggal perangkat secara manual), begini caranya:

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

    ...
}

Sebenarnya jika Anda menggunakan SharedPreferences dan Android 2.2 Froyo atau lebih tinggi, selama Anda menerapkan API Cadangan Data untuk Sinkronisasi Data Google, pengguna seharusnya tidak dapat menghindarinya di seluruh perangkat atau dengan mencopot pemasangan, hanya dengan membuka Pengaturan> Aplikasi dan membersihkan data. Juga, metode Tanggal getTimetidak getTimeInMillis.
Tom

Tidak setuju, ini juga akan gagal jika pengguna mengubah tanggal Perangkat secara manual dan juga bagaimana jika Pengguna menghapus data secara manual?
Mohammed Azharuddin Shaikh

@ Caner ini bagus untuk diperiksa dengan sharedprefrence tetapi pengguna akan menghapus memori dari seting-> manajer aplikasi dan menggunakan kembali lalu apa yang akan dilakukan?
CoronaPintu

@CoronaPintu, sehingga pendekatan ini juga akan mencoba dengan firebase, ini akan membuat kombinasi yang sempurna, dan masa percobaan akan berakhir bahkan aplikasi akan di-uninstal.
Noor Hossain

menggabungkan dan menambahkan pendekatan dengan jawaban "strangetimes", ini akan membuat pendekatan menjadi sempurna.
Noor Hossain

10

Pertanyaan dan jawaban snctln ini menginspirasi saya untuk mencari solusi berdasarkan metode 3 sebagai skripsi saya. Saya tahu status saat ini bukan untuk penggunaan produktif tetapi saya ingin mendengar pendapat Anda tentang hal itu! Apakah Anda akan menggunakan sistem seperti itu? Apakah Anda ingin melihatnya sebagai layanan cloud (tidak mengalami masalah dengan konfigurasi server)? Khawatir tentang masalah keamanan atau alasan stabilitas?

A segera setelah saya menyelesaikan prosedur sarjana saya ingin melanjutkan mengerjakan perangkat lunak. Jadi sekarang saatnya saya membutuhkan tanggapan Anda!

Sourcecode dihosting di GitHub https://github.com/MaChristmann/mobile-trial

Beberapa informasi tentang sistem: - Sistem memiliki tiga bagian, pustaka Android, server node.js, dan konfigurator untuk mengelola beberapa aplikasi uji coba dan akun penerbit / pengembang.

  • Ini hanya mendukung uji coba berbasis waktu dan menggunakan akun (play store atau lainnya) Anda daripada ID telepon.

  • Untuk perpustakaan Android ini didasarkan pada perpustakaan verifikasi lisensi Google Play. Saya memodifikasinya untuk terhubung ke server node.js dan sebagai tambahan perpustakaan mencoba mengenali jika pengguna mengubah tanggal sistem. Itu juga menyimpan lisensi uji coba yang diambil di Preferensi Bersama terenkripsi AES. Anda dapat mengkonfigurasi waktu yang valid dari cache dengan konfigurator. Jika pengguna "menghapus data", pustaka akan memaksa pemeriksaan sisi server.

  • Server menggunakan https dan juga tanda tangan digital untuk respons pemeriksaan lisensi. Ini juga memiliki API untuk aplikasi dan pengguna uji coba CRUD (penerbit dan pengembang). Mirip dengan Licensing Verfication Library, developer dapat menguji implementasi perilakunya dalam aplikasi uji coba dengan hasil pengujian. Jadi Anda di konfigurator, Anda dapat secara eksplisit mengatur tanggapan lisensi Anda ke "berlisensi", "tidak berlisensi" atau "kesalahan server".

  • Jika Anda memperbarui aplikasi dengan fitur baru yang luar biasa, Anda mungkin ingin semua orang dapat mencobanya lagi. Di konfigurator, Anda dapat memperbarui lisensi uji coba untuk pengguna dengan lisensi yang telah kedaluwarsa dengan menyetel kode versi yang harus memicu ini. Misalnya pengguna menjalankan aplikasi Anda pada kode versi 3 dan Anda ingin dia mencoba fitur kode versi 4. Jika dia memperbarui aplikasi atau memasangnya kembali, dia dapat menggunakan periode uji coba penuh lagi karena server tahu pada versi mana dia terakhir mencobanya waktu.

  • Semuanya di bawah lisensi Apache 2.0


2
Anda baru saja menyelamatkan hari saya, terima kasih atas kerja kerasnya. Saya hanya berpikir untuk menulis solusi yang sama dengan menggunakan konfigurasi terenkripsi yang diperoleh hanya sekali dan menyimpan kunci publik di dalam aplikasi. Jadi masalah utama saya hanya bisa melihat bagaimana Anda memberikan lisensi? Anda mungkin masih memerlukan beberapa id unik yang dibawa dengan telepon untuk menghindari banyak hibah.
pengguna2305886

6

Cara termudah dan terbaik untuk melakukannya adalah dengan mengimplementasikan BackupSharedPreferences.

Preferensi dipertahankan, meskipun aplikasi dicopot dan dipasang ulang.

Cukup simpan tanggal penginstalan sebagai preferensi dan Anda siap melakukannya.

Berikut teorinya: http://developer.android.com/reference/android/app/backup/SharedPreferencesBackupHelper.html

Berikut contohnya: Android SharedPreferences Backup Not Working


3
Pengguna dapat menonaktifkan cadangan di pengaturan sistem.
Patrick

5

Pendekatan 4: gunakan waktu penginstalan aplikasi.

Sejak API level 9 (Android 2.3.2, 2.3.1, Android 2.3, GINGERBREAD) ada firstInstallTime dan lastUpdateTime di PackageInfo.

Untuk membaca lebih lanjut: Cara mendapatkan waktu penginstalan aplikasi dari android


Dapatkah metode 1 dari jawaban snctln digunakan dengan ini dengan andal, tanpa mudah dielakkan, atau apakah itu memiliki masalah yang sama atau serupa?
jwinn

Saya telah menguji metode ini. Sisi baiknya adalah ia berfungsi bahkan jika datanya dihapus. Sisi buruknya adalah hal itu dapat dielakkan dengan uninstall / instal ulang.
Jean-Philippe Jodoin

dapat mengonfirmasi: di Pixel saya, mencopot pemasangan dan memasang ulang aplikasi akan menyetel ulang waktu pemasangan pertama. yang membuatnya sangat sepele bagi pengguna untuk menyetel ulang uji coba
Fabian Streitel

3

Sekarang di versi terbaru dari langganan uji coba gratis android telah ditambahkan, Anda dapat membuka kunci semua fitur aplikasi Anda hanya setelah membeli langganan dalam aplikasi untuk masa percobaan gratis. Ini akan memungkinkan pengguna untuk menggunakan aplikasi Anda untuk masa percobaan, jika aplikasi masih dicopot setelah masa percobaan maka uang langganan akan ditransfer kepada Anda. Saya belum mencoba, tetapi hanya berbagi ide.

Berikut dokumentasinya


2
Saya berharap ini berhasil untuk pembelian tunggal. Hanya berfungsi untuk langganan, yang bisa tahunan atau bulanan.
jwinn

3

Menurut pendapat saya, cara terbaik untuk melakukannya adalah dengan menggunakan Firebase Realtime Database:

1) Tambahkan dukungan Firebase ke aplikasi Anda

2) Pilih 'Autentikasi anonim' sehingga pengguna tidak perlu mendaftar atau bahkan tahu apa yang Anda lakukan. Ini dijamin untuk ditautkan ke akun pengguna yang saat ini diautentikasi dan akan berfungsi di seluruh perangkat.

3) Gunakan Realtime Database API untuk menetapkan nilai 'install_date'. Pada waktu peluncuran, cukup ambil nilai ini dan gunakan ini.

Saya telah melakukan hal yang sama dan berhasil dengan baik. Saya dapat menguji ini di seluruh uninstall / re-install dan nilai dalam database realtime tetap sama. Dengan cara ini, masa uji coba Anda berfungsi di beberapa perangkat pengguna. Anda bahkan dapat membuat versi install_date sehingga aplikasi 'menyetel ulang' tanggal Uji Coba untuk setiap rilis utama baru.

PEMBARUAN : Setelah menguji lebih banyak, tampaknya Firebase anonim tampaknya mengalokasikan ID yang berbeda jika Anda memiliki perangkat yang berbeda dan tidak dijamin antara penginstalan ulang: / Satu-satunya cara yang dijamin adalah dengan menggunakan Firebase tetapi mengaitkannya ke google mereka Akun. Ini seharusnya berfungsi, tetapi akan membutuhkan langkah ekstra di mana pengguna harus masuk / mendaftar terlebih dahulu.

Sejauh ini saya telah berakhir dengan pendekatan yang sedikit kurang elegan hanya dengan memeriksa preferensi yang dicadangkan dan tanggal yang disimpan dalam preferensi saat menginstal. Ini berfungsi untuk aplikasi yang berpusat pada data di mana tidak ada gunanya bagi seseorang untuk menginstal ulang aplikasi dan memasukkan kembali semua data yang sebelumnya ditambahkan, tetapi tidak akan berfungsi untuk game sederhana.


Saya memiliki persyaratan yang sama untuk aplikasi android saya dan saya memiliki database / server web saya sendiri. Pengguna tidak memerlukan login, Jadi saya berencana untuk menyimpan ID Perangkat dengan installation_date, apakah ini akan berhasil?
pengguna636525

@ Strangetimes, menurut saya solusi Anda bekerja paling baik, dapatkah Anda memberikan info lebih lanjut? terima kasih
DayDayHappy

Utas ini stackoverflow.com/q/41733137/1396068 menyarankan bahwa setelah menginstal ulang aplikasi Anda mendapatkan ID pengguna baru, yaitu masa percobaan baru
Fabian Streitel

3

Setelah melihat semua opsi di utas ini dan utas lainnya, ini adalah temuan saya

Preferensi bersama, database Dapat dihapus di pengaturan android, hilang setelah aplikasi diinstal ulang. Dapat dicadangkan dengan mekanisme pencadangan android dan akan dipulihkan setelah dipasang ulang. Cadangan mungkin tidak selalu tersedia, meskipun harus ada di sebagian besar perangkat

Penyimpanan eksternal (menulis ke file) Tidak terpengaruh oleh penghapusan dari pengaturan atau penginstalan ulang jika kita tidak menulis ke direktori pribadi aplikasi . Tetapi: mengharuskan Anda untuk meminta izin pengguna pada waktu proses di versi android yang lebih baru, jadi ini mungkin hanya layak jika Anda tetap membutuhkan izin itu. Bisa juga di-backup.

PackageInfo.firstInstallTime Di- reset setelah penginstalan ulang tetapi stabil di seluruh pembaruan

Masuk ke beberapa akun Tidak masalah apakah itu akun Google mereka melalui Firebase atau yang ada di server Anda sendiri: uji coba terikat ke akun. Membuat akun baru akan mengatur ulang uji coba.

Masuk anonim Firebase Anda dapat memasukkan pengguna secara anonim dan menyimpan data untuk mereka di Firebase. Namun tampaknya penginstalan ulang aplikasi dan mungkin peristiwa tidak berdokumen lainnya dapat memberi pengguna ID anonim baru , menyetel ulang waktu uji coba mereka. (Google sendiri tidak memberikan banyak dokumentasi tentang ini)

ANDROID_ID Mungkin tidak tersedia dan dapat berubah dalam keadaan tertentu , misalnya reset pabrik. Pendapat tentang apakah sebaiknya menggunakan ini untuk mengidentifikasi perangkat tampaknya berbeda.

Play Advertising ID Dapat disetel ulang oleh pengguna. Mungkin dinonaktifkan oleh pengguna dengan memilih keluar dari pelacakan iklan.

InstanceID Reset saat menginstal ulang . Setel ulang jika terjadi peristiwa keamanan. Dapat disetel ulang oleh aplikasi Anda.

Metode (kombinasi) mana yang cocok untuk Anda bergantung pada aplikasi Anda dan seberapa besar upaya yang menurut Anda akan dilakukan oleh rata-rata John untuk mendapatkan periode uji coba lagi. Saya akan merekomendasikan untuk tidak menggunakan hanya Firebase anonim dan ID Iklan karena ketidakstabilannya. Pendekatan multi-faktor sepertinya akan memberikan hasil terbaik. Faktor mana yang tersedia untuk Anda bergantung pada aplikasi Anda dan izinnya.

Untuk aplikasi saya sendiri, saya menemukan preferensi bersama + firstInstallTime + cadangan preferensi menjadi metode yang paling tidak mengganggu tetapi juga cukup efektif. Anda harus memastikan Anda hanya meminta cadangan setelah memeriksa dan menyimpan waktu mulai uji coba di preferensi bersama. Nilai dalam Preferensi bersama harus diutamakan daripada firstInstallTime. Kemudian pengguna harus menginstal ulang aplikasi, menjalankannya sekali dan kemudian menghapus data aplikasi untuk mengatur ulang uji coba, yang merupakan pekerjaan yang cukup banyak. Pada perangkat tanpa transportasi cadangan, pengguna dapat mengatur ulang uji coba hanya dengan menginstal ulang.

Saya telah membuat pendekatan itu tersedia sebagai perpustakaan yang dapat diperluas .


1

Menurut definisi, semua aplikasi Android berbayar di pasar dapat dievaluasi selama 24 jam setelah pembelian.

Ada tombol 'Copot Pemasangan dan Kembalikan' yang berubah menjadi 'Copot' setelah 24 jam.

Saya berpendapat tombol ini terlalu menonjol!


17
Perhatikan bahwa periode pengembalian dana sekarang hanya 15 menit.
Kerumitan

1

Saya menemukan pertanyaan ini saat mencari masalah yang sama, saya pikir kita dapat menggunakan api tanggal gratis seperti http://www.timeapi.org/utc/now atau api tanggal lainnya untuk memeriksa kedaluwarsa aplikasi jejak. cara ini efisien jika Anda ingin mengirimkan demo dan khawatir tentang pembayaran dan memerlukan demo masa kerja tetap. :)

temukan kode di bawah ini

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

}
}

solusi kerjanya .....


yakin Anda bisa menggunakan, tapi dalam hal ini hanya aktivitas utama Anda yang bisa memeriksa kedaluwarsa.
RQube

0

Inilah cara saya melakukannya, saya membuat 2 aplikasi, satu dengan aktivitas uji coba, yang lainnya tanpa,

saya mengunggah yang tanpa aktivitas percobaan ke play store sebagai aplikasi berbayar,

dan yang memiliki aktivitas uji coba sebagai aplikasi gratis.

Aplikasi gratis pada peluncuran pertama memiliki opsi untuk uji coba dan pembelian di toko, jika pengguna memilih pembelian di toko, aplikasi dialihkan ke toko untuk dibeli oleh pengguna, tetapi jika pengguna mengeklik uji coba, aplikasi tersebut akan membawa mereka ke aktivitas uji coba

NB: Saya menggunakan opsi 3 seperti @snctln tetapi dengan modifikasi

pertama , saya tidak bergantung pada waktu perangkat, saya mendapatkan waktu saya dari file php yang melakukan registrasi percobaan ke db,

kedua , saya menggunakan nomor seri perangkat untuk mengidentifikasi setiap perangkat secara unik,

terakhir , aplikasi bergantung pada nilai waktu yang dikembalikan dari koneksi server, bukan pada waktunya sendiri sehingga sistem hanya dapat dielakkan jika nomor seri perangkat diubah, yang cukup membuat pengguna stres.

jadi inilah kode saya (untuk aktivitas Percobaan):

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);
    }


}

File php saya terlihat seperti ini (ini adalah teknologi 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;
        }

    }

kemudian pada aktivitas utama saya menggunakan preferensi bersama (installDate dibuat dalam aktivitas uji coba) untuk memantau jumlah hari yang tersisa dan jika hari-hari selesai saya memblokir UI aktivitas utama dengan pesan yang membawa mereka ke toko untuk membeli.

Satu-satunya kekurangan yang saya lihat di sini adalah jika pengguna Rogue membeli aplikasi berbayar dan memutuskan untuk berbagi dengan aplikasi seperti Zender, berbagi file atau bahkan menghosting file apk langsung di server agar orang-orang dapat mengunduh secara gratis. Tetapi saya yakin saya akan segera mengedit jawaban ini dengan solusi untuk itu atau tautan ke solusi.

Semoga ini menyelamatkan jiwa ... suatu hari nanti

Selamat Coding ...


0

@snctln opsi 3 dapat dengan mudah dilakukan menambahkan file php ke server web dengan php dan mysql diinstal seperti yang dimiliki banyak dari mereka.

Dari sisi Android pengenal (ID perangkat, akun google atau apa pun yang Anda inginkan) diteruskan sebagai argumen di URL menggunakan HttpURLConnection dan php mengembalikan tanggal pemasangan pertama jika ada di tabel atau menyisipkan baris baru dan itu mengembalikan tanggal saat ini.

Ini bekerja dengan baik untuk saya.

Jika saya punya waktu, saya akan memposting beberapa kode!

Semoga berhasil !


tunggu, tetapi apakah Anda kehilangan id unik Anda setelah menginstal ulang aplikasi? !!
Maksim Kniazev

ID ini mengidentifikasi perangkat keras, telepon itu sendiri, pengguna tidak melihatnya dan tidak dapat mengubahnya. Jika dia menginstal ulang aplikasi, layanan web php akan mendeteksi bahwa itu adalah telepon yang sama. Di sisi lain, jika pengguna mengganti telepon, dia akan menikmati periode baru.
Lluis Felisart
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.