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

时间:2009-06-15 11:57:53

标签: android trialware android-1.5-cupcake

我有一个应用程序,我希望作为付费应用程序进入市场。我想有其他版本的“试用”版本,时间限制为5天?

我该怎么做呢?

13 个答案:

答案 0 :(得分:183)

目前,大多数开发人员使用以下3种技术之一来实现这一目标。

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

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

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

在onCreate中执行这些检查始终是一种好习惯。如果到期结束,则弹出一个带有market link的AlertDialog到应用程序的完整版本。只包含一个“确定”按钮,一旦用户点击“确定”,拨打“完成()”即可结束活动。

答案 1 :(得分:17)

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

中找到

答案 2 :(得分:15)

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

如果您想使用 最简单的方法 将失败,如果 该应用是卸载/重新安装或用户手动更改设备的日期),它是这样的:

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

    ...
}

答案 3 :(得分:10)

嘿伙计这个问题以及 snctln 的答案激励我开发基于方法3的解决方案作为我的学士论文。我知道目前的状态不是为了高效使用,但我很想听听你对它的看法!你会用这样的系统吗?您是否希望将其视为云服务(配置服务器时没有问题)?关注安全问题或稳定性原因? 我完成单身汉程序后,我想继续研究该软件。所以现在我需要你的反馈!

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

有关系统的一些信息: - 该系统包含三个部分:Android库,node.js服务器和用于管理多个试用版应用程序和发布者/开发者帐户的配置程序。

  • 它仅支持基于时间的试用,它使用您的(Play商店或其他)帐户而不是电话ID。

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

  • 服务器正在使用https并对许可检查响应进行数字签名。它还为CRUD试用版应用程序和用户(发布者和开发人员)提供了API。与许可证验证类似图书馆开发人员可以在测试应用中测试他们的行为实施,并提供测试结果。因此,您可以在配置程序中明确将许可响应设置为“许可”,“未许可”或“服务器错误”。

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

  • 所有内容都在Apache 2.0许可下

答案 4 :(得分:6)

最简单且最佳的方法是实现BackupSharedPreferences。

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

只需将安装日期保存为首选项即可开始使用。

这是理论: http://developer.android.com/reference/android/app/backup/SharedPreferencesBackupHelper.html

以下是示例: Android SharedPreferences Backup Not Working

答案 5 :(得分:5)

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

自API级别9(Android 2.3.2,2.3.1,Android 2.3,GINGERBREAD)以来PackageInfo中有firstInstallTimelastUpdateTime

阅读更多内容: How to get app install time from android

答案 6 :(得分:3)

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

Here's documentation

答案 7 :(得分:2)

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

1)为您的应用添加Firebase支持

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

3)使用实时数据库API为' installed_date'设置值。在发布时,只需检索此值并使用它。

我做了同样的事情并且效果很好。我能够在卸载/重新安装时测试这个,并且实时数据库中的值保持不变。这样,您的试用期可以在多个用户设备上运行。您甚至可以对install_date进行版本设置,以便应用重置'每个新主要版本的试用日期。

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

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

答案 8 :(得分:2)

在查看此线程和其他线程中的所有选项后,这些是我的发现

共享偏好设置,数据库 可以在android设置中清除,重新安装app后丢失。 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,重置他们的试用时间。 (谷歌自己没有提供太多关于此的文件)

<强> ANDROID_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 reinstallReset in case of a security event. Can be reset by your app.

哪种(组合)方法适合您,取决于您的应用程序以及您认为普通约翰将为获得另一个试用期付出多少努力。我建议不要使用匿名Firebase和广告ID,因为它们不稳定。多因素方法似乎会产生最好的结果。您可以使用哪些因素取决于您的应用及其权限。

对于我自己的应用程序,我发现共享首选项+ firstInstallTime +首选项的备份是最不具侵入性但也足够有效的方法。您必须确保在检查并在共享首选项中存储试用开始时间后才请求备份。共享Prefs中的值必须优先于firstInstallTime。然后用户必须重新安装应用程序,运行一次,然后清除应用程序的数据以重置试用,这是相当多的工作。在没有备份传输的设备上,用户可以通过简单地重新安装来重置试用版。

我已将此方法设为an extensible library

答案 9 :(得分:1)

根据定义,购买后24小时内可以评估市场上所有付费Android应用。

有一个'卸载和退款'按钮,24小时后会变为'卸载'。

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

答案 10 :(得分:1)

我在搜索同样的问题时遇到了这个问题,我想我们可以利用http://www.timeapi.org/utc/now之类的免费日期api或其他日期api来检查跟踪应用的到期时间。如果您希望提供演示并担心付款并需要修复任期演示,这种方式是有效的。 :)

找到下面的代码

public class ValidationActivity extends BaseMainActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
}

@Override
protected void onResume() {
    processCurrentTime();
    super.onResume();
}

private void processCurrentTime() {
    if (!isDataConnectionAvailable(ValidationActivity.this)) {
        showerrorDialog("No Network coverage!");
    } else {
        String urlString = "http://api.timezonedb.com/?zone=Europe/London&key=OY8PYBIG2IM9";
        new CallAPI().execute(urlString);
    }
}

private void showerrorDialog(String data) {
    Dialog d = new Dialog(ValidationActivity.this);
    d.setTitle("LS14");
    TextView tv = new TextView(ValidationActivity.this);
    tv.setText(data);
    tv.setPadding(20, 30, 20, 50);
    d.setContentView(tv);
    d.setOnDismissListener(new OnDismissListener() {
        @Override
        public void onDismiss(DialogInterface dialog) {
            finish();
        }
    });
    d.show();
}

private void checkExpiry(int isError, long timestampinMillies) {
    long base_date = 1392878740000l;// feb_19 13:8 in GMT;
    // long expiryInMillies=1000*60*60*24*5;
    long expiryInMillies = 1000 * 60 * 10;
    if (isError == 1) {
        showerrorDialog("Server error, please try again after few seconds");
    } else {
        System.out.println("fetched time " + timestampinMillies);
        System.out.println("system time -" + (base_date + expiryInMillies));
        if (timestampinMillies > (base_date + expiryInMillies)) {
            showerrorDialog("Demo version expired please contact vendor support");
            System.out.println("expired");
        }
    }
}

private class CallAPI extends AsyncTask<String, String, String> {
    @Override
    protected void onPreExecute() {
        // TODO Auto-generated method stub
        super.onPreExecute();
    }

    @Override
    protected String doInBackground(String... params) {
        String urlString = params[0]; // URL to call
        String resultToDisplay = "";
        InputStream in = null;
        // HTTP Get
        try {
            URL url = new URL(urlString);
            HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
            in = new BufferedInputStream(urlConnection.getInputStream());
            resultToDisplay = convertStreamToString(in);
        } catch (Exception e) {
            System.out.println(e.getMessage());
            return e.getMessage();
        }
        return resultToDisplay;
    }

    protected void onPostExecute(String result) {
        int isError = 1;
        long timestamp = 0;
        if (result == null || result.length() == 0 || result.indexOf("<timestamp>") == -1 || result.indexOf("</timestamp>") == -1) {
            System.out.println("Error $$$$$$$$$");
        } else {
            String strTime = result.substring(result.indexOf("<timestamp>") + 11, result.indexOf("</timestamp>"));
            System.out.println(strTime);
            try {
                timestamp = Long.parseLong(strTime) * 1000;
                isError = 0;
            } catch (NumberFormatException ne) {
            }
        }
        checkExpiry(isError, timestamp);
    }

} // end CallAPI

public static boolean isDataConnectionAvailable(Context context) {
    ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
    NetworkInfo info = connectivityManager.getActiveNetworkInfo();
    if (info == null)
        return false;

    return connectivityManager.getActiveNetworkInfo().isConnected();
}

public String convertStreamToString(InputStream is) throws IOException {
    if (is != null) {
        Writer writer = new StringWriter();

        char[] buffer = new char[1024];
        try {
            Reader reader = new BufferedReader(new InputStreamReader(is, "UTF-8"));
            int n;
            while ((n = reader.read(buffer)) != -1) {
                writer.write(buffer, 0, n);
            }
        } finally {
            is.close();
        }
        return writer.toString();
    } else {
        return "";
    }
}

@Override
public void onClick(View v) {
    // TODO Auto-generated method stub

}
}

其工作解决方案.....

答案 11 :(得分:0)

以下是我如何去找我的, 我创建了2个具有试用活动的应用程序,另一个没有,

我将没有试用活动的那个上传到付费应用商店,

和试用活动的免费应用程序。

首次推出的免费应用程序有试用和商店购买的选项, 如果用户选择商店购买,则重定向到商店以供用户购买 但如果用户点击试用版,则会将其带到试用活动

注意:我使用了选项3,如@snctln但有修改

第一次,我不依赖于设备时间,我从试用注册到db的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技术):

/**
     * 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,并带有一条消息,将其带到商店购买

我看到的唯一缺点是,如果 Rogue用户购买付费应用并决定与Zender等应用共享,文件共享甚至直接在服务器上托管apk文件免费下载。但我相信我会很快编辑这个答案,并提供解决方案或解决方案的链接。

希望这能拯救灵魂......有一天

快乐编码......

答案 12 :(得分:0)

@snctln 选项3可以很容易地将php文件添加到安装了php和mysql的Web服务器上,因为很多人都有。

从Android端,使用HttpURLConnection将标识符(设备ID,谷歌帐户o,无论你想要的任何内容)作为参数传递给URL,如果表中存在第一次安装的日期,它将返回第一次安装的日期新行,它返回当前日期。

它适用于我。

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

祝你好运!