คำนำ: เมื่อไม่นานมานี้เมื่อพัฒนาแอพฉันมักจะไม่สามารถรับข้อมูลที่ได้จากเครือข่ายเนื่องจากสภาพแวดล้อมอุปกรณ์ของผู้ใช้ดังนั้นผลลัพธ์ที่แสดงในแอพจึงเป็นกล่องว่างเสมอ สถานการณ์นี้แย่มากสำหรับประสบการณ์ผู้ใช้ ดังนั้นฉันจึงคิดอย่างหนักและตัดสินใจที่จะเริ่มต้นด้วย OKHTTP (เพราะกรอบการร้องขอเครือข่ายที่ฉันใช้ในโครงการคือ OKHTTP) และฉันเขียน Interceptor แคชข้อมูลเครือข่ายดังกล่าว
ตกลงจากนั้นเราตัดสินใจที่จะเริ่มเขียน ก่อนอื่นให้ฉันพูดถึงความคิดของฉัน:
ความคิด
เนื่องจากเรากำลังเขียนเกี่ยวกับตัวดักจับแคชข้อมูลเครือข่ายซึ่งส่วนใหญ่ใช้ฟังก์ชั่น interceptor ที่ทรงพลังของ Okhttp เราควรแคชข้อมูลอะไรหรือภายใต้สถานการณ์ใดที่เราควรเปิดใช้งานกลไกแคชข้อมูล
ครั้งแรก: สนับสนุนคำขอโพสต์เนื่องจากเจ้าหน้าที่ได้จัดเตรียมแคช interceptor แต่ข้อเสียอย่างหนึ่งคือพวกเขาสามารถแคชข้อมูลที่ร้องขอโดย GET เท่านั้น แต่พวกเขาไม่สนับสนุนโพสต์
ประการที่สอง: เมื่อเครือข่ายเป็นปกติจะดึงข้อมูลจากเครือข่าย หากเครือข่ายมีความผิดปกติเช่น TimeoutException UnknowHostException และปัญหาอื่น ๆ เราต้องแคชและดึงข้อมูล
ประการที่สาม: หากข้อมูลที่ดึงมาจากแคชนั้นว่างเปล่าเราก็ยังต้องปล่อยให้คำขอนี้ผ่านกระบวนการปกติที่เหลืออยู่
ประการที่สี่: ผู้โทรจะต้องควบคุมกลไกการแคชอย่างสมบูรณ์และสามารถเลือกได้ว่าจะแคชข้อมูลตามความต้องการทางธุรกิจของเขาหรือเธอ
ประการที่ห้า: มันต้องใช้งานง่ายซึ่งเป็นจุดที่สำคัญที่สุด
ตกลงเราได้แสดงรายการห้าคะแนนด้านบนซึ่งเป็นแนวคิดทั่วไปของเรา พูดคุยเกี่ยวกับส่วนรหัสตอนนี้:
บทความรหัส
กรอบการแคช: กรอบแคชที่ฉันใช้ที่นี่คือ disklrucache https://github.com/jakewharton/disklrucache เฟรมเวิร์กแคชนี้สามารถจัดเก็บได้ในเครื่องและได้รับการอนุมัติจาก Google นี่เป็นเหตุผลหลักในการเลือกเฟรมเวิร์กนี้ ฉันยังห่อหุ้มคลาส cachemanager ไปยังกรอบแคช:
นำเข้า Android.content.context; นำเข้า Android.content.pm.packageInfo; นำเข้า Android.content.pm.pm.pm.pmackemanager; นำเข้า com.xiaolei.okhttpcacheptorceptor.log.log; นำเข้า java.io.bytearrayutputstream; java.io.ioException; นำเข้า java.io.OutputStream; นำเข้า java.io.unsupportencodexception; นำเข้า Java.security.messagedigest; นำเข้า Java.security.nosuchalgorithmexception; */คลาสสาธารณะ cachemanager {public stative final String tag = "cachemanager"; // ขนาดแคชสูงสุด 10MB ส่วนตัวคงที่สุดท้าย DISK_CACHE_SIZE = 1024 * 10; INT สุดท้ายคงที่ INT DISK_CACHE_INDEX = 0; สตริงสุดท้ายคงที่ส่วนตัว CACHE_DIR = "การตอบสนอง"; disklrucache ส่วนตัว mdisklrucache; cachemanager แบบคงที่ส่วนตัว McAcheManager; สาธารณะ cachemanager สาธารณะ getInstance (บริบทบริบท) {ถ้า (mcachemanager == null) {ซิงโครไนซ์ (cachemanager.class) {ถ้า (mcachemanager == null) {mcachemanager = ใหม่ cachemanager (บริบท); }}} ส่งคืน mcachemanager; } cacheManager ส่วนตัว (บริบทบริบท) {ไฟล์ diskcachedir = getDiskCachedir (บริบท, cache_dir); if (! diskcachedir.exists ()) {boolean b = diskcachedir.mkdir (); log.d (tag, "! diskcachedir.exists () --- diskcachedir.mkdir () =" + b); } if (diskcachedir.getUsableSpace ()> disk_cache_size) {ลอง {mdisklrucache = disklrucache.open (diskcachedir, getappersion (บริบท), 1/*ไฟล์จำนวนมากสอดคล้องกับกุญแจ*/, disk_cache_size); log.d (แท็ก, "mdisklrucache สร้างขึ้น"); } catch (ioexception e) {e.printstacktrace (); }}} / *** ตั้งค่าแคชแบบซิงโครนัส* / โมฆะสาธารณะ putcache (คีย์สตริง, ค่าสตริง) {ถ้า (mdisklrucache == null) return; OutputStream OS = NULL; ลอง {disklrucache.editor editor = mdiskLrucache.edit (ENCRYPTMD5 (คีย์)); OS = editor.newOutputStream (disk_cache_index); OS.WRITE (value.getBytes ()); os.flush (); editor.Commit (); mdisklrucache.flush (); } catch (ioexception e) {e.printstacktrace (); } ในที่สุด {ถ้า (os! = null) {ลอง {os.close (); } catch (ioexception e) {e.printstacktrace (); }}}} / *** ตั้งค่าแคชแบบอะซิงโครนัส* / โมฆะสาธารณะ setCache (คีย์สตริงสุดท้ายค่าสตริงสุดท้าย) {เธรดใหม่ () {@Override โมฆะสาธารณะเรียกใช้ () {putcache (คีย์, ค่า); } }.เริ่ม(); } /*** ซิงโครนัส getCache getCache (คีย์สตริง) {ถ้า (mDiskLrucache == null) {return null; } fileInputStream fis = null; ByteArrayOutputStream BOS = NULL; ลอง {disklrucache.snapshot snapshot = mdisklrucache.get (encryptmd5 (คีย์)); if (snapshot! = null) {fis = (fileInputStream) snapshot.getInputStream (disk_cache_index); BOS = ใหม่ byteArrayOutputStream (); ไบต์ [] buf = ไบต์ใหม่ [1024]; int len; ในขณะที่ ((len = fis.read (buf))! = -1) {bos.write (buf, 0, len); } byte [] data = bos.tobytearray (); ส่งคืนสตริงใหม่ (ข้อมูล); }} catch (ioexception e) {e.printstacktrace (); } ในที่สุด {ถ้า (fis! = null) {ลอง {fis.close (); } catch (ioexception e) {e.printstacktrace (); }} if (bos! = null) {ลอง {bos.close (); } catch (ioexception e) {e.printstacktrace (); }}} return null; } / *** asynchronously getCache* / โมฆะสาธารณะ getCache (คีย์สตริงสุดท้าย, cachecallback สุดท้าย callback) {เธรดใหม่ () {@Override โมฆะสาธารณะเรียกใช้ () {สตริงแคช = getCache (คีย์); callback.ongetCache (แคช); } }.เริ่ม(); } / *** ลบแคช* / public boolean RemoveCache (คีย์สตริง) {ถ้า (mdisklrucache! = null) {ลอง {return mdisklucache.remove (Encryptmd5 (คีย์)); } catch (ioexception e) {e.printstacktrace (); }} return false; } / *** รับไดเรกทอรีแคช* / ไฟล์ส่วนตัว getDiskCachedir (บริบทบริบท, uniquename สตริง) {สตริง cachepath = context.getCachedir (). getPath (); ส่งคืนไฟล์ใหม่ (cachepath + file.Sparator + uniquename); } / *** การเข้ารหัส MD5 ของสตริง* / สตริงคงที่สาธารณะ Encryptmd5 (สตริงสตริง) {ลอง {byte [] hash = messageGeSt.getInstance ("md5"). digest (string.getBytes ("UTF-8")); StringBuilder hex = new StringBuilder (hash.length * 2); สำหรับ (byte b: hash) {if ((b & 0xff) <0x10) {hex.append ("0"); } hex.append (Integer.tohexstring (B & 0xff)); } return hex.toString (); } catch (nosuchalgorithmexception | unsupportencodingexception e) {e.printstacktrace (); } return String; } / *** รับหมายเลขเวอร์ชันแอพ* / ส่วนตัว int getAppversion (บริบทบริบท) {PackageManager PM = context.getPackageManager (); ลอง {PackageInfo pi = pm.getPackageInfo (context.getPackagename (), 0); กลับ pi == null? 0: PI.VersionCode; } catch (packageManager.namenotfoundException e) {e.printstacktrace (); } return 0; - cacheinterceptor interceptor: ใช้กลไกการสกัดกั้นของ Okhttp เพื่อตัดสินสถานการณ์แคชและเงื่อนไขเครือข่ายอย่างชาญฉลาดและจัดการกับสถานการณ์ที่แตกต่างกัน
นำเข้า Android.content.context; นำเข้า com.xiolei.okhttpcacheinceDceptor.catch.cachemanager; นำเข้า com.xiaolei.okhttpcachementeceptor.log.log; นำเข้า java.io.ioexception; นำเข้า okhttp3.formbody; okhttp3.request; นำเข้า okhttp3.response; นำเข้า okhttp3.responsebody;/*** คลาสแคชของสตริง* สร้างโดย Xiaolei เมื่อ 2017/12/9 */คลาสสาธารณะ cacheinterceptor ใช้ interceptor {บริบทส่วนตัว; โมฆะสาธารณะ setContext (บริบทบริบท) {this.context = บริบท; } cacheInterceptor สาธารณะ (บริบทบริบท) {this.context = บริบท; } @Override สกัดกั้นการตอบสนองสาธารณะ (ห่วงโซ่โซ่) พ่น IOException {คำขอคำขอ = chain.request (); String cachehead = request.header ("cache"); String cache_control = request.header ("cache-control"); if ("true" .equals (cachehead) || // หมายถึงแคช (cache_control! = null &&! cache_control.isempty ())) // นอกจากนี้ยังรองรับส่วนหัวแคชสำหรับโปรโตคอลด้านเว็บ string url = request.url (). url (). toString (); String Responsestr = null; String reqbodystr = getPostParams (คำขอ); ลอง {Response Response = chain.proceed (คำขอ); if (response.issuccessful ()) // การประมวลผลแคชจะดำเนินการหลังจากคำขอเครือข่ายส่งคืนสำเร็จแล้ว มิฉะนั้น 404 จะถูกเก็บไว้ในแคช มันไม่ใช่เรื่องตลกเหรอ? {responsebody responsebody = response.body (); if (responsebody! = null) {responsestr = responsebody.string (); if (respondtr == null) {responsestr = ""; } cacheManager.getInstance (บริบท) .setCache (cachemanager.encryptmd5 (url + reqbodystr), responsestr); // การจัดเก็บแคชใช้ลิงก์ + พารามิเตอร์ไปยังการเข้ารหัส MD5 เป็นบันทึกการจัดเก็บคีย์ "httpretrofit" } return getonlineresponse (การตอบสนอง, responsestr); } else {return chain.proceed (คำขอ); }} catch (exception e) {response response = getCacheresponse (คำขอ, oldNow); // มีข้อยกเว้นเกิดขึ้นฉันเริ่มแคชที่นี่ แต่อาจไม่ถูกแคชดังนั้นฉันต้องโยนมันไปยังรอบถัดไปของการประมวลผลเป็นเวลานานถ้า (ตอบกลับ == null) {return chain.proceed (คำขอ); // ลดลงไปในรอบถัดไปของการประมวลผล }}} else {return chain.proceed (คำขอ); }} การตอบสนองส่วนตัว getCacheresponse (คำขอคำขอ, Long OldNow) {log.i ("httpretrofit", "-> พยายามรับแคช ---------"); string url = request.url (). url (). toString (); String params = getPostParams (คำขอ); String cachestr = cachemanager.getInstance (บริบท) .getCache (cacheManager.encryptmd5 (url + params)); // รับแคชใช้ลิงก์ + พารามิเตอร์ไปยัง MD5 การเข้ารหัสไปยังคีย์เพื่อรับถ้า (cachestr == null) {log.i ( คืนค่า null; } การตอบสนองการตอบสนอง = การตอบกลับใหม่ builder () .Code (200) .body (responsebody.create (null, cachestr)). request (คำขอ) .message ("ตกลง") .protocol (Protocol.http_1_0) .build (); usetime ยาว = system.currenttimeMillis () - oldNow; log.i ("httpretrofit", "<- รับแคช:" + response.code () + "" + response.message () + "" + url + "(" + usetime + "ms)"); log.i ("httpretrofit", cachestr + ""); การตอบกลับกลับ; } การตอบสนองส่วนตัว getOnlineresponse (การตอบสนองการตอบสนอง, สตริงร่างกาย) {responsebody responsebody = response.body (); ส่งคืนการตอบกลับใหม่ builder () .code (response.code ()) .body (responsebody.create (responsebody == null? null: responsebody.contenttype (), body)). } /*** เข้าสู่โหมดโพสต์ พารามิเตอร์ที่ส่งไปยังเซิร์ฟเวอร์ * * @param Request * @return */ สตริงส่วนตัว getPostParams (คำขอคำขอ) {String reqBodystr = ""; String method = request.method (); if ("post" .equals (method)) // ถ้าเป็นโพสต์ให้แยกวิเคราะห์แต่ละพารามิเตอร์ให้มากที่สุดเท่าที่จะเป็นไปได้ {StringBuilder sb = new StringBuilder (); if (request.body () อินสแตนซ์ของ formbody) {formbody body = (formbody) request.body (); if (body! = null) {สำหรับ (int i = 0; i <body.size (); i ++) {sb.append (body.encodedName (i)). ผนวก ("=") ภาคผนวก (body.encodedValue (i)). } sb.delete (sb.length () - 1, sb.length ()); } reqbodystr = sb.toString (); sb.delete (0, sb.length ()); }} ส่งคืน reqbodystr; -ข้างต้นเป็นแนวคิดหลักและรหัสการใช้งานหลัก มาพูดคุยเกี่ยวกับวิธีการใช้งานตอนนี้
วิธีใช้:
Gradle ใช้:
คอมไพล์ 'com.xiaolei: okhttpcacheinterceptor: 1.0.0'
เนื่องจากเพิ่งถูกส่งไปยัง JCenter จึงไม่สามารถดึงมันลงมาได้ (ยังไม่ได้รับการตรวจสอบ) ผู้อ่านที่วิตกกังวลสามารถเพิ่มลิงก์ Maven ของฉันไปยังที่เก็บในโครงการของคุณ: build.gradle:
AllProjects {repositories {maven {url 'https://dl.bintray.com/kavipyouxiang/maven'}}}เราสร้างโครงการใหม่และภาพหน้าจอของโครงการมีดังนี้:
ภาพหน้าจอโครงการ
การสาธิตนั้นง่ายมากหน้าหลักหนึ่งหน้าถั่วหนึ่งชุดติดตั้งเพิ่มเติมหนึ่งอินเตอร์เฟสคำขอเครือข่ายหนึ่งอินเตอร์เฟส
โปรดทราบว่าเนื่องจากเป็นเครือข่ายแคชและที่เกี่ยวข้องจึงไม่ต้องสงสัยเลยว่าเราจำเป็นต้องเพิ่มสิทธิ์การร้องขอเครือข่ายและสิทธิ์การอ่านและเขียนไฟล์ในรายการ:
<Use-Permission Android: name = "Android.permission.Internet" /> <Use-Permission Android: name = "Android.permission.read_external_storage" /> <use-permission Android: name = "Android.permission.write_external_storage
เมื่อใช้งานคุณเพียงแค่ต้องเพิ่ม interceptor ลงใน okhttpClient ของคุณ:
ไคลเอนต์ = ใหม่ okhttpClient.builder () .addinterceptor (ใหม่ cacheinterceptor (บริบท)) // เพิ่ม cache interceptor เพิ่ม cache support.retreonconnectionfailure (จริง) // failed reconnect.connecttimeout (30, timeunit.seconds) //
หากคุณต้องการแคชข้อมูลของอินเทอร์เฟซใดเป็นเวลานานเพิ่มส่วนหัวคำขอสำหรับอินเทอร์เฟซเครือข่ายของคุณ คลาส Cacheheaders.java มีสถานการณ์ทั้งหมด โดยทั่วไปเฉพาะ cacheheaders จำเป็นต้องมีปกติ
อินเตอร์เฟสสาธารณะ net {@headers (cacheheaders.normal) // นี่คือคีย์ @formurlencoded @post ("Geocoding") การโทรสาธารณะ <Databean> getIndex (@field ("A") สตริง A);}รหัสธุรกิจ:
net = retrofitbase.getRetrofit (). สร้าง (net.class); โทร <DataBean> call = net.getIndex ("เมืองซูโจว"); call.enqueue (การโทรกลับใหม่ <Databean> () {@Override โมฆะสาธารณะ onResponse (โทร <Tatabean> การโทรตอบกลับ <Tatabean> การตอบสนอง) {databean data = response.body (); วันที่ = วันที่ใหม่ (); textView.Settext OnFailure (Call <Tatabean> โทร throwable t) {textView.Settext ("คำขอล้มเหลว!")}});หากคำขอเครือข่ายของเราสำเร็จเราจะส่งข้อความบนอินเทอร์เฟซและเพิ่มเวลาปัจจุบัน หากเครือข่ายล้มเหลวผลลัพธ์ของคำขอล้มเหลว
อาจเป็นรหัสนี้รหัสรายละเอียดจะถูกโพสต์ในตอนท้ายของบทความ
ดูเอฟเฟกต์: สาธิต
ที่นี่เราแสดงให้เห็นว่าจากเครือข่ายปกติไปยังเครือข่ายที่ผิดปกติและจากนั้นกลับสู่สถานการณ์ปกติ
ตอนจบ
บทข้างต้นเป็นกระบวนการทั้งหมดจากความคิดรหัสและจากนั้นไปจนถึงการเรนเดอร์ นี่คือที่อยู่ของการสาธิต ถ้าคุณชอบคุณสามารถคลิกเริ่ม
ที่อยู่ตัวอย่าง: https://github.com/xiaolei123/okhttpcacheintor
ข้างต้นเป็นเนื้อหาทั้งหมดของบทความนี้ ฉันหวังว่ามันจะเป็นประโยชน์ต่อการเรียนรู้ของทุกคนและฉันหวังว่าทุกคนจะสนับสนุน wulin.com มากขึ้น