문제

유료 앱으로 출시하고 싶은 애플리케이션이 있습니다.예를 들어 5일이라는 시간 제한이 있는 "평가판" 버전인 다른 버전을 갖고 싶습니다.

이 작업을 어떻게 수행할 수 있나요?

도움이 되었습니까?

해결책

현재 대부분의 개발자는 다음 3 가지 기술 중 하나를 사용하여이를 달성합니다.

첫 번째 접근 방식은 쉽게 우회 할 수 있습니다. 처음으로 앱을 실행할 때 날짜/시간을 파일, 데이터베이스 또는 공유 환경 설정에 저장하고 그 후 앱을 실행할 때마다 시험 기간이 종료되었는지 확인합니다. 제거하고 재설치하면 사용자가 다른 시험 기간을 가질 수 있기 때문에 이는 쉽게 우회 할 수 있습니다.

두 번째 접근법은 우회하기가 더 어렵지만 여전히 우회 할 수 있습니다. 하드 코딩 시한 폭탄을 사용하십시오. 기본적 으로이 접근법을 사용하면 시험의 종료 날짜가 하드 코드가되며 앱을 다운로드하고 사용하는 모든 사용자는 동시에 앱을 사용할 수 없습니다. 구현하기 쉽기 때문에이 접근법을 사용했습니다. 대부분의 경우 세 번째 기술의 문제를 겪고 싶지 않았습니다. 사용자는 휴대 전화의 날짜를 수동으로 변경하여이를 피할 수 있지만 대부분의 사용자는 그러한 일을하는 데 어려움을 겪지 않습니다.

세 번째 기술은 내가하고 싶은 일을 진정으로 성취 할 수있는 유일한 방법입니다. 서버를 설정해야하며 응용 프로그램이 시작될 때마다 앱이 전화 고유 식별자 서버에. 서버에 해당 전화 ID에 대한 항목이없는 경우 새 제품을 만들고 시간을 기록합니다. 서버에 전화 ID에 대한 항목이있는 경우 시험 기간이 만료되었는지 간단한 점검을 수행합니다. 그런 다음 평가판 만료 결과를 신청서에 다시 통신합니다. 이 접근법은 우회 할 수 없어야하지만 웹 서버 등을 설정해야합니다.

OnCreate에서 이러한 점검을 수행하는 것이 항상 좋은 관행입니다. 만료가 팝업을 끝내면 ALERTDIALOG 시장 링크 앱의 정식 버전으로. "OK"버튼 만 포함하고 사용자가 "OK"를 클릭하면 "Finish ()"을 호출하여 활동을 끝내십시오.

다른 팁

나는 개발했다 안드로이드 시험 SDK 단순히 Android Studio Project에 들어갈 수 있으며 오프라인 유예 기간을 포함하여 모든 서버 측 관리를 처리합니다.

간단히 사용하려면

메인 모듈에 라이브러리를 추가하십시오 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는 귀하의 트리얼 개발자 대시 보드.

이것은 오래된 질문이지만 어쨌든, 아마도 이것은 누군가를 도울 것입니다.

당신이 함께 가고 싶다면 가장 단순한 접근법(어느 경우 실패합니다 앱이 제거/재설치 또는 사용자 변경 장치의 날짜를 수동으로 변경합니다.) 이것이 다음과 같습니다.

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

    ...
}

이 질문과 답변 snctln 방법 3을 학사 학위 논문으로 기반으로 솔루션을 연구하도록 영감을주었습니다. 나는 현재 상태가 생산적인 사용을위한 것이 아니라는 것을 알고 있지만 당신이 그것에 대해 어떻게 생각하는지 듣고 싶습니다! 그런 시스템을 사용 하시겠습니까? 클라우드 서비스로보고 싶습니까 (서버 구성에 문제가 없음)? 보안 문제 또는 안정성 이유에 대해 걱정하십니까? 학사 절차를 마치 자마자 소프트웨어 작업을 계속하고 싶습니다. 그래서 이제 당신의 피드백이 필요할 때!

Sourcecode는 Github에서 호스팅됩니다 https://github.com/machristmann/mobile-trial

시스템에 대한 일부 정보 : - 시스템에는 Android 라이브러리, Node.js 서버 및 여러 평가판 앱 및 게시자/개발자 계정을 관리하기위한 구성자인 세 부분이 있습니다.

  • 시간 기반 시험 만 지원하며 전화 ID가 아닌 (플레이 스토어 또는 기타) 계정을 사용합니다.

  • Android 라이브러리의 경우 Google Play 라이센스 확인 라이브러리를 기반으로합니다. Node.js 서버에 연결하도록 수정했으며 추가로 라이브러리는 사용자가 시스템 날짜를 변경했는지 인식하려고합니다. 또한 암호화 된 공유 선호도에서 검색된 시험 라이센스를 캐시합니다. 구성 자로 캐시의 유효한 시간을 구성 할 수 있습니다. 사용자가 "데이터를 클리어"하는 경우 라이브러리가 서버 측 점검을 강제합니다.

  • 서버는 HTTPS를 사용하고 라이센스 확인 응답에 디지털 서명을 사용하고 있습니다. 또한 CRUD 시험 앱 및 사용자 (게시자 및 개발자)를위한 API가 있습니다. Verfication Library 개발자를 라이센스하는 것과 비슷한 경우 시험 결과를 통해 시험 앱에서 행동 구현을 테스트 할 수 있습니다. 따라서 구성기에서 라이센스 응답을 "라이센스", "라이센스가없는"또는 "서버 오류"에 명시 적으로 설정할 수 있습니다.

  • Ass-Kicking 새로운 기능으로 앱을 업데이트하면 모든 사람이 다시 시도 할 수 있기를 원할 수 있습니다. Configurator에서는이를 트리거 해야하는 버전 코드를 설정하여 만료 된 라이센스가있는 사용자의 평가판 라이센스를 갱신 할 수 있습니다. 예를 들어, 사용자는 versioncode 3에서 앱을 실행하고 있습니다. 버전 코드 4의 기능을 시도하기를 원합니다. 앱을 업데이트하거나 다시 설치하면 서버가 마지막으로 시도한 버전을 알고 있기 때문에 전체 시험 기간을 다시 사용할 수 있습니다. 시각.

  • 모든 것은 Apache 2.0 라이센스에 따라 있습니다

가장 쉬운 것과 베스트 이를 수행하는 방법은 Backupshartepreferences 구현입니다.

앱이 제거되고 다시 설치되어 있어도 환경 설정이 보존됩니다.

설치 날짜를 기본 설정으로 저장하면 가면 좋습니다.

이론은 다음과 같습니다.http://developer.android.com/reference/android/app/backup/sharedpreferencesbackuphelper.html

예는 다음과 같습니다.Android SharedPreferences 백업이 작동하지 않습니다

접근 4 : 응용 프로그램 설치 시간을 사용하십시오.

API 레벨 9 (Android 2.3.2, 2.3.1, Android 2.3, Gingerbread) 이후 FirstInstAllTime 그리고 마지막으로 안에 PackageInfo.

더 읽기 :Android에서 앱 설치 시간을 얻는 방법

이제 최신 버전의 Android 무료 평가판 구독이 추가되었습니다. 무료 평가판 기간 동안 앱 내에서 구독을 구매한 후에만 앱의 모든 기능을 잠금 해제할 수 있습니다.이를 통해 사용자는 평가판 기간 동안 앱을 사용할 수 있습니다. 평가판 기간 후에도 앱이 제거된 경우 구독 금액이 귀하에게 이체됩니다.나는 시도하지 않았지만 단지 아이디어를 공유했습니다.

여기에 문서가 있습니다

제 생각에는이를 수행하는 가장 좋은 방법은 단순히 Firebase Realtime 데이터베이스를 사용하는 것입니다.

1) 앱에 Firebase 지원을 추가하십시오

2) '익명 인증'을 선택하여 사용자가 가입하거나 무엇을하고 있는지 알 필요가 없습니다. 이는 현재 인증 된 사용자 계정에 연결되도록 보장되므로 장치에서 작동합니다.

3) 실시간 데이터베이스 API를 사용하여 'installed_date'값을 설정하십시오. 시작 시간 에이 값을 검색하고 이것을 사용하십시오.

나는 똑같이했고 그것은 훌륭하게 작동합니다. 제거 / 재 설계에서 이것을 테스트 할 수 있었고 실시간 데이터베이스의 값은 동일하게 유지됩니다. 이렇게하면 시험 기간이 여러 사용자 장치에서 작동합니다. 앱이 각각의 새로운 주요 릴리스에 대한 평가판 날짜를 '재설정'하도록 install_date를 버전 할 수도 있습니다.

업데이트: 조금 더 테스트 한 후에는 익명의 Firebase가 다른 장치를 가지고 있고 다시 설치 사이에 보장되지 않는 경우 다른 ID를 할당하는 것처럼 보입니다. . 이것은 작동하지만 사용자가 먼저 로그인 / 가입 해야하는 추가 단계가 필요합니다.

나는 지금까지 백업 환경 설정에 대해 간단히 확인하는 약간 덜 우아한 접근 방식과 설치시 선호도에 저장된 날짜로 끝났습니다. 이는 사람이 앱을 다시 설치하고 이전에 추가 된 모든 데이터를 다시 입력하는 것이 무의미한 데이터 중심 앱에 대해 작동하지만 간단한 게임에서는 작동하지 않습니다.

이 스레드와 다른 스레드의 모든 옵션을 살펴본 후 찾은 결과는 다음과 같습니다.

공유 환경설정, 데이터베이스안드로이드 설정에서 지울 수 있으며, 앱 재설치 후 손실됩니다. 안드로이드의 백업 메커니즘으로 백업 가능 재설치 후 복원됩니다.백업은 항상 사용 가능한 것은 아니지만 대부분의 장치에서는 가능합니다.

외부 저장소(파일에 쓰기)설정을 지우거나 다시 설치해도 영향을 받지 않습니다. 애플리케이션의 개인 디렉터리에 쓰지 마세요..하지만: 런타임 시 사용자에게 허가를 요청해야 합니다. 최신 Android 버전에서는 어쨌든 해당 권한이 필요한 경우에만 가능합니다.백업도 가능합니다.

PackageInfo.firstInstallTime재설치 후 재설정되지만 업데이트 간에는 안정적입니다.

일부 계정에 로그인Firebase를 통한 Google 계정인지 아니면 자체 서버에 있는 Google 계정인지는 중요하지 않습니다.평가판은 계정에 바인딩되어 있습니다.새 계정을 만들면 평가판이 재설정됩니다.

Firebase 익명 로그인사용자를 익명으로 로그인하고 해당 데이터를 Firebase에 저장할 수 있습니다.하지만 분명히 앱을 다시 설치하거나 문서화되지 않은 다른 이벤트로 인해 사용자에게 새로운 익명 ID가 제공될 수 있습니다., 평가판 시간을 재설정합니다.(구글 자체는 이에 대한 많은 문서를 제공하지 않습니다)

ANDROID_ID 제공되지 않을 수 있으며 특정 상황에 따라 변경될 수 있습니다., 예를 들어 공장 초기화.이를 장치 식별에 활용하는 것이 좋은지에 대해서는 의견이 분분한 것 같습니다.

Play 광고 ID사용자가 재설정할 수 있습니다. 사용자가 광고 추적을 선택 해제하여 비활성화할 수 있습니다.

인스턴스ID 재설치 시 재설정. 보안 이벤트 발생 시 재설정됩니다.앱에서 재설정할 수 있습니다.

귀하에게 적합한 방법(조합)은 귀하의 앱과 평균 John이 추가 시험 기간을 확보하기 위해 얼마나 많은 노력을 기울일 것이라고 생각하는지에 따라 다릅니다.나는 사용을 피하는 것이 좋습니다 오직 불안정성으로 인해 익명의 Firebase 및 광고 ID.다중 요소 접근 방식이 최상의 결과를 가져올 것으로 보입니다.사용할 수 있는 요소는 앱과 해당 권한에 따라 다릅니다.

내 앱의 경우 공유 기본 설정 + firstInstallTime + 기본 설정 백업이 방해가 가장 적으면서도 충분히 효과적인 방법이라는 것을 알았습니다.반드시 공유 환경설정에서 체험판 시작 시간을 확인하고 저장하신 후 백업을 요청하셔야 합니다.공유 Prefs의 값은 firstInstallTime보다 우선해야 합니다.그런 다음 사용자는 앱을 다시 설치하고 한 번 실행한 다음 앱의 데이터를 지워 평가판을 재설정해야 하는데 이는 상당히 많은 작업입니다.하지만 백업 전송 기능이 없는 장치에서는 사용자가 간단히 재설치하여 평가판을 재설정할 수 있습니다.

나는 그 접근 방식을 다음과 같이 사용할 수 있도록 만들었습니다. 확장 가능한 라이브러리.

정의에 따라 모두 시장에 유료 된 Android 앱은 구매 후 24 시간 동안 평가할 수 있습니다.

24 시간 후에 '제거'버튼이 있습니다.

나는이 버튼이 너무 두드러 졌다고 주장 할 것이다!

같은 문제를 검색하는 동안이 질문을 접하게됩니다. 무료 날짜 API를 활용할 수 있다고 생각합니다. http://www.timeapi.org/utc/now 또는 트레일 앱 만료를 확인하는 다른 날짜 API. 데모를 전달하고 결제에 대해 걱정하고 수정 임기 데모가 필요하다면이 방법은 효율적입니다. :)

아래 코드를 찾으십시오

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

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

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

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

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

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

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

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

} // end CallAPI

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

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

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

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

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

}
}

작동 솔루션 .....

여기에 내가 어떻게 갔는지, 나는 시험 활동이없는 2 개의 앱을 만들었습니다.

시험 활동이없는 사람에게 상점을 플레이하여 유료 앱으로 업로드했습니다.

무료 앱으로 시험 활동을하는 사람.

First Launch의 무료 앱에는 시험 및 저장 구매 옵션이 있습니다. 사용자가 구매를 선택하면 구매를 선택하여 사용자가 구매할 수 있도록 스토어로 리디렉션하지만 사용자가 시험을 클릭하면 시험 활동으로 이동합니다.

NB : @snctln과 같은 옵션 3을 사용했지만 수정이 있습니다.

첫 번째, 나는 장치 시간에 의존하지 않았고, 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 파일은 다음과 같습니다 (휴게소 기술) :

/**
     * 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;
        }

    }

그런 다음 주요 활동에서 공유 환경 설정 (시험 활동에서 생성 된 설치)을 사용하여 남은 일 수를 모니터링하고 일이 끝나면 매장으로 가져가는 메시지가있는 메인 활동 UI를 차단합니다.

여기서 내가 보는 유일한 아래쪽은 도적 사용자 유료 앱을 구매하고 Zender, 파일 공유와 같은 앱과 공유하거나 사람들이 무료로 다운로드 할 수 있도록 서버에서 APK 파일을 직접 호스팅하기로 결정합니다. 그러나이 답변을 솔루션에 대한 솔루션이나 솔루션 링크로 곧 편집 할 것이라고 확신합니다.

이것이 영혼을 구하기를 바랍니다 ... 언젠가

행복한 코딩 ...

@snctln PHP가있는 웹 서버에 PHP 파일을 추가하고 MySQL이 많이 설치된 옵션 3을 쉽게 수행 할 수 있습니다.

Android 측에서 식별자 (장치 ID, Google 계정 O가 원하는대로)에서 HTTPURLConnection을 사용하여 URL의 인수로 전달되며 PHP는 테이블에 존재하는 경우 첫 번째 설치 날짜를 반환하거나 새 행을 삽입하고 새 행을 삽입합니다. 현재 날짜를 반환합니다.

그것은 나를 위해 잘 작동합니다.

시간이 있다면 코드를 게시하겠습니다!

행운을 빕니다 !

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top