การสร้างแอปพลิเคชันทดลองใช้ Android ที่หมดอายุหลังจากช่วงเวลาที่กำหนด


103

ฉันมีแอปพลิเคชันที่ต้องการเข้าสู่ตลาดเป็นแอปที่ต้องซื้อ ฉันต้องการที่จะมีเวอร์ชันอื่นซึ่งจะเป็นเวอร์ชัน "ทดลองใช้" โดยกำหนดเวลาไว้ว่า 5 วัน?

ฉันจะทำสิ่งนี้ได้อย่างไร?


Google ควรสนับสนุนสิ่งนี้ในบริการของ Play!
powder366

@ powder366 จริงๆแล้ว Google สนับสนุนสิ่งนี้โปรดดูที่developer.android.com/google/play/billing/…
Yazazzello

คำตอบ:


186

ปัจจุบันนักพัฒนาส่วนใหญ่ทำได้โดยใช้หนึ่งใน 3 เทคนิคต่อไปนี้

แนวทางแรกสามารถหลีกเลี่ยงได้ง่ายในครั้งแรกที่คุณเรียกใช้แอปให้บันทึกวันที่ / เวลาลงในไฟล์ฐานข้อมูลหรือค่ากำหนดที่ใช้ร่วมกันและทุกครั้งที่คุณเรียกใช้แอปหลังจากนั้นให้ตรวจสอบว่าระยะทดลองใช้สิ้นสุดลงหรือไม่ นี่เป็นเรื่องง่ายที่จะหลีกเลี่ยงเนื่องจากการถอนการติดตั้งและการติดตั้งใหม่จะทำให้ผู้ใช้มีช่วงทดลองใช้งานอีกครั้ง

แนวทางที่สองนั้นยากกว่าที่จะหลีกเลี่ยง แต่ก็ยังหลีกเลี่ยงไม่ได้ ใช้ระเบิดเวลาที่เข้ารหัสยาก โดยพื้นฐานแล้วด้วยวิธีนี้คุณจะกำหนดวันที่สิ้นสุดสำหรับการทดลองใช้ฮาร์ดโค้ดและผู้ใช้ทั้งหมดที่ดาวน์โหลดและใช้แอพจะหยุดไม่สามารถใช้แอพได้ในเวลาเดียวกัน ฉันใช้แนวทางนี้เพราะง่ายต่อการนำไปใช้และส่วนใหญ่ฉันไม่รู้สึกว่าจะต้องเจอกับปัญหาของเทคนิคที่สาม ผู้ใช้สามารถหลีกเลี่ยงปัญหานี้ได้โดยการเปลี่ยนวันที่ในโทรศัพท์ด้วยตนเอง แต่ผู้ใช้ส่วนใหญ่จะไม่ประสบปัญหาในการทำสิ่งนั้น

เทคนิคที่สามเป็นวิธีเดียวที่ฉันเคยได้ยินมาว่าจะสามารถบรรลุสิ่งที่คุณต้องการทำได้อย่างแท้จริง คุณจะต้องตั้งค่าเซิร์ฟเวอร์จากนั้นเมื่อใดก็ตามที่แอปพลิเคชันของคุณเริ่มต้นแอปของคุณจะส่งตัวระบุเฉพาะของโทรศัพท์ไปยังเซิร์ฟเวอร์ หากเซิร์ฟเวอร์ไม่มีรายการสำหรับรหัสโทรศัพท์นั้นเซิร์ฟเวอร์จะสร้างรายการใหม่และบันทึกเวลา หากเซิร์ฟเวอร์มีรายการสำหรับรหัสโทรศัพท์ระบบจะทำการตรวจสอบอย่างง่ายเพื่อดูว่าช่วงทดลองใช้หมดอายุหรือไม่ จากนั้นจะแจ้งผลการตรวจสอบการหมดอายุการทดลองใช้กลับไปยังใบสมัครของคุณ แนวทางนี้ไม่ควรหลีกเลี่ยงได้ แต่จำเป็นต้องมีการตั้งค่าเว็บเซิร์ฟเวอร์และอื่น ๆ

การตรวจสอบเหล่านี้ใน onCreate เป็นวิธีปฏิบัติที่ดีเสมอ หากการหมดอายุสิ้นสุดลงป๊อปอัป AlertDialog พร้อมลิงก์ตลาดไปยังแอปเวอร์ชันเต็ม รวมเฉพาะปุ่ม "ตกลง" และเมื่อผู้ใช้คลิกที่ "ตกลง" ให้โทรไปที่ "เสร็จสิ้น ()" เพื่อสิ้นสุดกิจกรรม


2
คำตอบที่ยอดเยี่ยม อย่างที่คุณบอกว่าฉันรู้สึกว่าตัวเลือกที่สองน่าจะดีที่สุด เป็นเรื่องน่าเสียดายที่ Google เองไม่ได้เสนอระบบการออกใบอนุญาตบางประเภทเนื่องจากอาจกระตุ้นให้นักพัฒนาแบรนด์ทั้งรายเล็กและรายใหญ่ผลิตแอปพลิเคชัน Android ได้มากขึ้น
ทอม

8
นอกจากนี้ฉันจะไม่ตรวจสอบระหว่างการเริ่มต้น เป้าหมายของคุณคือการขายแอปไม่ใช่ลงโทษผู้ใช้ (นั่นเป็นเพียงโบนัสเท่านั้น) หากคุณตั้งค่าให้ตรวจสอบทุก ๆ 2 นาทีในขณะที่กำลังทำงานอยู่คุณจะปล่อยให้ผู้ใช้เริ่มทำบางสิ่งบางอย่างแล้วรู้ว่าพวกเขาควรจ่าย หากคุณทำให้การจ่ายเงินเป็นเรื่องง่ายและกลับมาทำงานได้ (ฉันไม่แน่ใจว่าคุณทำได้ใน Android หรือไม่) ฉันคิดว่าคุณจะขายได้มากขึ้นจากนั้นตรวจสอบระหว่าง onCreate
Whaledawg

4
@Whaledawg: คุณไม่จำเป็นต้องเรียกใช้เซิร์ฟเวอร์ของคุณเองเนื่องจากเซิร์ฟเวอร์กำลังจัดเก็บรหัสโทรศัพท์และเวลาในการเรียกใช้ครั้งแรกในฐานข้อมูลที่จะเปรียบเทียบในภายหลังนอกจากนี้เมื่อคุณทำการตรวจสอบเป็นความต้องการของผู้พัฒนาเท่านั้นฉันใช้ฮาร์ด ระเบิดเวลาในเกมที่มีผลลัพธ์ที่ยอดเยี่ยม แอปทั้งหมดจะโหลด แต่ผู้ใช้สามารถโต้ตอบกับกล่องโต้ตอบที่เห็นได้เท่านั้นมีปุ่มบนกล่องโต้ตอบนั้นที่พาผู้ใช้ไปยังหน้าการซื้อเกมโดยตรง ผู้ใช้ดูเหมือนจะไม่สนใจ AFAIK เนื่องจากเกมดังกล่าวอยู่ใน 10 อันดับแรกของแอปแบบชำระเงินนับตั้งแต่เปิดตัว Android Market
snctln

11
สำหรับใครก็ตามที่ลังเลที่จะใช้ตัวเลือก 3 เนื่องจากการตั้งค่าเซิร์ฟเวอร์เพิ่มเติมโปรดดูที่ Parse.com ซึ่งเป็นการซิงค์
Joel Skrepnek

3
วันที่สิ้นสุดการทดลองใช้ฮาร์ดโค้ดหมายถึงอะไร นั่นหมายความว่าคุณจะปล่อยเวอร์ชันใหม่ของแอปทดลองใช้งานตลอดไปโดยมีวันที่เข้ารหัสที่แตกต่างกันในอนาคตใช่หรือไม่
Jasper

21

ฉันได้พัฒนา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


ต้องเปิดใช้งานข้อมูล?
Sivaram Boina

trialy ไม่น่าเชื่อถือ
Amir Dora

1
@AmirDe สวัสดีอาเมียร์คุณช่วยบอกฉันได้ไหมว่าอะไรที่ไม่ได้ผลสำหรับคุณ? ฉันยินดีที่จะช่วยเหลือ support@trialy.io Trialy ทำงานได้ดีสำหรับผู้ใช้มากกว่า 1,000 คน
Nick

@ นิคไม่รู้เหตุผลที่อุปกรณ์ของฉันใช้ android lollipop เมื่อฉันตั้งค่าวันจากแดชบอร์ดจากนั้นวันจะแสดงค่าเป็นลบหลังจากนั้นไม่กี่นาทีมันบอกว่าการทดลองใช้หมดอายุแม้ว่าฉันจะยังอยู่ในแดชบอร์ดหลายวันก็ตาม ฉันยังทดสอบกับอุปกรณ์ตังเมดูเหมือนว่าจะทำงานได้ดีบนนอกัต อาจมีปัญหาความเข้ากันได้ของ Android เวอร์ชันเก่ากว่า
Amir Dora

1
ฉันใช้บริการนี้มาตั้งแต่ปี 2559 ใช้งานได้ดีทุกครั้ง ฉันใช้สิ่งนี้ในโครงการอย่างเป็นทางการของฉันด้วยเช่นกัน คำตอบนี้ควรได้รับการยอมรับ
Tariq Mahmood

17

นี่เป็นคำถามเก่า แต่อย่างไรก็ตามบางทีนี่อาจช่วยใครบางคนได้

ในกรณีที่คุณต้องการใช้วิธีที่ง่ายที่สุด (ซึ่งจะล้มเหลวหากถอนการติดตั้ง / ติดตั้งแอปใหม่หรือผู้ใช้เปลี่ยนวันที่ของอุปกรณ์ด้วยตนเอง) นี่คือวิธีที่สามารถทำได้:

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

    ...
}

จริงๆแล้วถ้าคุณใช้ SharedPreferences และ Android 2.2 Froyo ขึ้นไปตราบใดที่คุณใช้ Data Backup API สำหรับการซิงโครไนซ์ข้อมูล Google ผู้ใช้ไม่ควรหลีกเลี่ยงสิ่งนี้ข้ามอุปกรณ์หรือโดยการถอนการติดตั้งเพียงไปที่การตั้งค่า> แอปพลิเคชันและ การล้างข้อมูล นอกจากนี้ยังมีวิธีการในวันที่เป็นไม่ได้getTime getTimeInMillis
ทอม

ไม่เห็นด้วยสิ่งนี้จะล้มเหลวหากผู้ใช้เปลี่ยนวันที่ของอุปกรณ์ด้วยตนเองและจะเกิดอะไรขึ้นหากผู้ใช้ล้างข้อมูลด้วยตนเอง
Mohammed Azharuddin Shaikh

@Caner นี่เป็นการดีที่จะ chek ด้วย sharedprefrence แต่ผู้ใช้ wiil clear memory จากการตั้งค่า -> aplication manager แล้วนำมาใช้ใหม่แล้วจะทำอย่างไร?
CoronaPintu

@CoronaPintu ดังนั้นวิธีนี้จะลองใช้กับ firebase ด้วยซึ่งจะเป็นการผสมผสานที่ลงตัวและช่วงทดลองใช้งานจะหมดไปแม้แอปจะถูกถอนการติดตั้ง
Noor Hossain

รวมและเพิ่มแนวทางด้วยคำตอบ "แปลกประหลาด" สิ่งนี้จะทำให้แนวทางสมบูรณ์แบบ
Noor Hossain

10

คำถามนี้และคำตอบของsnctln เป็นแรงบันดาลใจให้ฉันคิดหาวิธีแก้ปัญหาตามวิธีที่ 3 เป็นวิทยานิพนธ์ปริญญาตรี ฉันรู้ว่าสถานะปัจจุบันไม่ใช่เพื่อการใช้งานที่มีประสิทธิผล แต่ฉันชอบที่จะได้ยินว่าคุณคิดอย่างไรเกี่ยวกับเรื่องนี้! คุณจะใช้ระบบดังกล่าวหรือไม่? คุณต้องการที่จะเห็นว่าเป็นบริการคลาวด์ (ไม่มีปัญหาในการกำหนดค่าเซิร์ฟเวอร์) หรือไม่? กังวลเกี่ยวกับปัญหาด้านความปลอดภัยหรือเหตุผลด้านความเสถียรหรือไม่?

ทันทีที่ฉันเรียนจบปริญญาตรีฉันต้องการทำงานกับซอฟต์แวร์ต่อไป ตอนนี้ถึงเวลาที่ฉันต้องการความคิดเห็นจากคุณ!

Sourcecode โฮสต์บน GitHub https://github.com/MaChristmann/mobile-trial

ข้อมูลบางอย่างเกี่ยวกับระบบ: - ระบบมีสามส่วนไลบรารี Android เซิร์ฟเวอร์ node.js และตัวกำหนดค่าสำหรับจัดการแอพทดลองใช้งานหลายแอพและบัญชีผู้เผยแพร่ / นักพัฒนา

  • รองรับเฉพาะการทดลองตามเวลาและใช้บัญชี (play store หรืออื่น ๆ ) ของคุณแทนรหัสโทรศัพท์

  • สำหรับไลบรารี Android จะขึ้นอยู่กับไลบรารีการตรวจสอบสิทธิ์การใช้งาน Google Play ฉันแก้ไขเพื่อเชื่อมต่อกับเซิร์ฟเวอร์ node.js และนอกจากนี้ไลบรารีจะพยายามรับรู้ว่าผู้ใช้เปลี่ยนวันที่ของระบบหรือไม่ นอกจากนี้ยังแคชใบอนุญาตทดลองใช้ที่ดึงมาใน AES ที่เข้ารหัสแชร์การตั้งค่า คุณสามารถกำหนดค่าเวลาที่ถูกต้องของแคชด้วยตัวกำหนดค่า หากผู้ใช้ "ล้างข้อมูล" ไลบรารีจะบังคับให้มีการตรวจสอบฝั่งเซิร์ฟเวอร์

  • เซิร์ฟเวอร์กำลังใช้ https และการเซ็นชื่อแบบดิจิทัลในการตอบสนองการตรวจสอบใบอนุญาต นอกจากนี้ยังมี API สำหรับแอปทดลอง CRUD และผู้ใช้ (ผู้เผยแพร่และผู้พัฒนา) คล้ายกับนักพัฒนา Licensing Verfication Library สามารถทดสอบการใช้งานพฤติกรรมของพวกเขาในแอปทดลองพร้อมผลการทดสอบ ดังนั้นคุณในตัวกำหนดค่าคุณสามารถตั้งค่าการตอบกลับใบอนุญาตของคุณอย่างชัดเจนเป็น "license" "not license" หรือ "server error"

  • หากคุณอัปเดตแอปของคุณด้วยคุณสมบัติใหม่ที่น่าสนใจคุณอาจต้องการให้ทุกคนลองอีกครั้ง ในตัวกำหนดค่าคุณสามารถต่ออายุใบอนุญาตทดลองใช้สำหรับผู้ใช้ที่มีใบอนุญาตหมดอายุได้โดยการตั้งค่ารหัสเวอร์ชันที่จะเรียกใช้สิ่งนี้ ตัวอย่างเช่นผู้ใช้กำลังเรียกใช้แอปของคุณใน versioncode 3 และคุณต้องการให้เขาลองใช้คุณสมบัติของ versioncode 4 หากเขาอัปเดตแอปหรือติดตั้งใหม่เขาจะสามารถใช้ช่วงทดลองใช้งานเต็มรูปแบบได้อีกครั้งเนื่องจากเซิร์ฟเวอร์ทราบว่าเขาได้ลองใช้เวอร์ชันใดล่าสุด เวลา.

  • ทุกอย่างอยู่ภายใต้ลิขสิทธิ์ Apache 2.0


2
คุณเพิ่งช่วยฉันวันนี้ขอบคุณสำหรับการทำงานหนัก ฉันคิดว่าจะเขียนวิธีการเดียวกันกับการใช้การกำหนดค่าที่เข้ารหัสที่ได้รับเพียงครั้งเดียวและเก็บคีย์สาธารณะไว้ในแอปพลิเคชัน ปัญหาสำคัญที่ฉันเห็นคือคุณให้ใบอนุญาตได้อย่างไร? คุณยังอาจต้องใช้ ID ที่ไม่ซ้ำกันในโทรศัพท์เพื่อหลีกเลี่ยงการให้สิทธิ์หลายครั้ง
user2305886

6

วิธีที่ง่ายที่สุดและดีที่สุดคือใช้ BackupSharedPreferences

ค่ากำหนดจะยังคงอยู่แม้ว่าจะถอนการติดตั้งและติดตั้งแอปใหม่แล้วก็ตาม

เพียงบันทึกวันที่ติดตั้งตามความต้องการและคุณก็พร้อมที่จะไป

นี่คือทฤษฎี: http://developer.android.com/reference/android/app/backup/SharedPreferencesBackupHelper.html

นี่คือตัวอย่าง: การ สำรองข้อมูล SharedPreferences ของ Android ไม่ทำงาน


3
ผู้ใช้สามารถปิดการสำรองข้อมูลในการตั้งค่าระบบ
Patrick

5

แนวทางที่ 4: ใช้เวลาในการติดตั้งแอปพลิเคชัน

ตั้งแต่ API ระดับ 9 (Android 2.3.2, 2.3.1, Android 2.3, ขนมปังขิง) มีfirstInstallTimeและlastUpdateTimePackageInfoใน

หากต้องการอ่านเพิ่มเติม: วิธีรับเวลาติดตั้งแอปจาก Android


วิธีที่ 1 จากคำตอบของ snctln สามารถใช้กับสิ่งนี้ได้อย่างน่าเชื่อถือโดยไม่ถูกหลีกเลี่ยงได้ง่ายหรือมีปัญหาเดียวกันหรือคล้ายกันหรือไม่?
jwinn

ฉันได้ทดสอบวิธีนี้แล้ว ด้านดีคือใช้งานได้แม้ว่าข้อมูลจะถูกล้าง ด้านเสียคือสามารถหลีกเลี่ยงได้โดยการถอนการติดตั้ง / ติดตั้งใหม่
Jean-Philippe Jodoin

สามารถยืนยัน: บน Pixel ของฉันการถอนการติดตั้งและการติดตั้งแอปใหม่จะรีเซ็ตเวลาติดตั้งครั้งแรก ซึ่งทำให้ผู้ใช้รีเซ็ตการทดลองใช้งานเป็นเรื่องเล็กน้อย
Fabian Streitel

3

ขณะนี้มีการเพิ่มการสมัครสมาชิกรุ่นทดลองใช้ฟรีของ Android เวอร์ชันล่าสุดแล้วคุณสามารถปลดล็อกคุณสมบัติทั้งหมดของแอปได้หลังจากซื้อการสมัครสมาชิกภายในแอปเพื่อทดลองใช้ฟรี วิธีนี้จะช่วยให้ผู้ใช้สามารถใช้แอปของคุณเป็นระยะเวลาทดลองใช้งานได้หากแอปยังคงถูกถอนการติดตั้งหลังจากช่วงทดลองใช้งานแล้วเงินในการสมัครสมาชิกจะถูกโอนไปให้คุณ ฉันยังไม่ได้ลอง แต่แค่แบ่งปันความคิด

นี่คือเอกสาร


2
ฉันหวังว่าสิ่งนี้จะใช้ได้กับการซื้อครั้งเดียว ใช้ได้เฉพาะกับการสมัครสมาชิกซึ่งอาจเป็นรายปีหรือรายเดือน
jwinn

3

ในความคิดของฉันวิธีที่ดีที่สุดในการทำเช่นนี้คือใช้ฐานข้อมูลเรียลไทม์ของ Firebase:

1) เพิ่มการสนับสนุน Firebase ให้กับแอปของคุณ

2) เลือก 'การรับรองความถูกต้องแบบไม่ระบุตัวตน' เพื่อให้ผู้ใช้ไม่ต้องสมัครหรือรู้ว่าคุณกำลังทำอะไรอยู่ สิ่งนี้รับประกันได้ว่าจะเชื่อมโยงกับบัญชีผู้ใช้ที่ได้รับการรับรองความถูกต้องในปัจจุบันและจะใช้ได้กับอุปกรณ์ต่างๆ

3) ใช้ Realtime Database API เพื่อตั้งค่าสำหรับ 'installed_date' ในเวลาเปิดตัวเพียงแค่ดึงค่านี้และใช้สิ่งนี้

ฉันทำแบบเดียวกันและได้ผลดี ฉันสามารถทดสอบสิ่งนี้ในการถอนการติดตั้ง / ติดตั้งใหม่และค่าในฐานข้อมูลเรียลไทม์ยังคงเหมือนเดิม วิธีนี้ระยะเวลาทดลองใช้ของคุณใช้ได้กับอุปกรณ์ของผู้ใช้หลายเครื่อง คุณยังสามารถกำหนดเวอร์ชัน install_date ของคุณเพื่อให้แอป 'รีเซ็ต' วันที่ทดลองใช้สำหรับรุ่นหลักใหม่แต่ละรุ่น

อัปเดต : หลังจากทดสอบอีกเล็กน้อยดูเหมือนว่า Firebase ที่ไม่ระบุตัวตนจะจัดสรร ID ที่แตกต่างกันในกรณีที่คุณมีอุปกรณ์ที่แตกต่างกันและไม่รับประกันระหว่างการติดตั้งซ้ำ: / วิธีเดียวที่รับประกันได้คือใช้ Firebase แต่ผูกไว้กับ Google บัญชีผู้ใช้. สิ่งนี้ควรใช้งานได้ แต่จะต้องมีขั้นตอนเพิ่มเติมที่ผู้ใช้ต้องเข้าสู่ระบบ / สมัครใช้งานก่อน

จนถึงตอนนี้ฉันลงเอยด้วยวิธีการที่หรูหราน้อยกว่าเล็กน้อยเพียงแค่ตรวจสอบกับค่ากำหนดที่สำรองไว้และวันที่ที่เก็บไว้ในการตั้งค่าเมื่อติดตั้ง สิ่งนี้ใช้ได้กับแอปที่เน้นข้อมูลเป็นศูนย์กลางซึ่งไม่มีจุดหมายสำหรับผู้ที่จะติดตั้งแอปใหม่และป้อนข้อมูลทั้งหมดที่เพิ่มก่อนหน้านี้อีกครั้ง แต่จะใช้ไม่ได้กับเกมง่ายๆ


ฉันมีข้อกำหนดเดียวกันสำหรับแอป Android ของฉันและฉันมีฐานข้อมูล / เว็บเซิร์ฟเวอร์ของตัวเอง ผู้ใช้ไม่ต้องการการเข้าสู่ระบบดังนั้นฉันจึงวางแผนที่จะบันทึก ID อุปกรณ์ด้วยวันที่ติดตั้งจะได้ผลหรือไม่
user636525

@strangetimes ฉันคิดว่าโซลูชันของคุณทำงานได้ดีที่สุดคุณสามารถให้ข้อมูลเพิ่มเติมได้หรือไม่? ขอบคุณ
DayDayHappy

หัวข้อนี้stackoverflow.com/q/41733137/1396068แนะนำว่าหลังจากติดตั้งแอปใหม่คุณจะได้รับ ID ผู้ใช้ใหม่คือช่วงทดลองใช้งานใหม่
Fabian Streitel

3

หลังจากดูตัวเลือกทั้งหมดในหัวข้อนี้และหัวข้ออื่น ๆ แล้วนี่คือสิ่งที่ฉันค้นพบ

การตั้งค่าฐานข้อมูลที่ใช้ร่วมกัน สามารถล้างได้ในการตั้งค่า Android หายไปหลังจากติดตั้งแอปใหม่ สามารถสำรองข้อมูลด้วยกลไกการสำรองข้อมูลของ Androidและจะถูกเรียกคืนหลังจากติดตั้งใหม่ การสำรองข้อมูลอาจไม่พร้อมใช้งานเสมอไป แต่ควรอยู่ในอุปกรณ์ส่วนใหญ่

จัดเก็บข้อมูลภายนอก (เขียนไปยังแฟ้ม) ไม่ได้รับผลกระทบจากความชัดเจนจากการตั้งค่าหรือติดตั้งถ้าเราไม่ได้เขียนไปยังไดเรกทอรีส่วนตัวของโปรแกรมประยุกต์ แต่: คุณต้องขออนุญาตจากผู้ใช้ขณะรันไทม์ในเวอร์ชัน Android ที่ใหม่กว่าดังนั้นสิ่งนี้อาจเป็นไปได้ก็ต่อเมื่อคุณต้องการการอนุญาตเท่านั้น ยังสามารถสำรองข้อมูล

PackageInfo.firstInstallTime ถูก รีเซ็ตหลังจากติดตั้งใหม่ แต่เสถียรในการอัปเดต

ลงชื่อเข้าใช้บางบัญชี ไม่สำคัญว่าจะเป็นบัญชี Google ผ่าน Firebase หรือบัญชีในเซิร์ฟเวอร์ของคุณเองการทดลองใช้จะผูกมัดกับบัญชี การสร้างบัญชีใหม่จะรีเซ็ตการทดลองใช้

การลงชื่อเข้าใช้แบบไม่ระบุตัวตนของ Firebase คุณสามารถลงชื่อเข้าใช้โดยไม่ระบุชื่อผู้ใช้และจัดเก็บข้อมูลสำหรับพวกเขาใน Firebase แต่เห็นได้ชัดว่ามีการติดตั้งแอปใหม่และเหตุการณ์อื่น ๆ ที่ไม่มีเอกสารอาจทำให้ผู้ใช้มี ID ที่ไม่ระบุตัวตนใหม่ซึ่งเป็นการรีเซ็ตเวลาทดลองใช้ (Google เองไม่ได้ให้เอกสารเกี่ยวกับเรื่องนี้มากนัก)

ANDROID_ID อาจไม่มีให้ใช้งานและอาจมีการเปลี่ยนแปลงภายใต้สถานการณ์บางอย่างเช่นการรีเซ็ตเป็นค่าเริ่มต้นจากโรงงาน ความคิดเห็นว่าควรใช้สิ่งนี้เพื่อระบุอุปกรณ์หรือไม่

ผู้ใช้ อาจรีเซ็ตรหัสโฆษณา Play ผู้ใช้อาจปิดการใช้งานโดยการเลือกไม่ใช้การติดตามโฆษณา

InstanceID รีเซ็ตบนติดตั้ง รีเซ็ตในกรณีที่เกิดเหตุการณ์ด้านความปลอดภัย สามารถรีเซ็ตได้โดยแอปของคุณ

(การรวมกัน) วิธีการใดที่เหมาะกับคุณขึ้นอยู่กับแอปของคุณและความพยายามที่คุณคิดว่าโดยเฉลี่ยแล้ว John จะใช้เวลาทดลองใช้อีกครั้ง ฉันขอแนะนำให้หลีกเลี่ยงการใช้เฉพาะ Firebase และรหัสโฆษณาที่ไม่ระบุตัวตนเนื่องจากความไม่เสถียร ดูเหมือนว่าวิธีการหลายปัจจัยจะให้ผลลัพธ์ที่ดีที่สุด ปัจจัยใดที่คุณสามารถใช้ได้ขึ้นอยู่กับแอปของคุณและสิทธิ์ของแอป

สำหรับแอปของฉันเองฉันพบว่าการตั้งค่าที่ใช้ร่วมกัน + การสำรองข้อมูล firstInstallTime + ของการตั้งค่าเป็นวิธีที่รบกวนน้อยที่สุด แต่ก็มีประสิทธิภาพเพียงพอ คุณต้องแน่ใจว่าคุณขอข้อมูลสำรองหลังจากตรวจสอบและจัดเก็บเวลาเริ่มทดลองใช้ในการตั้งค่าที่ใช้ร่วมกันเท่านั้น ค่าใน Prefs ที่แบ่งใช้ต้องมีความสำคัญเหนือ firstInstallTime จากนั้นผู้ใช้จะต้องติดตั้งแอพใหม่เรียกใช้หนึ่งครั้งจากนั้นล้างข้อมูลของแอพเพื่อรีเซ็ตการทดลองใช้ซึ่งค่อนข้างทำงานได้ดี บนอุปกรณ์ที่ไม่มีการขนส่งสำรองผู้ใช้สามารถรีเซ็ตการทดลองใช้งานได้โดยเพียงแค่ติดตั้งใหม่

ฉันได้ทำให้แนวทางนั้นพร้อมใช้งานเป็นไลบรารีที่ขยายได้


1

ตามความหมายแล้วแอป Android ที่ต้องซื้อทั้งหมดในตลาดสามารถประเมินได้ภายใน 24 ชั่วโมงหลังการซื้อ

มีปุ่ม 'ถอนการติดตั้งและคืนเงิน' ซึ่งจะเปลี่ยนเป็น 'ถอนการติดตั้ง' หลังจาก 24 ชั่วโมง

ฉันขอยืนยันว่าปุ่มนี้โดดเด่นเกินไป!


17
โปรดทราบว่าขณะนี้ระยะเวลาการคืนเงินเหลือเพียง 15 นาทีเท่านั้น
Intrications

1

ฉันเจอคำถามนี้ในขณะที่ค้นหาปัญหาเดียวกันฉันคิดว่าเราสามารถใช้ 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

}
}

วิธีแก้ปัญหาการทำงาน .....


แน่ใจว่าคุณสามารถใช้ได้ แต่ในกรณีนี้จะมีเพียงกิจกรรมหลักของคุณเท่านั้นที่สามารถตรวจสอบการหมดอายุได้
RQube

0

นี่คือวิธีที่ฉันทำเกี่ยวกับของฉันฉันสร้าง 2 แอพหนึ่งโดยมีกิจกรรมทดลองใช้อีกแอพหนึ่งโดยไม่มี

ฉันอัปโหลดหนึ่งโดยไม่มีกิจกรรมทดลองเล่นเพื่อเล่นร้านค้าเป็นแอปที่ต้องซื้อ

และกิจกรรมทดลองใช้เป็นแอปฟรี

แอปฟรีในการเปิดตัวครั้งแรกมีตัวเลือกสำหรับการทดลองใช้และการซื้อในร้านหากผู้ใช้เลือกร้านค้าที่ซื้อแอปจะเปลี่ยนเส้นทางไปยังร้านค้าเพื่อให้ผู้ใช้ซื้อ แต่หากผู้ใช้คลิกทดลองใช้จะนำพวกเขาไปสู่กิจกรรมทดลองใช้

หมายเหตุ: ฉันใช้ตัวเลือก 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 ของกิจกรรมหลักด้วยข้อความที่นำพวกเขาไปที่ร้านเพื่อซื้อ

ข้อเสียอย่างเดียวที่ฉันเห็นที่นี่คือหากผู้ใช้ Rogueซื้อแอปที่ต้องซื้อและตัดสินใจที่จะแชร์กับแอปเช่น Zender แชร์ไฟล์หรือแม้แต่โฮสต์ไฟล์ apk โดยตรงบนเซิร์ฟเวอร์เพื่อให้ผู้ใช้ดาวน์โหลดได้ฟรี แต่ฉันแน่ใจว่าเร็ว ๆ นี้ฉันจะแก้ไขคำตอบนี้ด้วยวิธีแก้ปัญหานั้นหรือลิงก์ไปยังโซลูชัน

หวังว่านี่จะช่วยชีวิต ... สักวัน

แฮปปี้โค้ดดิ้ง ...


0

@snctln option 3 สามารถทำได้อย่างง่ายดายเพิ่มไฟล์ php ไปยังเว็บเซิร์ฟเวอร์ที่ติดตั้ง php และ mysql เท่าที่มี

จากฝั่ง Android ตัวระบุ (รหัสอุปกรณ์บัญชี Google หรืออะไรก็ได้ที่คุณต้องการ) จะถูกส่งเป็นอาร์กิวเมนต์ใน URL โดยใช้ HttpURLConnection และ php จะส่งคืนวันที่ของการติดตั้งครั้งแรกหากมีอยู่ในตารางหรือแทรกแถวใหม่และ มันส่งคืนวันที่ปัจจุบัน

มันใช้ได้ดีสำหรับฉัน

ถ้าฉันมีเวลาฉันจะโพสต์รหัส!

โชคดี !


รอ แต่คุณทำ id เฉพาะของคุณหลวมหลังจากติดตั้งแอพใหม่หรือไม่ !!
Maksim Kniazev

รหัสนี้ระบุฮาร์ดแวร์โทรศัพท์เองผู้ใช้ไม่เห็นและไม่สามารถเปลี่ยนแปลงได้ หากเขาติดตั้งแอปใหม่บริการเว็บ php จะตรวจพบว่าเป็นโทรศัพท์เครื่องเดียวกัน ในทางกลับกันหากผู้ใช้เปลี่ยนโทรศัพท์เขาจะเพลิดเพลินไปกับช่วงเวลาใหม่
Lluis Felisart
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.