서문 : 얼마 전, 앱을 개발할 때, 나는 종종 사용자의 장치 환경으로 인해 네트워크에서 얻은 데이터를 얻을 수 없었기 때문에 앱에 표시된 결과는 항상 빈 상자입니다. 이 상황은 사용자 경험에 매우 나쁩니다. 따라서 나는 어려운 생각을하고 OKHTTP로 시작하기로 결정했으며 (프로젝트에서 사용한 네트워크 요청 프레임 워크가 OKHTTP이기 때문에) 네트워크 데이터 캐시 인터셉터를 작성했습니다.
좋아, 우리는 글쓰기를 시작하기로 결정했습니다. 내 아이디어에 대해 먼저 이야기하겠습니다.
아이디어
OKHTTP의 강력한 인터셉터 기능을 주로 활용하는 네트워크 데이터 캐시 인터셉터에 대해 글을 쓰고 있기 때문에, 어떤 데이터를 캐시 해야하는지 또는 어떤 상황에서 데이터 캐시 메커니즘을 활성화해야합니까?
첫째 : 공무원이 캐시 인터셉터를 제공했기 때문에 게시물 요청이 지원되지만 한 가지 단점은 Get에 의해 요청 된 데이터 만 캐시 할 수 있지만 게시물을 지원하지 않는다는 것입니다.
두 번째 : 네트워크가 정상인 경우 네트워크에서 데이터를 가져 오는 것입니다. TimeOutexception UnknowHostException 및 기타 문제와 같이 네트워크가 비정상적인 경우 데이터를 캐시하고 검색해야합니다.
셋째 : 캐시에서 가져온 데이터가 비어 있으면이 요청이 나머지 정상 프로세스를 거쳐야합니다.
넷째 : 발신자는 캐싱 메커니즘을 완전히 제어해야하며 비즈니스 요구에 따라 데이터를 캐시할지 선택적으로 결정할 수 있습니다.
다섯 번째 : 사용하기가 간단해야합니다. 이것이 가장 중요한 지점입니다.
좋아, 우리는 위의 5 점을 나열했는데, 이것은 우리의 일반적인 아이디어입니다. 이제 코드 부분에 대해 이야기 해 봅시다.
코드 기사
캐싱 프레임 워크 : 여기에 사용하는 캐시 프레임 워크는 disklrucache https://github.com/jakewharton/disklrucache입니다.이 캐시 프레임 워크는 로컬로 저장 될 수 있으며 Google에서 승인했습니다. 이것이 또한이 프레임 워크를 선택하는 주된 이유입니다. CacheManager 클래스를 캐시 프레임 워크에 캡슐화합니다.
import android.content.context; import android.content.pm.packageinfo; import android.content.pm.packagemanager; import com.xiaolei.okhttpcacheinterceptor.log.log; import java.io.bytearrayoutputstream; import java.io.file; java.io.ioexception; import java.io.outputStream; import java.io.unsupportedencodingException; import java.security.messagegegest; import java.security.nosuchalgorithmexception;/*** Xiaolei가 2017/5/17에 작성했습니다. */public class cachemanager {public static final String tag = "Cachemanager"; // 최대 캐시 크기 10MB 개인 정적 최종 최종 최종 Long Disk_Cache_Size = 1024 * 10; 개인 정적 최종 최종 int disk_cache_index = 0; 개인 정적 최종 문자열 cache_dir = "응답"; 개인 디스크 루카 치 mdisklrucache; 개인 휘발성 정적 캐시 메너 McACHEMANAGER; public static cachemanager getInstance (컨텍스트 컨텍스트) {if (mcachemanager == null) {synchronized (cachemanager.class) {if (mcachemanager == null) {McACheManager = new Cachemanager (컨텍스트); }}} return mcachemanager; } private cachemanager (context context) {file diskCachedir = getDiskCachedir (context, cache_dir); if (! diskCachedir.exists ()) {boolean b = diskCachedir.mkdir (); log.d (tag, "! diskCachedir.exists () --- diskCachedir.mkdir () =" + b); } if (diskCachedir.getUsableSpace ()> disk_cache_size) {try {mdisklrucache = disklrucache.open (diskCachedir, getAppversion (context), 1/*키*/, disk_cache_size에 해당하는 파일 수); log.d (태그, "mdisklrucache를 만들었습니다"); } catch (ioexception e) {e.printstacktrace (); }}} / *** 동기로 설정 캐시* / public void putcache (문자열 키, 문자열 값) {if (mdisklrucache == null) return; OutputStream os = null; try {disklrucache.editor editor = mdisklrucache.edit (encryptmd5 (key)); os = editor.newoutputStream (disk_cache_index); os.write (value.getBytes ()); os.flush (); editor.commit (); mdisklrucache.flush (); } catch (ioexception e) {e.printstacktrace (); } 마침내 {if (os! = null) {try {os.close (); } catch (ioexception e) {e.printstacktrace (); }}}} / *** 캐시* / public void setCache (최종 문자열 키, 최종 문자열 값) {new Thread () {@override public void run () {putcache (key, value); } }.시작(); } /*** 동기식 getCache getCache (String key) {if (mdisklrucache == null) {return null; } fileInputStream fis = null; BYTEARRAYOUTPUTSTREAM BOS = NULL; try {disklrucache.snapshot snapshot = mdisklrucache.get (encryptmd5 (key)); if (snapshot! = null) {fis = (fileInputStream) snapshot.getInputStream (disk_cache_index); BOS = 새로운 BYTEARRAYOUTPUTSTREAM (); 바이트 [] buf = 새로운 바이트 [1024]; int len; while ((len = fis.read (buf))! = -1) {bos.write (buf, 0, len); } byte [] data = bos.tobytearray (); 새 문자열 (데이터)을 반환합니다. }} catch (ioexception e) {e.printstacktrace (); } 마침내 {if (fis! = null) {try {fis.close (); } catch (ioexception e) {e.printstacktrace (); }} if (bos! = null) {try {bos.close (); } catch (ioexception e) {e.printstacktrace (); }}} return null; } / *** 비동기 적으로 getCache* / public void getCache (최종 문자열 키, 최종 CacheCallback 콜백) {new Thread () {@override public void run () {String Cache = getCache (key); Callback.ongetCache (캐시); } }.시작(); } / *** 캐시 제거* / public boolean removecache (string key) {if (mdisklrucache! = null) {try {return mdisklrucache.remove (encryptmd5 (key)); } catch (ioexception e) {e.printstacktrace (); }} 거짓을 반환합니다. } / *** 캐시 디렉토리 가져 오기* / 개인 파일 getDiskCachedir (컨텍스트 컨텍스트, 문자열 고유 이름) {String cachepath = context.getCachedir (). getPath (); 새 파일을 반환합니다 (CachePath + file.separator + eliquename); } / *** 문자열의 md5 인코딩* / public static string encryptmd5 (문자열 문자열) {try {byte [] hash = messageDigest.getInstance ( "md5"). digest (string.getBytes ( "utf-8")); StringBuilder hex = New StringBuilder (hash.length * 2); for (byte b : hash) {if ((b & 0xff) <0x10) {hex.append ( "0"); } hex.append (Integer.toHexString (b & 0xff)); } return hex.tostring (); } catch (nosuchalgorithmexception | UnsupportedEncodingException e) {e.printstacktrace (); } return string; } / *** 앱 버전 번호 가져 오기* / private int getAppversion (Context Context) {packagemanager pm = context.getPackagemanager (); try {packageInfo pi = pm.getPackageInfo (context.getPackagename (), 0); 반환 pi == null? 0 : pi.versioncode; } catch (packagemanager.namenotfoundException e) {e.printstacktrace (); } 반환 0; }} 캐시 인터셉터 인터셉터 : OKHTTP의 인터셉터 인터셉터 메커니즘을 사용하여 캐시 시나리오 및 네트워크 조건을 지능적으로 판단하고 다양한 시나리오를 처리합니다.
import android.content.context; import com.xiaolei.okhttpcacheinterceptor.catch.cachemanager; import com.xiaolei.okhttpcacheinterceptor.log.log; import java.io.ioexception; import okhttp3. formbody; import okhttp3.interceptor; intercector; okhttp3.request; import okhttp3.response; import okhttp3.responsebody;/*** Cache Class of String* Xiaolei가 2017/12/9에 생성 한 문자열 클래스*. */공개 클래스 캐시 인터셉터는 인터셉터 {개인 컨텍스트 컨텍스트; public void setcontext (컨텍스트 컨텍스트) {this.context = 컨텍스트; } public cacheinterceptor (컨텍스트 컨텍스트) {this.context = 컨텍스트; } @override public response intercept (체인 체인) IoException {request request = chain.request (); 문자열 cachehead = request.header ( "캐시"); 문자열 cache_control = request.header ( "Cache-Control"); if ( "true".equals (cachehead) || // 캐시를 의미합니다 (cache_control! = null &&! cache_control.isempty ()))) // 웹 측 프로토콜의 캐시 헤더 {long oldnow = system.currenttimemillis (); 문자열 url = request.url (). url (). toString (); 문자열 response resport = null; 문자열 reqbodystr = getPostParams (요청); try {응답 응답 = chain.proceed (요청); if (response.issuccessful ()) // 캐시 처리는 네트워크 요청이 성공적으로 반환 된 후에 만 수행됩니다. 그렇지 않으면 404가 캐시에 저장됩니다. 농담이 아닌가요? {responsebody responsebody = response.body (); if (responsebody! = null) {responseStr = responsebody.string (); if (racktr == null) {responseStr = ""; } cachemanager.getInstance (context) .setCache (cachemanager.encryptmd5 (url + reqbodystr), responseStrest); // 캐시 저장, 링크 + 매개 변수를 MD5 인코딩으로 키 스토리지 로그로 사용합니다. } let } else {return Chain.ProCeed (요청); }} catch (예외 e) {응답 응답 = getCacherEsponse (요청, OldNow); // 예외가 발생했지만 여기에서 캐시를 시작했지만 캐시되지 않을 수도 있으므로 다음 프로세싱 라운드에 오랫동안 던져야합니다. (응답 == NULL) {return Chain.Proceed (요청); // 다음 처리 라운드로 드롭} else {return response; }}} else {return Chain.proceed (요청); }} 개인 응답 getCacherEsponse (요청 요청, Long OldNow) {log.i ( "httpretrofit", "-> 캐시를 얻으려고합니다 ---------"); 문자열 url = request.url (). url (). toString (); 문자열 params = getPostParams (요청); 문자열 cachest = cachemanager.getInstance (context) .getCache (cachemanager.encryptmd5 (url + params)); // 캐시 받기, 링크 + 매개 변수를 md5 인코딩에 링크 + 매개 변수를 사용하여 if (cachest == null) {log.i ( "httpretrofit", "-캐시 실패 ------------------------------------------------------------------------------------ 널 리턴; } 응답 응답 = new response.Builder () .code (200) .body (responsebody.create (null, cachest)) .request (request (request)) .message ( "ok") .protocol (protocol.http_1_0) .build (); long usetime = system.currenttimeMillis () - OldNow; log.i ( "httpretrofit", "<- 캐시를 얻습니다 :" + respons.code () + "" + response.message () + "" + url + "(" + usetime + "ms)"); log.i ( "httpretrofit", cachest + ""); 반환 응답; } 개인 응답 getOnLinerEsponse (응답 응답, 문자열 본문) {응답 body responsebody = response.body (); 새 응답을 반환합니다 .Builder () .code (response.code ()) .body (responsebody.create (response == null? null : null : response.contentType (), body)) .request (response.request ()) .message (response.message ()) .Protocol () .Build (); } /*** 포스트 모드에 들어갑니다. 서버로 전송 된 매개 변수 * * @param request * @return */ private String getPostParams (요청 요청) {String reqBodyStr = ""; 문자열 메소드 = request.method (); if ( "post".equals (method)) // post 인 경우 각 매개 변수를 가능한 한 많이 구문 분석하십시오 {StringBuilder sb = new StringBuilder (); if (request.body () formbody) {formbody body = (formbody) requess.body (); if (body! = null) {for (int i = 0; i <body.size (); i ++) {sb.append (body.encodedName (i)). Append ( "="). } sb.delete (sb.length () -1, sb.length ()); } reqbodystr = sb.toString (); sb.delete (0, sb.length ()); }} return reqBodyStr; }}위의 주요 아이디어 및 주요 구현 코드입니다. 지금 사용법에 대해 이야기합시다
사용 방법 :
Gradle 사용 :
'com.xiaolei : alkhttpcacheinterceptor : 1.0.0'컴파일
방금 Jcenter에 제출되었으므로 철수하지 않을 수 있습니다 (아직 검토되지 않았습니다). 불안한 독자는 프로젝트의 저장소에 Maven 링크를 추가 할 수 있습니다 : build.gradle :
AllProjects {repositories {maven {url 'https://dl.bintray.com/kavipyouxiang/maven'}}}우리는 새로운 프로젝트를 만들고 프로젝트 스크린 샷은 다음과 같습니다.
프로젝트 스크린 샷
데모는 매우 간단하고 하나의 메인 페이지, 하나의 콩, 1 개의 로트, 하나의 네트워크 요청 인터페이스입니다.
네트워크, 캐시 및 관련이므로 네트워크 요청 권한을 추가하고 매니페스트에서 읽기 및 쓰기 권한을 추가해야한다는 것은 의심의 여지가 없습니다.
<use-permission android : name = "android.permission.internet" /> <use-permission android : "android.permission.read_external_storage" /> <use-permission android : "android.permission.write_external_storage" />
그것을 사용할 때는 alkhttpclient에 인터셉터를 추가하면됩니다.
client = new Okhttpclient.Builder () .addinterceptor (new Cacheinterceptor (Context)) // 캐시 인터셉터 추가, 캐시 지원을 추가합니다. RetryOnConnectionFailure (true) // 실패한 retonnect.connectTimeout (30, TimeUnit.seconds) // 네트워크 요청 단위는 Seconds.Build ();
어떤 인터페이스의 데이터를 캐시하려면 오랫동안 네트워크 인터페이스에 대한 요청 헤더를 추가하십시오. CACHEHEADERS.JAVA 클래스에는 모든 상황이 포함되어 있습니다. 일반적으로 캐시 헤더만이 필요합니다.
public interface net {@Headers (CacheHeaders.normal) // key @formurlencoded @post ( "geocoding") public call <databean> getIndex (@field ( "a") string;}입니다.비즈니스 코드 :
net net = retrofitbase.getRetrofit (). create (net.class); 전화 <databean> call = net.getIndex ( "Suzhou City"); Call.Enqueue (new Callback <databean> () {@override public void onresponse (call <databean> call, responsk <databean> responsk) {databean data = response.body (); date date = new date (); textView.settext (date.getMinutes () + "" + date.seconds () + " +" + " +" + " +" + " +" + " onfailure (hall <databean> call, trashable t) {textview.settext ( "요청 실패!");네트워크 요청이 성공하면 인터페이스에서 텍스트를 출력하고 현재 시간을 추가합니다. 네트워크가 실패하면 요청의 출력이 실패합니다.
아마도 이것은 코드 일 것입니다. 자세한 코드는 기사 끝에 게시됩니다.
효과를보십시오 : 데모
여기서 우리는 정상 네트워크에서 비정상 네트워크로, 정상적인 상황으로 돌아온다는 것을 보여줍니다.
종결
위의 장은 아이디어, 코드, 렌더링에 이르는 전체 프로세스입니다. 다음은 데모의 주소입니다. 마음에 들면 시작을 클릭 할 수 있습니다.
데모 주소 : https://github.com/xiaolei123/okhttpcacheinterceptor
위는이 기사의 모든 내용입니다. 모든 사람의 학습에 도움이되기를 바랍니다. 모든 사람이 wulin.com을 더 지원하기를 바랍니다.