1. 程式人生 > >chromium瀏覽器頁面longclick彈出選單功能的實現

chromium瀏覽器頁面longclick彈出選單功能的實現

最開始做這個功能是在chromium34上面實現的,後來移植到39上面,呼叫的相關的系統和核心的底層的介面還都好用,從34到39版本變化,chromium核心對於事件的傳遞這塊邏輯程式碼應該沒有太大的變化。

首先說下webkit瀏覽器是如何實現長按網頁彈出選單的:

從最開始的說起,對於android使用原生webview的瀏覽器來講,長按一個連結(當然也包括圖片,網站,郵箱,手機號碼等),都會彈出一個選單供使用者選擇,當然使用者點選長按的內容不同,選單彈出來的內容也不一樣。那麼系統把判斷使用者點選的類別大致包括下面幾類:WebView.HitTestResult.PHONE_TYPE,WebView.HitTestResult.EMAIL_TYPE,WebView.HitTestResult.GEO_TYPE,WebView.HitTestResult.IMAGE_TYPE,WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE,WebView.HitTestResult.SRC_ANCHOR_TYPE,WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE。

這些在一個android原生瀏覽器的程式碼中,都是可以看到的,上面也寫出了類名,方便大家查詢。

那麼這些資訊,在應用層是如何得到的呢?在webview依賴的activity中,有一個方法:

@Override
public void onCreateContextMenu(ContextMenu menu, View v,
ContextMenuInfo menuInfo) {

}

這其中第二個引數,返回的webview就能夠獲取到WebView.HitTestResult的相關資訊(當然這塊不是所有的時候都會返回webview,我在這裡說的我想大家應該都明白,我就不像寫程式碼那樣嚴謹的描述了)。接下來就是處理的相關邏輯了,這裡不再贅述,自己看程式碼就好了。

接下來說下chromiun核心的瀏覽器,應用層可以copy安卓原生瀏覽器的程式碼,非常簡單,和底層的互動就略有些複雜。在實現這個功能的過程中,主要做兩部分內容的工作:一部分是響應事件;另一部分是是將點選事件的位置資訊傳到底層,底層處理後將獲取到的資訊再反給上層。

先在這裡插一句:我們是將contentshell封裝成自己的webview,提供了和android原生的相似的介面供上層呼叫,這也符合軟體開發的基本原則。

1.事件響應:想要得到onCreateContextMenu回撥,需要webview performLongclick,contentView中的performLongclick也要設定為返回true,這樣才能保證一層一層的傳給activity

2.將點選事件的位置資訊傳到底層,底層處理後將獲取到的資訊再反給上層:

webview的onTouchEvent傳給Shell進行處理,在shell中呼叫nativeRequestNewTestDataAt方法,對應的c++檔案是shell_android.cc檔案,新增RequestNewTestDataAt方法,再呼叫shell_render_view_host_ext.cc中的RequestNewTestDataAt方法,這個方法有兩個引數(Int view_x, int view_y),這兩個就是座標資訊。前面的操作都是在主程序中進行,我們要把這兩個引數通過browser程序傳送給render程序。我們在shell_render_view_message.h中註冊IPC通訊的程式碼:

(browser to render)
IPC_MESSAGE_ROUTED2(ShellViewMsg_DoHitTest, int, int)

(render to browser)
IPC_MESSAGE_ROUTED1(ShellViewHostMsg_UpdateHitTestData, content::ShellHitTestData)

這塊註冊了兩個IPC_MESSAGE資訊,分別用於收發。

傳送訊息流程:shell_render_view_host_ext.cc中的RequestNewTestDataAt進行傳送;

shell_render_view_ext.cc中進行接收,下面是主要程式碼:

(省略了大部分詳細處理過程,省略的這些在chromium39原始碼中都能找到)

void ShellRenderViewExt::OnDoHitTest(int view_x, int view_y) {
  if (!render_view() || !render_view()->GetWebView())
    return;

  const blink::WebHitTestResult result =
      render_view()->GetWebView()->hitTestResultAt(
          blink::WebPoint(view_x, view_y));
  ShellHitTestData data;

  if (!result.urlElement().isNull()) {
    data.anchor_text = result.urlElement().innerText();
    data.href = GetHref(result.urlElement());
  }

  PopulateHitTestData(result.absoluteLinkURL(),
                      result.absoluteImageURL(),
                      result.isContentEditable(),
                      &data);
  Send(new ShellViewHostMsg_UpdateHitTestData(routing_id(), data));
}

將處理後的資訊,封裝成ShellHitTestData再次傳送給browser程序,注意,shell_render_view_ext.cc是在子程序中進行的。

render程序再次將處理後封裝好的東西傳送給browser程序。(ShellHitTestData是在shell_hit_test_data.cc中建立的,有的程式碼裡面叫AwHitTestData,這裡不再貼程式碼了)

下面是在shell_render_view_host_ext.cc中處理的程式碼(主程序)

void ShellRenderViewHostExt::OnUpdateHitTestData(
    const ShellHitTestData& hit_test_data) {
  DCHECK(CalledOnValidThread());
  last_hit_test_data_ = hit_test_data;
  has_new_hit_test_data_ = true;
}

這裡面同時也要提供介面:

const ShellHitTestData& ShellRenderViewHostExt::GetLastHitTestData() const {
  DCHECK(CalledOnValidThread());
  return last_hit_test_data_;
}

我們在shell_android.cc中呼叫上面的介面:

void Shell::UpdateLastHitTestData(JNIEnv* env, jobject obj) {
  if (!shell_render_view_host_ext_->HasNewHitTestData()) return;

  const ShellHitTestData& data = shell_render_view_host_ext_->GetLastHitTestData();
  shell_render_view_host_ext_->MarkHitTestDataRead();

  // Make sure to null the Java object if data is empty/invalid.
  ScopedJavaLocalRef<jstring> extra_data_for_type;
  if (data.extra_data_for_type.length())
    extra_data_for_type = ConvertUTF8ToJavaString(
        env, data.extra_data_for_type);

  ScopedJavaLocalRef<jstring> href;
  if (data.href.length())
    href = ConvertUTF16ToJavaString(env, data.href);

  ScopedJavaLocalRef<jstring> anchor_text;
  if (data.anchor_text.length())
    anchor_text = ConvertUTF16ToJavaString(env, data.anchor_text);

  ScopedJavaLocalRef<jstring> img_src;
  if (data.img_src.is_valid())
    img_src = ConvertUTF8ToJavaString(env, data.img_src.spec());

  Java_Shell_updateHitTestData(env,
                                    obj,
                                    data.type,
                                    extra_data_for_type.obj(),
                                    href.obj(),
                                    anchor_text.obj(),
                                    img_src.obj());
}

上面程式碼中最後一個掉用的方法,剛好是回撥Shell.java中的方法,相當於轉了一圈又回到了應用層,呼叫java層的updateHitTestData方法中包含了一些引數,其中data.type就是最前面說的哪幾種類型,後面就不說了,原始碼中bean裡面的註釋都有自己看吧。

接下來就是在onCreateContextMenu中進行處理了,回撥也有了,資料也傳回來了,接下來全都是在java層處理的程式碼,開啟新視窗,下載等操作了。