android浏览器插件小结

    做了一段时间的WebKit开发,后来又研究了一下WebKit的插件,但一直没有时间总结一下,现在终于有点时间可以梳理一下了,也希望能跟大家多多交流

    首先要说明一下什么是WebKit插件,WebKit插件就是内核是WebKit的浏览器的插件,IE的不知道,但firefox的插件应该与 WebKit插件类似,因为WebKit插件使用的主要一个技术或者说接口叫做NPAPI,而这套接口在mozilla的网站上面有详细的介绍,各位有兴趣可以去mozilla看看。回过头来,啥叫浏览器插件呢?或者说为啥要有这么个东西存在?我的理解是为了扩展浏览器的功能,大家知道,浏览器的功能主要是渲染网页,网页主要是由html,javascript,css组成,另外还包括一些资源文件,如果只是渲染网页,浏览器一般够用了,但随着网页内容的不断多样化,复杂化,比如多媒体文件的播放,浏览器已经不能提供这些功能了,于是插件就出现了,其中最有代表性的就是flash了。

 

插件的加载过程简单的说就是:

  • 浏览器在解析网页的时候,发现某个节点声明了某个MIME类型
  • 根据这个MIME类型查找注册过的插件,找到了就加载该插件

具体的说,在Android上面这个过程如下:

  • 当发现网页上存在object节点的时候,WebKit将进入加载plug-in的流程
  • 查找所有Intent类型为PLUGIN_ACTION的service,返回所有service的插件lib路径,形如/data/data/com.android.sampleplugin/lib(注:可能还会搜索/system/lib/下面的so)
  • 遍历各个目录中的动态库.so文件,通过预先定义的entryPoint解析出插件的信息,包括名字,描述,mime类型等,并保存这些信息
  • 整个WebView在layout的时候会创建plug-in,在这个过程中根据mime类型等创建PluginView(插件视图?)
  • 接着会建立PluginPackage,建立的过程中会调用NP_Initialize,完成Plugin的初始化工作

注:在3中建立PluginView的时候会找到对应mime类型的PluginPackage(mime和PluginPackage一一对应),这样在 PluginView的start里面会利用PluginPackage的接口调用NPP_New,创建plugin的实例(一个PluginView和一个plugin的实例一一对应),并且同时创建一个对应的NPObject的实例,记录在NPP的pdata里面(一个plugin的实例对应一个 NPObject的实例)

 

在Android平台开发插件,下面这4个函数是必须实现的:

 

NPError NP_Initialize(NPNetscapeFuncs* browserFuncs, NPPluginFuncs* pluginFuncs, void *java_env);
NPError NP_GetValue(NPP instance, NPPVariable variable, void *value);
const char* NP_GetMIMEDescription(void);
void NP_Shutdown(void);

注:这个在不同的平台下面有区别,我发现在mac os上面需要实现

NPError NP_Initialize(NPNetscapeFuncs *browserFuncs);
NPError NP_GetEntryPoints(NPPluginFuncs *pluginFuncs);
void NP_Shutdown(void);

这3个函数,比较一下参数就知道,Android下面的

NP_Initialize

包括了mac os中的

NP_Initialize

NP_GetEntryPoints  

的功能,额外的java_env是将java虚拟机环境传下来,作为jni层的context,这个是Android必须要的,你懂的生气

但mac os为啥不需要实现

NP_GetMIMEDescription  

呢?嗯,这个俺没去深究,有谁有兴趣的可以去研究一下

 

 

 

 

 

 

 

 

 

 

 

下面看看

const char* NP_GetMIMEDescription(void);  

 

比如返回:"application/x-yourname:tst:MIME type is application/x-yourname"

可以参考这里

接着看看

NP_Initialize  

 

比较重要,通过browserFuncs将浏览器的接口传给插件,通过pluginFuncs得到插件的接口,java_env上面提过了

问题:为啥用这种方式将接口传给插件呢?

一般来说,公布模块的接口,我们会直接声明函数的,要使用它,包含头文件,库文件就可以直接调用了,但这里有个问题,我们开发的是插件,是给浏览器用的,而不是直接调用浏览器。从语法上说明可能更清楚一些,在Android上面这些插件都是app,并包含动态库.so文件,按照一般的调用方式,将提示找不到这些接口的实现,是的,用函数指针将解决这个问题,在插件中访问浏览器的接口都是通过函数指针实现的,感觉有点类似C++中的虚函数,万能的指针,C的灵魂啊

问题:那为啥插件的接口传给浏览器也要用这种方式呢?

对啊,既然调用是从浏览器到插件的,那插件直接提供头文件,浏览器直接调用不就好了,为啥又要用函数指针?这个我也没整明白,貌似用直接调用的方式也是可行的,比如我们在讨论的这4个函数就是从浏览器直接调用插件的,不知道大家有没有别的答案。

下面分析一下参数browserFuncs,在npfunctions.h中定义的:

typedef struct _NPNetscapeFuncs {
    uint16 size;
    uint16 version;
    
    NPN_GetURLProcPtr geturl;
    NPN_PostURLProcPtr posturl;
    NPN_RequestReadProcPtr requestread;
    NPN_NewStreamProcPtr newstream;
    NPN_WriteProcPtr write;
    NPN_DestroyStreamProcPtr destroystream;
    NPN_StatusProcPtr status;
    NPN_UserAgentProcPtr uagent;
    NPN_MemAllocProcPtr memalloc;
    NPN_MemFreeProcPtr memfree;
    NPN_MemFlushProcPtr memflush;
    NPN_ReloadPluginsProcPtr reloadplugins;
    NPN_GetJavaEnvProcPtr getJavaEnv;
    NPN_GetJavaPeerProcPtr getJavaPeer;
    NPN_GetURLNotifyProcPtr geturlnotify;
    NPN_PostURLNotifyProcPtr posturlnotify;
    NPN_GetValueProcPtr getvalue;
    NPN_SetValueProcPtr setvalue;
    NPN_InvalidateRectProcPtr invalidaterect;
    NPN_InvalidateRegionProcPtr invalidateregion;
    NPN_ForceRedrawProcPtr forceredraw;
    
    NPN_GetStringIdentifierProcPtr getstringidentifier;
    NPN_GetStringIdentifiersProcPtr getstringidentifiers;
    NPN_GetIntIdentifierProcPtr getintidentifier;
    NPN_IdentifierIsStringProcPtr identifierisstring;
    NPN_UTF8FromIdentifierProcPtr utf8fromidentifier;
    NPN_IntFromIdentifierProcPtr intfromidentifier;
    NPN_CreateObjectProcPtr createobject;
    NPN_RetainObjectProcPtr retainobject;
    NPN_ReleaseObjectProcPtr releaseobject;
    NPN_InvokeProcPtr invoke;
    NPN_InvokeDefaultProcPtr invokeDefault;
    NPN_EvaluateProcPtr evaluate;
    NPN_GetPropertyProcPtr getproperty;
    NPN_SetPropertyProcPtr setproperty;
    NPN_RemovePropertyProcPtr removeproperty;
    NPN_HasPropertyProcPtr hasproperty;
    NPN_HasMethodProcPtr hasmethod;
    NPN_ReleaseVariantValueProcPtr releasevariantvalue;
    NPN_SetExceptionProcPtr setexception;
    NPN_PushPopupsEnabledStateProcPtr pushpopupsenabledstate;
    NPN_PopPopupsEnabledStateProcPtr poppopupsenabledstate;
    NPN_EnumerateProcPtr enumerate;
    NPN_PluginThreadAsyncCallProcPtr pluginthreadasynccall;
    NPN_ConstructProcPtr construct;
    NPN_GetValueForURLProcPtr getvalueforurl;
    NPN_SetValueForURLProcPtr setvalueforurl;
    NPN_GetAuthenticationInfoProcPtr getauthenticationinfo;
    NPN_ScheduleTimerProcPtr scheduletimer;
    NPN_UnscheduleTimerProcPtr unscheduletimer;
    NPN_PopUpContextMenuProcPtr popupcontextmenu;
    NPN_ConvertPointProcPtr convertpoint;
} NPNetscapeFuncs;

 

有超过50个函数。。。。。我没找到详细的说明文档,那就以后碰到了再研究吧,有个概念,里面提供了浏览器端的各种接口,提供给插件使用。

下面是pluginFuncs的:

typedef struct _NPPluginFuncs {
    uint16 size;
    uint16 version;
    NPP_NewProcPtr newp;
    NPP_DestroyProcPtr destroy;
    NPP_SetWindowProcPtr setwindow;
    NPP_NewStreamProcPtr newstream;
    NPP_DestroyStreamProcPtr destroystream;
    NPP_StreamAsFileProcPtr asfile;
    NPP_WriteReadyProcPtr writeready;
    NPP_WriteProcPtr write;
    NPP_PrintProcPtr print;
    NPP_HandleEventProcPtr event;
    NPP_URLNotifyProcPtr urlnotify;
    JRIGlobalRef javaClass;
    NPP_GetValueProcPtr getvalue;
    NPP_SetValueProcPtr setvalue;
} NPPluginFuncs;

 

这个只有13个函数,比较好看哈,我也没找到详细的说明文档。。。。但看名字大概可以猜出作用,这些都是插件提供给浏览器的回调,浏览器会在需要的时候调用它们,比如创建插件的时候调用NPP_NewProcPtr,销毁的时候调用NPP_DestroyProcPtr等。

在这里,Android为我们提供了一套接口,扩展了浏览器端的接口,定义在Android_npapi.h中:

#define kLogInterfaceV0_ANPGetValue         ((NPNVariable)1000)
#define kAudioTrackInterfaceV0_ANPGetValue  ((NPNVariable)1001)
#define kCanvasInterfaceV0_ANPGetValue      ((NPNVariable)1002)
#define kMatrixInterfaceV0_ANPGetValue      ((NPNVariable)1003)
#define kPaintInterfaceV0_ANPGetValue       ((NPNVariable)1004)
#define kPathInterfaceV0_ANPGetValue        ((NPNVariable)1005)
#define kTypefaceInterfaceV0_ANPGetValue    ((NPNVariable)1006)
#define kWindowInterfaceV0_ANPGetValue      ((NPNVariable)1007)
#define kBitmapInterfaceV0_ANPGetValue      ((NPNVariable)1008)
#define kSurfaceInterfaceV0_ANPGetValue     ((NPNVariable)1009)
#define kSystemInterfaceV0_ANPGetValue      ((NPNVariable)1010)
#define kEventInterfaceV0_ANPGetValue       ((NPNVariable)1011)
#define kSupportedDrawingModel_ANPGetValue  ((NPNVariable)2000)
#define kJavaContext_ANPGetValue            ((NPNVariable)2001)

 

上面列出的是ANPGetValue使用到的类型参数,分别可以得到相关的接口,比如Log,播放音频,绘图,控制视图,得到当前Context等等,这些接口的实现都是由WebKit引擎做的,其中有个ANPEventInterfaceV0,post事件会在 NPP_HandleEventProcPtr中得到,相当于引擎提供的一个消息分发中心,这里负责分发各种系统消息,包括鼠标,键盘,触摸,绘制等,用户也可以自定义消息,具体请参考android_npapi.h,这里的消息将发送给具体的插件,后面将介绍具体的插件是怎么生成的

注意一下上面接口的NPX前缀,NPN表示浏览器端的接口(Netscape-Plugin-Netscape?),而NPP表示插件端的接口(Netscape-Plugin-Plugin?),那最开始的NP接口呢?嗯,不清楚,可能只是为了和这两种区别开来

OK,到了这里,浏览器通过MIME找到了插件,通过函数指针,告诉了插件,我浏览器提供的接口,也得到了插件提供的接口,顺便也把java环境Context传给了插件。

 

但初始化插件以后(NP_Initialize),WebKit将实例化插件,调用:

NPError NPP_New(NPMIMEType pluginType, NPP instance, uint16 mode, int16 argc,
        char* argn[], char* argv[], NPSavedData* saved);

 

所以上一节提到的NP前缀,我认为可以理解成类函数,而NPP前缀相当于实例函数了,这里的NPP,就可以看成是this指针,或者C里面的叫法Handle了,看NPP的定义:

typedef struct _NPP
{
    void*    pdata;            /* plug-in private data */
    void*    ndata;            /* netscape private data */
} NPP_t;
 
typedef NPP_t*    NPP;

 

也证实了这一点,对于它就是简单的使用它的pdata来传递参数吧

先来看看NPP_New里面的实现:

instance->pdata = browser->createobject (instance, getPluginClass());

其中browser是前面初始化的时候传过来的浏览器接口,利用它来实例化插件,getPluginClass是干嘛的?

这里的getpluginClass返回NPClass指针,其中定义了插件的一系列函数指针(又是函数指针。。。。),下面是一个实现的例子:

static NPClass pluginClass = {
    NP_CLASS_STRUCT_VERSION,
    pluginAllocate,
    pluginDeallocate,
    pluginInvalidate,
    pluginHasMethod,
    pluginInvoke,
    pluginInvokeDefault,
    pluginHasProperty,
    pluginGetProperty,
    pluginSetProperty,
    pluginRemoveProperty,
    pluginEnumerate
};

这些函数指针应该是预先定义好的,顺序,个数都要保证,由于以前开发过相关的这部分内容,我知道这些是浏览器用来操作NPObject的接口,任意一个js对象,调用它的属性或者方法,最终都是调用到对应的NPObject中来的,这样来看,这个NPObject代表了对应的一个js的对象,我们这里就是对应了MIME那个节点。

浏览器知道了这个NPObject的接口,就直接利用pluginAllocate建立这个NPObject,回调。。。又是回调,插件的地位很低的,告诉别人怎么样调用自己,但啥时候被调不是自己说了算的

实际中,我们创建了一个PluginObject,这个相当于是插件这边的Handle,里面保存了很多东东:

typedef struct PluginObject {
    NPObject header;
    NPP npp;
    NPWindow* window;
 
    PluginType pluginType;
    SubPlugin* activePlugin;
 
} PluginObject;
其中,NPObject,NPP在pluginAllocate中赋值:
static NPObject *pluginAllocate(NPP npp, NPClass *theClass)
{
    PluginObject *newInstance = (PluginObject*) malloc(sizeof(PluginObject));
    newInstance->header._class = theClass;
    newInstance->header.referenceCount = 1;
 
    if (!identifiersInitialized) {
        identifiersInitialized = true;
        initializeIdentifiers();
    }
 
    newInstance->npp = npp;
 
    return &newInstance->header;
}

这里可以看到,NPObject就是NPClass的封装,代表了js的对象本质就是一组函数

NPWindow在哪里赋值的?还记得NPP_SetWindow吧,这个应该是在插件视图创建好以后调用的。

嗯,还没有完,继续NPP_New,看看这3个参数

 

 

 

int16 argc,char* argn[], char* argv[]  

 

类似main函数的参数,argn和argv分别代表节点的属性名和属性值,我对HTML不是很熟,不知道是不是叫这个,还是对照测试用的HTML文件来解释好一些:

<object type="application/x-testbrowserplugin" height=200 width=400 id="sample">
<param name="DrawingModel" value="Surface" />
<param name="PluginType" value="Paint" />
</object>

 

OK,这里的argc == 2,argn[0] == "DrawingModel",argn[1] == "Surface",这样大家应该就明白了

浏览器通过HTML中插件的参数,调用NPP_New初始化插件,得到SubPlugin和PluginType

这里的SubPlugin代表真正的插件实例的接口,定义如下:

class SubPlugin {
public:
    SubPlugin(NPP inst) : m_inst(inst) {}
    virtual ~SubPlugin() {}
    virtual int16 handleEvent(const ANPEvent* evt) = 0;
    virtual bool supportsDrawingModel(ANPDrawingModel) = 0;
 
    int getPluginWidth();
    int getPluginHeight();
 
    NPP inst() const { return m_inst; }
 
private:
    NPP m_inst;
};
 
class SurfaceSubPlugin : public SubPlugin {
public:
    SurfaceSubPlugin(NPP inst) : SubPlugin(inst) { m_context = NULL; }
    virtual ~SurfaceSubPlugin() {}
    virtual jobject getSurface() = 0;
    virtual bool supportsDrawingModel(ANPDrawingModel);
 
    void setContext(jobject context);
 
    jobject m_context;
};

 

SubPlugin,提供处理事件接口,SurfaceSubPlugin继承SubPlugin,增加了Context,用作jni相关的调用

实际中,我们继承SurfaceSubPlugin,实现这些纯虚函数,创建我们自己的插件,而HandleEvent的事件就是前面讲到了的在NPP_HandleEvent中传入的

OK,到现在为止,初始化的工作做完了,基本的调用逻辑,框架也搭好了,可以开始具体的插件实现了。

你可能感兴趣的:(android)