ฉันมีแอปพลิเคชันที่ต้องการเข้าสู่ตลาดเป็นแอปที่ต้องซื้อ ฉันต้องการที่จะมีเวอร์ชันอื่นซึ่งจะเป็นเวอร์ชัน "ทดลองใช้" โดยกำหนดเวลาไว้ว่า 5 วัน?
ฉันจะทำสิ่งนี้ได้อย่างไร?
ฉันมีแอปพลิเคชันที่ต้องการเข้าสู่ตลาดเป็นแอปที่ต้องซื้อ ฉันต้องการที่จะมีเวอร์ชันอื่นซึ่งจะเป็นเวอร์ชัน "ทดลองใช้" โดยกำหนดเวลาไว้ว่า 5 วัน?
ฉันจะทำสิ่งนี้ได้อย่างไร?
คำตอบ:
ปัจจุบันนักพัฒนาส่วนใหญ่ทำได้โดยใช้หนึ่งใน 3 เทคนิคต่อไปนี้
แนวทางแรกสามารถหลีกเลี่ยงได้ง่ายในครั้งแรกที่คุณเรียกใช้แอปให้บันทึกวันที่ / เวลาลงในไฟล์ฐานข้อมูลหรือค่ากำหนดที่ใช้ร่วมกันและทุกครั้งที่คุณเรียกใช้แอปหลังจากนั้นให้ตรวจสอบว่าระยะทดลองใช้สิ้นสุดลงหรือไม่ นี่เป็นเรื่องง่ายที่จะหลีกเลี่ยงเนื่องจากการถอนการติดตั้งและการติดตั้งใหม่จะทำให้ผู้ใช้มีช่วงทดลองใช้งานอีกครั้ง
แนวทางที่สองนั้นยากกว่าที่จะหลีกเลี่ยง แต่ก็ยังหลีกเลี่ยงไม่ได้ ใช้ระเบิดเวลาที่เข้ารหัสยาก โดยพื้นฐานแล้วด้วยวิธีนี้คุณจะกำหนดวันที่สิ้นสุดสำหรับการทดลองใช้ฮาร์ดโค้ดและผู้ใช้ทั้งหมดที่ดาวน์โหลดและใช้แอพจะหยุดไม่สามารถใช้แอพได้ในเวลาเดียวกัน ฉันใช้แนวทางนี้เพราะง่ายต่อการนำไปใช้และส่วนใหญ่ฉันไม่รู้สึกว่าจะต้องเจอกับปัญหาของเทคนิคที่สาม ผู้ใช้สามารถหลีกเลี่ยงปัญหานี้ได้โดยการเปลี่ยนวันที่ในโทรศัพท์ด้วยตนเอง แต่ผู้ใช้ส่วนใหญ่จะไม่ประสบปัญหาในการทำสิ่งนั้น
เทคนิคที่สามเป็นวิธีเดียวที่ฉันเคยได้ยินมาว่าจะสามารถบรรลุสิ่งที่คุณต้องการทำได้อย่างแท้จริง คุณจะต้องตั้งค่าเซิร์ฟเวอร์จากนั้นเมื่อใดก็ตามที่แอปพลิเคชันของคุณเริ่มต้นแอปของคุณจะส่งตัวระบุเฉพาะของโทรศัพท์ไปยังเซิร์ฟเวอร์ หากเซิร์ฟเวอร์ไม่มีรายการสำหรับรหัสโทรศัพท์นั้นเซิร์ฟเวอร์จะสร้างรายการใหม่และบันทึกเวลา หากเซิร์ฟเวอร์มีรายการสำหรับรหัสโทรศัพท์ระบบจะทำการตรวจสอบอย่างง่ายเพื่อดูว่าช่วงทดลองใช้หมดอายุหรือไม่ จากนั้นจะแจ้งผลการตรวจสอบการหมดอายุการทดลองใช้กลับไปยังใบสมัครของคุณ แนวทางนี้ไม่ควรหลีกเลี่ยงได้ แต่จำเป็นต้องมีการตั้งค่าเว็บเซิร์ฟเวอร์และอื่น ๆ
การตรวจสอบเหล่านี้ใน onCreate เป็นวิธีปฏิบัติที่ดีเสมอ หากการหมดอายุสิ้นสุดลงป๊อปอัป AlertDialog พร้อมลิงก์ตลาดไปยังแอปเวอร์ชันเต็ม รวมเฉพาะปุ่ม "ตกลง" และเมื่อผู้ใช้คลิกที่ "ตกลง" ให้โทรไปที่ "เสร็จสิ้น ()" เพื่อสิ้นสุดกิจกรรม
ฉันได้พัฒนา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
นี่เป็นคำถามเก่า แต่อย่างไรก็ตามบางทีนี่อาจช่วยใครบางคนได้
ในกรณีที่คุณต้องการใช้วิธีที่ง่ายที่สุด (ซึ่งจะล้มเหลวหากถอนการติดตั้ง / ติดตั้งแอปใหม่หรือผู้ใช้เปลี่ยนวันที่ของอุปกรณ์ด้วยตนเอง) นี่คือวิธีที่สามารถทำได้:
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 !!!
}
}
...
}
getTime
getTimeInMillis
คำถามนี้และคำตอบของ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
วิธีที่ง่ายที่สุดและดีที่สุดคือใช้ BackupSharedPreferences
ค่ากำหนดจะยังคงอยู่แม้ว่าจะถอนการติดตั้งและติดตั้งแอปใหม่แล้วก็ตาม
เพียงบันทึกวันที่ติดตั้งตามความต้องการและคุณก็พร้อมที่จะไป
นี่คือทฤษฎี: http://developer.android.com/reference/android/app/backup/SharedPreferencesBackupHelper.html
นี่คือตัวอย่าง: การ สำรองข้อมูล SharedPreferences ของ Android ไม่ทำงาน
แนวทางที่ 4: ใช้เวลาในการติดตั้งแอปพลิเคชัน
ตั้งแต่ API ระดับ 9 (Android 2.3.2, 2.3.1, Android 2.3, ขนมปังขิง) มีfirstInstallTimeและlastUpdateTimePackageInfo
ใน
หากต้องการอ่านเพิ่มเติม: วิธีรับเวลาติดตั้งแอปจาก Android
ขณะนี้มีการเพิ่มการสมัครสมาชิกรุ่นทดลองใช้ฟรีของ Android เวอร์ชันล่าสุดแล้วคุณสามารถปลดล็อกคุณสมบัติทั้งหมดของแอปได้หลังจากซื้อการสมัครสมาชิกภายในแอปเพื่อทดลองใช้ฟรี วิธีนี้จะช่วยให้ผู้ใช้สามารถใช้แอปของคุณเป็นระยะเวลาทดลองใช้งานได้หากแอปยังคงถูกถอนการติดตั้งหลังจากช่วงทดลองใช้งานแล้วเงินในการสมัครสมาชิกจะถูกโอนไปให้คุณ ฉันยังไม่ได้ลอง แต่แค่แบ่งปันความคิด
ในความคิดของฉันวิธีที่ดีที่สุดในการทำเช่นนี้คือใช้ฐานข้อมูลเรียลไทม์ของ Firebase:
1) เพิ่มการสนับสนุน Firebase ให้กับแอปของคุณ
2) เลือก 'การรับรองความถูกต้องแบบไม่ระบุตัวตน' เพื่อให้ผู้ใช้ไม่ต้องสมัครหรือรู้ว่าคุณกำลังทำอะไรอยู่ สิ่งนี้รับประกันได้ว่าจะเชื่อมโยงกับบัญชีผู้ใช้ที่ได้รับการรับรองความถูกต้องในปัจจุบันและจะใช้ได้กับอุปกรณ์ต่างๆ
3) ใช้ Realtime Database API เพื่อตั้งค่าสำหรับ 'installed_date' ในเวลาเปิดตัวเพียงแค่ดึงค่านี้และใช้สิ่งนี้
ฉันทำแบบเดียวกันและได้ผลดี ฉันสามารถทดสอบสิ่งนี้ในการถอนการติดตั้ง / ติดตั้งใหม่และค่าในฐานข้อมูลเรียลไทม์ยังคงเหมือนเดิม วิธีนี้ระยะเวลาทดลองใช้ของคุณใช้ได้กับอุปกรณ์ของผู้ใช้หลายเครื่อง คุณยังสามารถกำหนดเวอร์ชัน install_date ของคุณเพื่อให้แอป 'รีเซ็ต' วันที่ทดลองใช้สำหรับรุ่นหลักใหม่แต่ละรุ่น
อัปเดต : หลังจากทดสอบอีกเล็กน้อยดูเหมือนว่า Firebase ที่ไม่ระบุตัวตนจะจัดสรร ID ที่แตกต่างกันในกรณีที่คุณมีอุปกรณ์ที่แตกต่างกันและไม่รับประกันระหว่างการติดตั้งซ้ำ: / วิธีเดียวที่รับประกันได้คือใช้ Firebase แต่ผูกไว้กับ Google บัญชีผู้ใช้. สิ่งนี้ควรใช้งานได้ แต่จะต้องมีขั้นตอนเพิ่มเติมที่ผู้ใช้ต้องเข้าสู่ระบบ / สมัครใช้งานก่อน
จนถึงตอนนี้ฉันลงเอยด้วยวิธีการที่หรูหราน้อยกว่าเล็กน้อยเพียงแค่ตรวจสอบกับค่ากำหนดที่สำรองไว้และวันที่ที่เก็บไว้ในการตั้งค่าเมื่อติดตั้ง สิ่งนี้ใช้ได้กับแอปที่เน้นข้อมูลเป็นศูนย์กลางซึ่งไม่มีจุดหมายสำหรับผู้ที่จะติดตั้งแอปใหม่และป้อนข้อมูลทั้งหมดที่เพิ่มก่อนหน้านี้อีกครั้ง แต่จะใช้ไม่ได้กับเกมง่ายๆ
หลังจากดูตัวเลือกทั้งหมดในหัวข้อนี้และหัวข้ออื่น ๆ แล้วนี่คือสิ่งที่ฉันค้นพบ
การตั้งค่าฐานข้อมูลที่ใช้ร่วมกัน สามารถล้างได้ในการตั้งค่า Android หายไปหลังจากติดตั้งแอปใหม่ สามารถสำรองข้อมูลด้วยกลไกการสำรองข้อมูลของ Androidและจะถูกเรียกคืนหลังจากติดตั้งใหม่ การสำรองข้อมูลอาจไม่พร้อมใช้งานเสมอไป แต่ควรอยู่ในอุปกรณ์ส่วนใหญ่
จัดเก็บข้อมูลภายนอก (เขียนไปยังแฟ้ม) ไม่ได้รับผลกระทบจากความชัดเจนจากการตั้งค่าหรือติดตั้งถ้าเราไม่ได้เขียนไปยังไดเรกทอรีส่วนตัวของโปรแกรมประยุกต์ แต่: คุณต้องขออนุญาตจากผู้ใช้ขณะรันไทม์ในเวอร์ชัน Android ที่ใหม่กว่าดังนั้นสิ่งนี้อาจเป็นไปได้ก็ต่อเมื่อคุณต้องการการอนุญาตเท่านั้น ยังสามารถสำรองข้อมูล
PackageInfo.firstInstallTime ถูก รีเซ็ตหลังจากติดตั้งใหม่ แต่เสถียรในการอัปเดต
ลงชื่อเข้าใช้บางบัญชี ไม่สำคัญว่าจะเป็นบัญชี Google ผ่าน Firebase หรือบัญชีในเซิร์ฟเวอร์ของคุณเองการทดลองใช้จะผูกมัดกับบัญชี การสร้างบัญชีใหม่จะรีเซ็ตการทดลองใช้
การลงชื่อเข้าใช้แบบไม่ระบุตัวตนของ Firebase คุณสามารถลงชื่อเข้าใช้โดยไม่ระบุชื่อผู้ใช้และจัดเก็บข้อมูลสำหรับพวกเขาใน Firebase แต่เห็นได้ชัดว่ามีการติดตั้งแอปใหม่และเหตุการณ์อื่น ๆ ที่ไม่มีเอกสารอาจทำให้ผู้ใช้มี ID ที่ไม่ระบุตัวตนใหม่ซึ่งเป็นการรีเซ็ตเวลาทดลองใช้ (Google เองไม่ได้ให้เอกสารเกี่ยวกับเรื่องนี้มากนัก)
ANDROID_ID อาจไม่มีให้ใช้งานและอาจมีการเปลี่ยนแปลงภายใต้สถานการณ์บางอย่างเช่นการรีเซ็ตเป็นค่าเริ่มต้นจากโรงงาน ความคิดเห็นว่าควรใช้สิ่งนี้เพื่อระบุอุปกรณ์หรือไม่
ผู้ใช้ อาจรีเซ็ตรหัสโฆษณา Play ผู้ใช้อาจปิดการใช้งานโดยการเลือกไม่ใช้การติดตามโฆษณา
InstanceID รีเซ็ตบนติดตั้ง รีเซ็ตในกรณีที่เกิดเหตุการณ์ด้านความปลอดภัย สามารถรีเซ็ตได้โดยแอปของคุณ
(การรวมกัน) วิธีการใดที่เหมาะกับคุณขึ้นอยู่กับแอปของคุณและความพยายามที่คุณคิดว่าโดยเฉลี่ยแล้ว John จะใช้เวลาทดลองใช้อีกครั้ง ฉันขอแนะนำให้หลีกเลี่ยงการใช้เฉพาะ Firebase และรหัสโฆษณาที่ไม่ระบุตัวตนเนื่องจากความไม่เสถียร ดูเหมือนว่าวิธีการหลายปัจจัยจะให้ผลลัพธ์ที่ดีที่สุด ปัจจัยใดที่คุณสามารถใช้ได้ขึ้นอยู่กับแอปของคุณและสิทธิ์ของแอป
สำหรับแอปของฉันเองฉันพบว่าการตั้งค่าที่ใช้ร่วมกัน + การสำรองข้อมูล 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 แอพหนึ่งโดยมีกิจกรรมทดลองใช้อีกแอพหนึ่งโดยไม่มี
ฉันอัปโหลดหนึ่งโดยไม่มีกิจกรรมทดลองเล่นเพื่อเล่นร้านค้าเป็นแอปที่ต้องซื้อ
และกิจกรรมทดลองใช้เป็นแอปฟรี
แอปฟรีในการเปิดตัวครั้งแรกมีตัวเลือกสำหรับการทดลองใช้และการซื้อในร้านหากผู้ใช้เลือกร้านค้าที่ซื้อแอปจะเปลี่ยนเส้นทางไปยังร้านค้าเพื่อให้ผู้ใช้ซื้อ แต่หากผู้ใช้คลิกทดลองใช้จะนำพวกเขาไปสู่กิจกรรมทดลองใช้
หมายเหตุ: ฉันใช้ตัวเลือก 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 โดยตรงบนเซิร์ฟเวอร์เพื่อให้ผู้ใช้ดาวน์โหลดได้ฟรี แต่ฉันแน่ใจว่าเร็ว ๆ นี้ฉันจะแก้ไขคำตอบนี้ด้วยวิธีแก้ปัญหานั้นหรือลิงก์ไปยังโซลูชัน
หวังว่านี่จะช่วยชีวิต ... สักวัน
แฮปปี้โค้ดดิ้ง ...
@snctln option 3 สามารถทำได้อย่างง่ายดายเพิ่มไฟล์ php ไปยังเว็บเซิร์ฟเวอร์ที่ติดตั้ง php และ mysql เท่าที่มี
จากฝั่ง Android ตัวระบุ (รหัสอุปกรณ์บัญชี Google หรืออะไรก็ได้ที่คุณต้องการ) จะถูกส่งเป็นอาร์กิวเมนต์ใน URL โดยใช้ HttpURLConnection และ php จะส่งคืนวันที่ของการติดตั้งครั้งแรกหากมีอยู่ในตารางหรือแทรกแถวใหม่และ มันส่งคืนวันที่ปัจจุบัน
มันใช้ได้ดีสำหรับฉัน
ถ้าฉันมีเวลาฉันจะโพสต์รหัส!
โชคดี !