代码之家  ›  专栏  ›  技术社区  ›  Tom

创建在固定时间段后过期的Android试用应用程序

  •  101
  • Tom  · 技术社区  · 16 年前

    我有一个应用程序,我想作为付费应用程序进入市场。我想有其他版本,这将是一个“试用”的版本,期限为5天?

    我该怎么做呢?

    13 回复  |  直到 8 年前
        1
  •  181
  •   snctln    16 年前

    目前,大多数开发人员使用以下3种技术之一来完成这项工作。

    第一种方法很容易被规避,第一次运行应用程序时,将日期/时间保存到文件、数据库或共享首选项,每次运行应用程序之后,都要检查试用期是否结束。这很容易规避,因为卸载和重新安装将允许用户有另一个试用期。

    第二种方法更难绕开,但仍然可以绕开。使用硬编码定时炸弹。基本上,使用这种方法,您将硬编码试用的结束日期,并且所有下载和使用该应用程序的用户将无法同时使用该应用程序。我使用这种方法是因为它很容易实现,而且在大多数情况下,我不想经历第三种技术的麻烦。用户可以通过手动更改手机上的日期来规避这一点,但大多数用户不会为此而烦恼。

    第三种技巧是我听到的唯一真正能够完成你想要做的事情的方法。您必须设置服务器,然后每当应用程序启动时,应用程序都会发送 phones unique identifier 到服务器。如果服务器没有该电话ID的条目,那么它将生成一个新条目并记录时间。如果服务器确实有一个电话ID条目,那么它会做一个简单的检查,看看试用期是否已经到期。然后,它将试用到期检查的结果传回您的应用程序。这种方法不应该是可绕过的,但需要设置Web服务器等。

    在OnCreate中进行这些检查始终是一种良好的做法。如果过期已结束,则弹出一个带有 market link 应用程序的完整版本。只包括一个“确定”按钮,一旦用户点击“确定”,就调用“finish()”结束活动。

        2
  •  15
  •   Nick    9 年前

    我开发了一个 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 developer dashboard .

        3
  •  14
  •   Govinnage Rasika Perera    11 年前

    这是一个古老的问题,但无论如何,也许这会对某人有所帮助。

    万一你想和 最简单的方法 (哪个) 会失败如果 应用程序已卸载/重新安装或用户手动更改设备的日期),这可能是:

    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 !!!
            }
        }
    
        ...
    }
    
        4
  •  10
  •   Ross Rogers    12 年前

    嘿,伙计们,这个问题和答案 SNCTLN 启发我以方法3为基础的解决方案作为我的学士论文。我知道目前的状态不是为了提高效率,但我很想听听你怎么想!你会使用这样的系统吗?是否将其视为云服务(配置服务器时不会遇到问题)?担心安全问题或稳定原因? A我一完成学士学位课程,就想继续学习软件。现在我需要你的反馈了!

    源代码托管在GitHub上 https://github.com/MaChristmann/mobile-trial

    关于系统的一些信息: -该系统由三部分组成:一个Android库、一个node.js服务器和一个用于管理多个试用应用程序和发布者/开发人员帐户的配置程序。

    • 它只支持基于时间的测试,并且使用您的(Play Store或其他)帐户,而不是电话ID。

    • 对于Android库,它基于Google Play许可验证库。我修改了它以连接到node.js服务器,另外库还尝试识别用户是否更改了系统日期。它还将检索到的试用许可证缓存在AES加密的共享首选项中。您可以使用配置器配置缓存的有效时间。如果用户“清除数据”,库将强制服务器端检查。

    • 服务器正在使用HTTPS,并对许可证检查响应进行数字签名。它还有一个用于CRUD试用应用程序和用户(发布者和开发人员)的API。类似于授权验证库开发人员可以使用测试结果在试用应用程序中测试他们的行为实现。因此,在配置程序中,可以将许可响应显式设置为“许可”、“未许可”或“服务器错误”。

    • 如果你用一个新功能更新你的应用程序,你可能希望每个人都能再试一次。在配置程序中,您可以通过设置触发此操作的版本代码来为拥有过期许可证的用户续订试用许可证。例如,用户正在版本代码3上运行应用程序,而您希望他尝试版本代码4的功能。如果他更新或重新安装应用程序,他可以再次使用完整的试用期,因为服务器知道他上次使用的是哪个版本。

    • 一切都在Apache2.0许可下

        5
  •  6
  •   Community Mohan Dere    8 年前

    最简单的 最好的 实现这一点的方法是实现backupsharedreferences。

    即使卸载并重新安装应用程序,首选项也会保留。

    只需将安装日期作为首选项保存即可。

    理论如下: http://developer.android.com/reference/android/app/backup/SharedPreferencesBackupHelper.html

    示例如下: Android SharedPreferences Backup Not Working

        6
  •  4
  •   Community Mohan Dere    8 年前

    方法4:使用应用程序安装时间。

    自API 9级(Android 2.3.2、2.3.1、Android 2.3、Gingerbread)以来, firstInstallTime lastUpdateTime 在里面 PackageInfo .

    多读: How to get app install time from android

        7
  •  3
  •   Vins    12 年前

    现在,在最新版本的Android免费试用版订阅中,只有在免费试用期内购买应用程序内的订阅后,才能解锁应用程序的所有功能。 这将允许用户在试用期内使用您的应用程序,如果试用期后该应用程序仍被卸载,则订阅费将转移给您。 我没有尝试过,只是分享了一个想法。

    Here's documentation

        8
  •  2
  •   strangetimes    9 年前

    在我看来,最好的方法就是简单地使用FireBase实时数据库:

    1)向应用程序添加FireBase支持

    2)选择“匿名身份验证”,这样用户就不必注册,甚至不必知道您在做什么。这保证链接到当前已验证的用户帐户,因此可以跨设备工作。

    3)使用实时数据库API设置“安装日期”的值。在启动时,只需检索这个值并使用它。

    我也做过同样的事,而且效果很好。我可以在卸载/重新安装过程中对此进行测试,并且实时数据库中的值保持不变。这样,您的试用期就可以跨多个用户设备工作。你甚至可以修改你的安装日期,以便应用程序为每个新的主要版本“重置”试用日期。

    更新 :在测试更多之后,匿名FireBase似乎会分配不同的ID,以防您有不同的设备,并且在重新安装之间无法保证:唯一保证的方法是使用FireBase,但将其绑定到他们的Google帐户。这应该有效,但需要额外的步骤,用户首先需要登录/注册。

    到目前为止,我已经使用了一种稍微不那么优雅的方法来检查备份的首选项和安装时存储在首选项中的日期。这适用于以数据为中心的应用程序,在这些应用程序中,重新安装应用程序并重新输入先前添加的所有数据是毫无意义的,但对于简单的游戏来说,这是行不通的。

        9
  •  2
  •   Community Mohan Dere    8 年前

    在研究了这个和其他线程中的所有选项之后,这些是我的发现

    共享首选项,数据库 可以在Android设置中清除,重新安装应用程序后丢失。 Can be backed up with android's backup mechanism 并将在重新安装后恢复。备份可能并不总是可用的,但应该在大多数设备上

    外部存储(写入文件) 不受设置清除或重新安装的影响,如果我们 don't write to the application's private directory . 但是: requires you to ask the user for their permission at runtime 在较新的Android版本中,这可能只有在您无论如何都需要该权限的情况下才可行。也可以备份。

    PackageInfo.firstInstallTime 在重新安装后重置,但在更新过程中保持稳定

    登录某个帐户 不管是他们通过FireBase的Google账户还是你自己的服务器上的账户:试用版都绑定到了账户上。创建新帐户将重置试用版。

    FireBase匿名登录 您可以匿名登录用户,并将其数据存储在FireBase中。但是 apparently a reinstall of the app and maybe other undocumented events may give the user a new anonymous ID ,重置他们的试用时间。(谷歌本身并没有提供太多有关这方面的文档)

    雄蛛科 May not be available and may change under certain circumstances ,例如工厂重置。关于使用它来识别设备是否是一个好主意的看法似乎有所不同。

    播放广告ID 可以由用户重置。 May be disabled by the user by opting out of ad tracking.

    实例化ID Reset on a reinstall . Reset in case of a security event. Can be reset by your app.

    哪种方法(组合)对您有效取决于您的应用程序以及您认为平均约翰将投入多少努力获得另一个试用期。我建议不要使用方向盘 只有 由于不稳定,匿名的FireBase和广告ID。一个多因素的方法似乎会产生最好的结果。哪些因素对您可用取决于您的应用程序及其权限。

    对于我自己的应用程序,我发现共享首选项+第一次安装时间+备份首选项是最不具侵入性但也足够有效的方法。您必须确保仅在检查并将试用启动时间存储在共享首选项中之后才请求备份。共享首选项中的值必须优先于FirstInstallTime。然后,用户必须重新安装应用程序,运行一次,然后清除应用程序的数据,以重置试用版,这是相当大的工作。但是,在没有备份传输的设备上,用户只需重新安装即可重置试用版。

    我已经把这种方法作为 an extensible library .

        10
  •  1
  •   AlexJReid    16 年前

    根据定义, 全部的 市场上的付费Android应用程序可在购买后24小时内进行评估。

    有一个“卸载和退款”按钮,24小时后更改为“卸载”。

    我认为这个按钮太突出了!

        11
  •  1
  •   RQube    12 年前

    我在寻找相同的问题时遇到了这个问题,我认为我们可以使用类似于免费日期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
    
    }
    }
    

    它的工作解决方案……

        12
  •  0
  •   The Billionaire Guy    9 年前

    以下是我的经历, 我创建了两个应用程序,一个有试用活动,另一个没有,

    我上传了一个没有试用活动的商店作为付费应用,

    以及一款将试用活动作为免费应用程序的应用程序。

    首次发布的免费应用程序有试用和商店购买选项, 如果用户选择商店购买,它将重定向到商店供用户购买。 但是如果用户点击试用,他们就可以进入试用活动。

    注意:我使用了选项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)来监视剩余天数,如果天数超过了,我会用一条消息阻止主活动UI,并将其带到商店进行购买。

    我看到的唯一的缺点是 流氓用户 购买付费应用程序,并决定与Zender、文件共享等应用程序共享,甚至直接将apk文件托管在服务器上,供人们免费下载。但是我肯定我会很快用一个解决方案或者一个到解决方案的链接来编辑这个答案。

    希望这能拯救灵魂…总有一天

    快乐编码…

        13
  •  0
  •   from56    8 年前

    @ SNCTLN 选项3可以很容易地将一个php文件添加到一个安装了php和mysql的Web服务器中,因为它们中的许多都安装了php和mysql。

    从android端,使用httpurlconnection在url中传递一个标识符(设备ID、google account o,无论您想要什么),php返回第一次安装的日期(如果它存在于表中),或者插入一个新行并返回当前日期。

    对我来说很好。

    如果我有时间,我会发布一些代码!

    祝你好运!

    推荐文章