[안드로이드] 웹뷰(Webview)에서 캐시 컨트롤(feat. 웹 버전)
서론
프로젝트에서 웹뷰를 활용하여 네이티브 앱을 제작하고 있다. 그 중 안드로이드를 주로 담당하고 있는데, 안드로이드 네이티브 앱은 업데이트 없이 웹 상에서 업데이트가 일어날 경우, 기존에 남아있던 캐시들로 인해 업데이트 반영이 안되어 오류가 발생하는 문제가 있었다. 이를 해결하기 위해 백엔드 상에서는 웹 업데이트 시 버전을 올리고 이를 API로 호출하여 확인할 수 있도록 작업하고 앱에서 별도의 캐시 컨트롤을 해야했다.
본론
목표는 다음과 같다.
웹뷰를 로드하기 전에 기존 앱이 가지고 있던 웹 버전과 로드될 웹 버전을 비교한다. 필요하면 캐시를 삭제한다.
이 목표를 달성하기 위해서는 다음과 같은 작업을 진행해야 한다.
- 최초 실행 시 웹 버전을 SharedPreference에 저장한다.
- 실행할 때마다 SharedPreference에 저장된 웹 버전과 로드하는 웹 버전을 비교한다.(백엔드 단에서 별도의 API 제공)
- 기존 버전보다 로드되는 웹 버전이 높으면 캐시를 삭제해준다.
- 앱이 백그라운드에 있다가 포그라운드로 실행될 때도 2, 3번 작업을 진행해준다.
1번, 2번, 3번 같은 경우 아래 코드로 구현했다.
public class MainActivity extends AppCompatActivity {
// ...
// 캐시, 쿠키, 데이터베이스 관련 - CookieManager
CookieManager cookieManager = CookieManager.getInstance();
// SharedPreferences 선언
private SharedPreferences sharedPreferences;
// 새로운 버전 담을 변수 선언
private String version;
@Override
protected void onCreate(Bundle savedInstanceState){
// ...
sharedPreferences = getSharedPreferences("version", MODE_PRIVATE);
// 웹 버전 확인
try {
checkVersionWhenOnCreate();
} catch (IOException e) {
Log.d(TAG, "checkVersion Exception!!");
webView.loadUrl(BASE_URL);
}
// ...
}
// 웹 버전 확인
public void checkVersionWhenOnCreate() throws IOException {
Observable.fromCallable(() -> {
HttpURLConnection conn;
URL url = new URL(GET_VERSION_API);
// url 연결
conn = (HttpURLConnection) url.openConnection();
// 서버 접속시 연결 시간
conn.setConnectTimeout(10000);
// Read시 연결 시간
conn.setReadTimeout(100000);
// 요청방식 선택
conn.setRequestMethod(GET_METHOD);
// GET 요청 설정 (true 시 POST 요청)
conn.setDoOutput(false);
conn.setDoInput(true);
InputStream inputStream = conn.getInputStream();
StringBuilder stringBuilder = new StringBuilder();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));
// response에 응답 저장
String response;
while ((response = bufferedReader.readLine()) != null) {
stringBuilder.append(response + "\n");
}
response = stringBuilder.toString();
// 접속해지
conn.disconnect();
version = response.get("version");
return response;
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<String>() {
@Override
public void onSubscribe(Disposable d) {
// 구독 초기화
}
@Override
public void onNext(String url) {
Log.d(TAG, "------------------[START]------------------");
// 최초 실행인지 확인
if (sharedPreferences.contains("version")) {
// 최초 실행이 아닐 때, 저장된 버전 가져오기
String oldVersion = sharedPreferences.getString("version", "");
Log.d(TAG, "oldVersion: " + oldVersion);
Log.d(TAG, "newVersion: " + version);
// 웹 버전 비교
if (compareWebVersion(oldVersion, version)) {
// 캐시 컨트롤
clearCache(webView);
// 업데이트된 버전 저장
updateVersion(version);
String tmp = sharedPreferences.getString("version", "");
Log.d(TAG, "NEW VERSION: " + tmp);
}
} else {
// 최초 실행이 아닐 때
Log.d(TAG, "newVersion: " + version);
// 버전 저장
updateVersion(version);
}
Log.d(TAG, "------------------[END]------------------");
// 버전 확인 및 캐시 컨트롤 다하고 웹뷰 로드
webView.loadUrl(BASE_URL);
}
});
// 캐시 컨트롤
public void clearCache(WebView webView) {
// 캐시 삭제
Log.d(TAG, "Start clearCache");
try {
webView.clearCache(true);
webView.clearHistory();
webView.clearView();
} catch (Exception e) {
}
// 세션 쿠키 삭제
Log.d(TAG, "Start removeSessionCookies");
try {
cookieManager.removeSessionCookies(new ValueCallback<Boolean>() {
@Override
public void onReceiveValue(Boolean aBoolean) {
}
});
cookieManager.removeAllCookies(new ValueCallback() {
@Override
public void onReceiveValue(Object value) {
}
});
cookieManager.getInstance().flush();
} catch (Exception e) {
}
// 데이터베이스 삭제
Log.d(TAG, "Start deleteDatabase");
try {
MainActivity.this.deleteDatabase("webView.db");
MainActivity.this.deleteDatabase("webViewCache.db");
} catch (Exception e) {
}
}
// 웹 버전 비교(예를 들어 oldVersion: 1.0.2 newVersion: 1.0.3로 설정)
public boolean compareWebVersion(String oldVersion, String newVersion) {
String oldVersionArr[] = oldVersion.split("\\.");
String newVersionArr[] = newVersion.split("\\.");
if (Integer.parseInt(oldVersionArr[0]) < Integer.parseInt(newVersionArr[0])) {
return true;
}
if (Integer.parseInt(oldVersionArr[1]) < Integer.parseInt(newVersionArr[1])) {
return true;
}
if (Integer.parseInt(oldVersionArr[2]) < Integer.parseInt(newVersionArr[2])) {
return true;
}
return false;
}
// 업데이트 된 버전 저장
public void updateVersion(String version) {
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putString("version", version);
editor.commit();
}
}
4번 같은 경우 같은 메소드를 onRestart에서 실행했었으나, 웹뷰에서 갤러리를 여는 등 네이티브 기능을 통해 앱이 백그라운드에서 포그라운드로 오는 경우에도 웹뷰를 다시 로드하여 앱을 정상적으로 동작시키지 못했다. 이를 해결하기 위해서 checkVersionWhenOnRestart()라는 새로운 메소드를 작성해서 버전 업데이트가 필요할 때만 웹뷰를 다시 로드하고 아닐 경우 정상적으로 동작할 수 있도록 하였다. 또한, 버전을 확인할 때 오류가 발생하면 웹뷰를 다시 로드한다.
@Override
protected void onRestart() {
super.onRestart();
Log.d(TAG, "------------------[onRestart START]------------------");
try {
checkVersionWhenOnRestart();
} catch (IOException e) {
webView.loadUrl(BASE_URL);
}
Log.d(TAG, "------------------[onRestart END]------------------");
}
결론
가장 문제가 되었던 것은 onCreate()에서 웹 버전을 체크한 후에 웹뷰를 로드해야 하는데 Observable.fromCallable()이 비동기적으로 실행되었던 것이다. 이는 Observable에 대한 충분한 이해가 없었던 게 원인이었다. onNext()를 @Override하여 해결할 수 있었다.