Java tutorial
/* * Copyright (C) 2013 YROM.NET * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package tv.acfun.a63; import java.io.File; import java.io.FileOutputStream; import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.SocketTimeoutException; import java.net.URL; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; import org.jsoup.select.Elements; import tv.acfun.a63.api.ArticleApi; import tv.acfun.a63.api.Constants; import tv.acfun.a63.api.entity.Article; import tv.acfun.a63.api.entity.Article.SubContent; import tv.acfun.a63.base.BaseWebViewActivity; import tv.acfun.a63.db.DB; import tv.acfun.a63.service.KeepOnlineService; import tv.acfun.a63.util.ActionBarUtil; import tv.acfun.a63.util.Connectivity; import tv.acfun.a63.util.CustomUARequest; import tv.acfun.a63.util.FileUtil; import tv.acfun.a63.util.Theme; import android.annotation.TargetApi; import android.app.AlertDialog; import android.content.ActivityNotFoundException; import android.content.ComponentName; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.graphics.Bitmap; import android.net.Uri; import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; import android.support.v4.view.MenuItemCompat; import android.support.v7.widget.ShareActionProvider; import android.text.TextUtils; import android.util.Log; import android.view.Menu; import android.view.MenuItem; import android.webkit.WebChromeClient; import android.webkit.WebSettings.LayoutAlgorithm; import android.webkit.WebSettings.TextSize; import android.webkit.WebView; import android.webkit.WebViewClient; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.util.IOUtils; import com.android.volley.Cache.Entry; import com.android.volley.NetworkResponse; import com.android.volley.ParseError; import com.android.volley.Request; import com.android.volley.Response; import com.android.volley.Response.ErrorListener; import com.android.volley.Response.Listener; import com.android.volley.ServerError; import com.android.volley.VolleyError; import com.android.volley.toolbox.HttpHeaderParser; import com.umeng.analytics.MobclickAgent; /** * {@code <div id="title"> <h1 class="article-title"></h1> * <div id="info" class="article-info"> <span class="article-publisher"><i class="icon-slash"></i></span> <span class="article-pubdate"></span> <span class="article-category"></span> </div> </div> <section id="content" class="article-body"></section> } * * @author Yrom * */ @SuppressWarnings("deprecation") public class ArticleActivity extends BaseWebViewActivity implements Listener<Article>, ErrorListener { private static final Pattern sAreg = Pattern.compile("/a/ac(\\d{5,})"); private static final Pattern sVreg = Pattern.compile("/v/ac(\\d{5,})"); private static final Pattern sLiteAreg = Pattern.compile("/v/#ac=(\\d{5,});type=article"); private static final Pattern sLiteVreg = Pattern.compile("/v/#ac=(\\d{5,})$"); private static final String sAppReg = "^http://www.acfun.(com|tv)/app/?$"; public static final int MAX_AGE = 7 * 24 * 60 * 60 * 1000; private static String ARTICLE_PATH; private static final String NAME_ARTICLE_HTML = "a63-article.html"; public static void start(Context context, int aid, String title) { Intent intent = new Intent(context, ArticleActivity.class); intent.putExtra("aid", aid); intent.putExtra("title", title); context.startActivity(intent); } public static void start(Context context, String url) { Intent intent = new Intent(context, ArticleActivity.class); intent.setData(Uri.parse(url)); intent.setAction(Intent.ACTION_VIEW); intent.putExtra("webmode", true); context.startActivity(intent); } private Request<?> request; private Document mDoc; private List<String> imgUrls; private DownloadImageTask mDownloadTask; private String title; private boolean isDownloaded; private boolean isWebMode; private DB db; @TargetApi(Build.VERSION_CODES.HONEYCOMB) @Override protected void initView(Bundle savedInstanceState) { ARTICLE_PATH = AcApp.getExternalCacheDir("article").getAbsolutePath(); Uri data = getIntent().getData(); if (Intent.ACTION_VIEW.equalsIgnoreCase(getIntent().getAction()) && data != null) { String scheme = data.getScheme(); if (scheme.equals("ac")) { // ac://ac000000 aid = Integer.parseInt(getIntent().getDataString().substring(7)); } else if (scheme.equals("http")) { // http://www.acfun.tv/v/ac123456 Matcher matcher; String path = data.getPath(); if (path == null) { finish(); return; } if ((matcher = sVreg.matcher(path)).find() || (matcher = sAreg.matcher(path)).find()) { aid = Integer.parseInt(matcher.group(1)); } } if (aid != 0) title = "ac" + aid; isWebMode = getIntent().getBooleanExtra("webmode", false) && aid == 0; } else { aid = getIntent().getIntExtra("aid", 0); title = getIntent().getStringExtra("title"); } if (!isWebMode) { if (aid == 0) throw new IllegalArgumentException(" id"); getSupportActionBar().setTitle("ac" + aid); MobclickAgent.onEvent(this, "view_article"); db = new DB(this); isFaved = db.isFav(aid); } mWeb.getSettings().setAppCachePath(ARTICLE_PATH); mWeb.addJavascriptInterface(new ACJSObject(), "AC"); // Set a chrome client to handle the MediaResource on web page // like video,video loading progress, etc. mWeb.setWebChromeClient(new WebChromeClient() { @Override public void onReceivedTitle(WebView view, String title) { setTitle(title); } }); mWeb.setWebViewClient(new WebViewClient() { @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { Matcher matcher = sAreg.matcher(url); Intent intent = new Intent(Intent.ACTION_VIEW); if (matcher.find() || (matcher = sLiteAreg.matcher(url)).find() || (matcher = sVreg.matcher(url)).find() || (matcher = sLiteVreg.matcher(url)).find()) { String acId = matcher.group(1); try { intent.setData(Uri.parse("ac://ac" + acId)); startActivity(intent); return true; } catch (Exception e) { // nothing } } else if (Pattern.matches(sAppReg, url)) { String appLink = getString(R.string.app_ac_video_link); try { intent.setData(Uri.parse(appLink)); startActivity(intent); return true; } catch (Exception e) { view.loadUrl(appLink); return true; } } if (!isWebMode) { start(ArticleActivity.this, url); return true; } else { Uri uri = Uri.parse(url); if (uri.getHost() != null && !uri.getHost().contains("acfun")) { try { intent.setData(uri); startActivity(intent); return true; } catch (ActivityNotFoundException ignored) { } } } return false; } @Override public void onPageStarted(WebView view, String url, Bitmap favicon) { setSupportProgressBarIndeterminateVisibility(true); } @Override public void onPageFinished(WebView view, String url) { setSupportProgressBarIndeterminateVisibility(false); if (isWebMode || imgUrls == null || imgUrls.isEmpty() || url.startsWith("file:///android_asset") || AcApp.getViewMode() == Constants.MODE_NO_PIC) // ? return; // Log.d(TAG, "on finished:" + url); if ((url.equals(getBaseUrl()) || url.contains(NAME_ARTICLE_HTML)) && imgUrls.size() > 0 && !isDownloaded) { String[] arr = new String[imgUrls.size()]; mDownloadTask = new DownloadImageTask(); mDownloadTask.execute(imgUrls.toArray(arr)); } } }); mWeb.getSettings().setSupportZoom(true); mWeb.getSettings().setBuiltInZoomControls(true); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) mWeb.getSettings().setDisplayZoomControls(false); setTextZoom(AcApp.getConfig().getInt("text_size", 0)); } @Override public void onBackPressed() { if (mWeb.canGoBack()) { mWeb.goBack(); } else { super.onBackPressed(); } } protected String getBaseUrl() { return ArticleApi.getDomainRoot(getApplicationContext()); } @Override public boolean onCreateOptionsMenu(Menu menu) { if (AcApp.getViewMode() != Constants.MODE_COMMIC && !isWebMode) { getMenuInflater().inflate(R.menu.article_options_menu, menu); MenuItem actionItem = menu.findItem(R.id.menu_item_share_action_provider_action_bar); if (ActionBarUtil.hasSB()) { MenuItemCompat.setShowAsAction(actionItem, MenuItemCompat.SHOW_AS_ACTION_NEVER); } ShareActionProvider actionProvider = (ShareActionProvider) MenuItemCompat.getActionProvider(actionItem); actionProvider.setShareHistoryFileName(ShareActionProvider.DEFAULT_SHARE_HISTORY_FILE_NAME); actionProvider.setShareIntent(createShareIntent()); if (isFaved) { MenuItem fav = menu.findItem(R.id.menu_item_fav_action); fav.setIcon(R.drawable.rating_favorite_p); fav.setTitle("??"); } MenuItem item = menu.add(0, android.R.id.button1, 0, R.string.font_size) .setIcon(R.drawable.ic_text_size); MenuItemCompat.setShowAsAction(item, MenuItemCompat.SHOW_AS_ACTION_IF_ROOM); } return super.onCreateOptionsMenu(menu); } private Intent createShareIntent() { String shareurl = title + " http://" + ArticleApi.getDomainRoot(getApplicationContext()) + "/a/ac" + aid; Intent shareIntent = new Intent(Intent.ACTION_SEND); shareurl += " #Acfun# http://t.cn/8kLMite"; shareIntent.setType("text/plain"); shareIntent.putExtra(Intent.EXTRA_SUBJECT, ""); shareIntent.putExtra(Intent.EXTRA_TEXT, shareurl); return shareIntent; } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.menu_item_comment: if (mArticle != null) { CommentsActivity.start(ArticleActivity.this, mArticle.id); } return true; case R.id.menu_item_fav_action: if (isFaved) { db.deleteFav(aid); AcApp.showToast("??"); isFaved = false; item.setIcon(R.drawable.rating_favorite); } else { if (mArticle != null) { db.addFav(mArticle); isFaved = true; item.setIcon(R.drawable.rating_favorite_p); AcApp.showToast("??"); } } return true; case android.R.id.button1: if (mSizeChooser == null) { final int checked = AcApp.getConfig().getInt("text_size", 0); mSizeChooser = new AlertDialog.Builder(this).setCancelable(true).setTitle(R.string.font_size) .setSingleChoiceItems(R.array.title_sizes, checked, new DialogInterface.OnClickListener() { int lastSelected = checked; @Override public void onClick(DialogInterface dialog, int which) { if (lastSelected != which) { AcApp.putInt("text_size", which); setTextZoom(which); dialog.dismiss(); lastSelected = which; } } }).create(); } mSizeChooser.show(); return true; } return super.onOptionsItemSelected(item); } void setTextZoom(int level) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) { if (level > 4) level = 3; mWeb.getSettings().setTextSize(TextSize.values()[level + 1]); } else mWeb.getSettings().setTextZoom(100 + level * 25); } protected void initData() { super.initData(); if (isWebMode) { Uri uri = getIntent().getData(); // TODO: sync cookies to WebView String url = uri.toString(); mWeb.loadUrl(url); return; } request = new ArticleRequest(getApplicationContext(), aid, this, this); request.setTag(TAG); request.setShouldCache(true); Entry entry = AcApp.getGloableQueue().getCache().get(request.getCacheKey()); if (entry != null && entry.data != null && entry.isExpired()) { try { String json = new String(entry.data, "utf-8"); JSONObject articleJson = JSON.parseObject(json).getJSONObject("data").getJSONObject("fullArticle"); onResponse(Article.newArticle(articleJson)); return; } catch (Exception e) { e.printStackTrace(); } } AcApp.addRequest(request); } private String buildTitle(Article article) { StringBuilder builder = new StringBuilder(); builder.append("<h1 class=\"article-title\">").append(article.title).append("</h1>") .append("<div id=\"info\" class=\"article-info\">").append("<img src=\"") .append(TextUtils.isEmpty(article.poster.avatar) ? "file:///android_asset/wen.png" : article.poster.avatar) .append("\" >").append("<span class=\"article-publisher\">").append("<a href=\"http://") .append(ArticleApi.getDomainRoot(getApplicationContext())).append("/member/user.aspx?uid=") .append(article.poster.id).append("\" >").append(article.poster.name).append("</a>") .append("</span>") // .append("<span class=\"article-pubdate\">") // .append(AcApp.getPubDate(article.postTime)) // .append("?</span>") // .append("<span class=\"article-category\">") // .append(article.channelName) // .append("</span>") .append("</div>"); return builder.toString(); } private static final String TAG = "Article"; private Article mArticle; private AlertDialog mSizeChooser; static class ArticleRequest extends CustomUARequest<Article> { public ArticleRequest(Context context, int aid, Listener<Article> listener, ErrorListener errListener) { super(ArticleApi.getContentUrl(context, aid), Article.class, listener, errListener); } @Override protected Response<Article> parseNetworkResponse(NetworkResponse response) { try { String json = new String(response.data, HttpHeaderParser.parseCharset(response.headers)); JSONObject rsp = JSON.parseObject(json); int status = rsp.getIntValue("status"); if (status != 200) { throw new Article.InvalidArticleError(); } JSONObject articleJson = rsp.getJSONObject("data").getJSONObject("fullArticle"); if (articleJson == null) return Response.error(new Article.InvalidArticleError()); return Response.success(Article.newArticle(articleJson), Connectivity.newCache(response, MAX_AGE)); } catch (Article.InvalidArticleError e) { Log.w(TAG, "Invalid Article! Need to redirect intent"); return Response.error(e); } catch (Exception e) { Log.e(TAG, "parse article error", e); return Response.error(new ParseError(e)); } } } @Override protected void onResume() { super.onResume(); if (isWebMode // no images || isDocBuilding.get() || imageCaches == null // building doc // on request data || mArticle == null || imgUrls == null || imgUrls.isEmpty() || AcApp.getViewMode() == Constants.MODE_NO_PIC) return; if (!isDownloaded && imgUrls.size() > 0) { String[] arr = new String[imgUrls.size()]; mDownloadTask = new DownloadImageTask(); mDownloadTask.execute(imgUrls.toArray(arr)); } } @Override protected void onStop() { super.onStop(); if (mDownloadTask != null && !isDownloaded) { mDownloadTask.cancel(false); mDownloadTask = null; } } @Override protected void onDestroy() { super.onDestroy(); AcApp.cancelAllRequest(TAG); } @Override public void onResponse(Article response) { mArticle = response; imgUrls = response.imgUrls; KeepOnlineService.requestOnline(getApplicationContext(), aid); if (AcApp.getViewMode() == Constants.MODE_COMMIC && imgUrls != null && !imgUrls.isEmpty()) { ImagePagerActivity.startNetworkImage(this, (ArrayList<String>) imgUrls, 0, aid, title); finish(); } else new BuildDocTask().execute(mArticle); } @Override public void onErrorResponse(VolleyError error) { setSupportProgressBarIndeterminateVisibility(false); if (error instanceof Article.InvalidArticleError) { Intent intent = new Intent(Intent.ACTION_VIEW); intent.addCategory(Intent.CATEGORY_DEFAULT); intent.setData(Uri.parse("av://ac" + aid)); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); try { startActivity(intent); finish(); } catch (Exception e) { //http://www.acfun.tv/lite/v/#ac=1317054 mWeb.loadUrl("http://" + ArticleApi.getDomainRoot(this) + "/lite/v/#ac=" + aid); } } else if (error instanceof ServerError) { mWeb.loadUrl("http://" + ArticleApi.getDomainRoot(this) + "/lite/v/#ac=" + aid); } else { showErrorDialog(); } } List<File> imageCaches; private int aid; private boolean isFaved; private AtomicBoolean isDocBuilding = new AtomicBoolean(false); @TargetApi(Build.VERSION_CODES.KITKAT) private class BuildDocTask extends AsyncTask<Article, Void, Boolean> { boolean hasUseMap; private File cacheFile; @Override protected void onPreExecute() { isDocBuilding.set(true); cacheFile = new File(ARTICLE_PATH, NAME_ARTICLE_HTML); } @Override protected Boolean doInBackground(Article... params) { try { mDoc = Theme.getThemedDoc(ArticleActivity.this); initCaches(); Element title = mDoc.getElementById("title"); title.html(buildTitle(params[0])); Element content = mDoc.getElementById("content"); content.empty(); ArrayList<SubContent> contents = params[0].contents; if (contents.size() > 1) { content.appendElement("div").attr("id", "artcle-pager").html(buildParts(contents)); } for (int i = 0; i < contents.size(); i++) { SubContent sub = contents.get(i); handleSubContent(i, content, sub, params[0]); } FileWriter writer = null; try { writer = new FileWriter(cacheFile); writer.write(mDoc.outerHtml()); content.empty(); // release } catch (IOException e) { cacheFile.delete(); } finally { IOUtils.close(writer); } } catch (IOException e) { return false; } return true; } private String buildParts(ArrayList<SubContent> contents) { StringBuilder builder = new StringBuilder(); for (int i = 0; i < contents.size(); i++) { builder.append("<li><a class=\"pager\" href=\"#p").append(i).append("\" title=\"Part ") .append(i + 1).append("\">").append(contents.get(i).subTitle).append("</a></li>"); } builder.append("<hr>"); return builder.toString(); } private void handleSubContent(int p, Element content, SubContent sub, Article article) { if (!article.title.equals(sub.subTitle)) { content.append("<h2 class=\"article-subtitle\"><a class=\"anchor\" name=\"p" + p + "\"></a>Part " + (p + 1) + ". " + sub.subTitle + "</h2>"); } String data = sub.content.replaceAll("background-color:[^;\"]+;?", "") .replaceAll("font-family:[^;\"]+;?", ""); content.append(data).appendElement("hr"); handleImages(content); handleStyles(content); } private void handleStyles(Element content) { Elements es = content.getAllElements(); for (int i = 0; i < es.size(); i++) { Element e = es.get(i); // if("span".equals(e.nodeName())){ // continue; // } e.removeAttr("style"); } } private void handleImages(Element content) { Elements imgs = content.select("img"); if (imgs.hasAttr("usemap")) { hasUseMap = true; } for (int imgIndex = 0; imgIndex < imgs.size(); imgIndex++) { Element img = imgs.get(imgIndex); String src = img.attr("src").trim(); if (TextUtils.isEmpty(src)) continue; Uri parsedUri = Uri.parse(src); if ("file".equals(parsedUri.getScheme())) continue; if (parsedUri.getPath() == null) // wtf! continue; if (!"http".equals(parsedUri.getScheme())) { parsedUri = parsedUri.buildUpon().scheme("http") .authority(ArticleApi.getDomainRoot(getApplicationContext())).build(); } // url may have encoded path parsedUri = parsedUri.buildUpon().path(parsedUri.getPath()).build(); src = parsedUri.toString(); File cache = FileUtil.generateImageCacheFile(src); imageCaches.add(cache); imgUrls.add(src); img.attr("org", src); String localUri = FileUtil.getLocalFileUri(cache).toString(); if (AcApp.getViewMode() != Constants.MODE_NO_PIC) img.attr("src", "file:///android_asset/loading.gif"); else { // ? // TODO ? img.after("<p >[]</p>"); img.remove(); continue; } img.attr("loc", localUri); // style img.removeAttr("style"); // img if (!hasUseMap) { addClick(img, src); img.removeAttr("width"); img.removeAttr("height"); } } } private void initCaches() { if (imgUrls != null) imgUrls.clear(); else imgUrls = new ArrayList<>(); if (imageCaches != null) imageCaches.clear(); else imageCaches = new ArrayList<>(); } private void addClick(Element img, String src) { try { if ("icon".equals(img.attr("class")) || Integer.parseInt(img.attr("width")) < 100 || Integer.parseInt(img.attr("height")) < 100) { return; } } catch (Exception ignored) { } if (src.contains("emotion/images/")) return; // url if (img.parent() != null && img.parent().tagName().equalsIgnoreCase("a")) { img.parent().attr("href", "javascript:window.AC.viewImage('" + src + "');"); } else { img.attr("onclick", "javascript:window.AC.viewImage('" + src + "');"); } } @Override protected void onPostExecute(Boolean result) { isDocBuilding.set(false); if (isFinishing()) return; setSupportProgressBarIndeterminateVisibility(false); if (result) { if (cacheFile.exists()) { mWeb.loadUrl(Uri.fromFile(cacheFile).toString()); } else mWeb.loadDataWithBaseURL(getBaseUrl(), mDoc.html(), "text/html", "UTF-8", null); if (hasUseMap) mWeb.getSettings().setLayoutAlgorithm(LayoutAlgorithm.NARROW_COLUMNS); else mWeb.getSettings().setLayoutAlgorithm(LayoutAlgorithm.SINGLE_COLUMN); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { try { mWeb.getSettings().setLayoutAlgorithm(LayoutAlgorithm.TEXT_AUTOSIZING); } catch (IllegalArgumentException ignored) { } } } } }; /** * * * @author Yrom * */ private class DownloadImageTask extends AsyncTask<String, Integer, Void> { int timeoutMs = 3000; int tryTimes = 3; @Override protected Void doInBackground(String... params) { for (int index = 0; index < params.length; index++) { String url = params[index]; if (isCancelled()) { // cancel task on activity destory // Log.w(TAG, String.format("break download task,[%d/%d]", index+1, params.length)); break; } File cache = imageCaches.get(imgUrls.indexOf(url)); if (cache.exists() && cache.canRead()) { // Log.i(TAG, String.format("already downloaded.[%d/%d]",index+1, params.length)); publishProgress(index); continue; } else { cache.getParentFile().mkdirs(); } File temp = new File(cache.getAbsolutePath() + ".tmp"); InputStream in = null; OutputStream out = null; try { URL parsedUrl = new URL(url); retry: for (int i = 0; i < tryTimes && !isCancelled(); i++) { HttpURLConnection connection = Connectivity.openDefaultConnection(parsedUrl, timeoutMs * (1 + i / 2), (timeoutMs * (2 + i))); if (temp.exists()) { connection.addRequestProperty("Range", "bytes=" + temp.length() + "-"); out = new FileOutputStream(temp, true); } else out = new FileOutputStream(temp); try { int responseCode = connection.getResponseCode(); if (responseCode == 200 || responseCode == 206) { in = connection.getInputStream(); FileUtil.copyStream(in, out); cache.delete(); if (!temp.renameTo(cache)) { Log.w(TAG, "???" + temp.getName()); } publishProgress(index); break retry; } } catch (SocketTimeoutException e) { Log.w(TAG, "retry", e); } } } catch (IOException e) { e.printStackTrace(); } finally { try { if (in != null) in.close(); } catch (IOException ignored) { } try { if (out != null) out.close(); } catch (IOException ignored) { } } } return null; } @Override protected void onProgressUpdate(Integer... values) { if (imgUrls != null) { String url = imgUrls.get(values[0]); if (url == null) return; StringBuilder jsBuilder = new StringBuilder(); jsBuilder.append("javascript:(function(){") .append("var images = document.getElementsByTagName(\"img\"); ").append("var img = images[") .append(values[0] + 1).append("];").append("img.src = img.getAttribute(\"loc\");") .append("})()"); evaluateJavascript(jsBuilder.toString(), null); } } @Override protected void onPostExecute(Void result) { // ?? isDownloaded = true; evaluateJavascript( "javascript:(function(){" + "var images = document.getElementsByTagName(\"img\"); " + "for(var i=0;i<images.length;i++){" + "var imgSrc = images[i].getAttribute(\"loc\"); " + "if(imgSrc != null)" + "images[i].setAttribute(\"src\",imgSrc);" + "}" + "})()", null); } } class ACJSObject { @android.webkit.JavascriptInterface public void viewcomment() { CommentsActivity.start(ArticleActivity.this, mArticle.id); } @android.webkit.JavascriptInterface public void viewImage(String url) { ImagePagerActivity.startCacheImage(ArticleActivity.this, (ArrayList<File>) imageCaches, imgUrls.indexOf(url), aid, title); } } }