SharedWorker源码解析

最近工作需要了解WebWorker,根据RTFSC原则,空下来看一下Chrome的SharedWorker源码。

ShareWorker是共用Worker,Chrome的实现调用new SharedWorker会分配独立进程,不管调用多少次都只有这一个实例。除了进程管理,SharedWorker还需要通信,Chrome中SharedWorker通过MessagePort通信。

SharedWorker.cpp

// 构造函数,
inline SharedWorker::SharedWorker(ExecutionContext* context)
// 初始化父类AbstrctWorker,
// AbstractWorker继承自ActiveDOMObject,因为要在JS中使用,所以Worker需要是一个DOM Object。暂时不关注ActiveDOMObject的实现。
// AbstractWorker有一个方法,resolveURL,对传进来的url进行有效性和安全性检查。
: AbstractWorker(context)
    //初始化连接标志m_isBeingConnected。
      , m_isBeingConnected(false)
  {
  }

//接下来是SharedWorker真正创建的函数,create:
  PassRefPtrWillBeRawPtr<SharedWorker> SharedWorker::create(ExecutionContext* context, const String& url, const String& name, ExceptionState& exceptionState)
  {
    // SharedWorker是独立进程的
    ASSERT(isMainThread());
    // 因为目前还不支持worker与worker之间通信,context必须是JS中的document
    ASSERT_WITH_SECURITY_IMPLICATION(context->isDocument());

    // 引用计数,SharedWorker会被多次引用,所以需要引用计数,以便最后一个引用退出时析构。
    UseCounter::count(context, UseCounter::SharedWorkerStart);

    // 使用构造函数构造一个worker实例。
    RefPtrWillBeRawPtr<SharedWorker> worker = adoptRefWillBeNoop(new SharedWorker(context));// SharedWorker的构造函数并没有做太多的事情,初始化父类AbstractWorker,以及一个标志,是否连接

    // 通信管道,MessageChannel放到后面看。
    MessageChannel* channel = MessageChannel::create(context);
    // port是从channel中拿到的,管道中用来通信的
    worker->m_port = channel->port1();
    OwnPtr<WebMessagePortChannel> remotePort = channel->port2()->disentangle();
    ASSERT(remotePort);

    // 这个看名字就知道什么意思了
    worker->suspendIfNeeded();

    // 这里chrome已经注释了
    // We don't currently support nested workers, so workers can only be created from documents.
    Document* document = toDocument(context);
    // 判断下能不能连上
    if (!document->securityOrigin()->canAccessSharedWorkers()) {
      exceptionState.throwSecurityError("Access to shared workers is denied to origin '" + document->securityOrigin()->toString() + "'.");
      return nullptr;
    }

    // 判断url的安全性
    KURL scriptURL = worker->resolveURL(url, exceptionState);
    if (scriptURL.isEmpty())
      return nullptr;

    // 创建document与worker的连接
    if (document->frame()->loader().client()->sharedWorkerRepositoryClient())
      document->frame()->loader().client()->sharedWorkerRepositoryClient()->connect(worker.get(), remotePort.release(), scriptURL, name, exceptionState);

    // 返回worker的实例
    return worker.release();
  }

  SharedWorker::~SharedWorker()
  {
  }

  const AtomicString& SharedWorker::interfaceName() const
      {
          return EventTargetNames::SharedWorker;
}

bool SharedWorker::hasPendingActivity() const
    {
        return m_isBeingConnected;
}

其实SharedWorker挺简单的,进程管理,通信,下面看通信是如何实现的(急需恶补一番底层通信知识,预定下周把底层通信手段学习一遍)。

static void createChannel(MessagePort* port1, MessagePort* port2)
{
  // 创建连接
  WebMessagePortChannel* channel1;
  WebMessagePortChannel* channel2;
  Platform::current()->createMessageChannel(&channel1, &channel2);
  ASSERT(channel1 && channel2);

  // Now entangle the proxies with the appropriate local ports.
  port1->entangle(adoptPtr(channel2));
  port2->entangle(adoptPtr(channel1));
}

MessageChannel::MessageChannel(ExecutionContext* context)
// MessageChannel里面就是搞两个port
: m_port1(MessagePort::create(*context))
, m_port2(MessagePort::create(*context))
{
  createChannel(m_port1.get(), m_port2.get());
}

好吧,这个类也不怎么干活啊,活在WebMessagePortChannel和MessagePort里面做。

MessagePort

// 如果这里是管道通信的话,那可能需要两组channel worker端一组,web端一组
// 这是一个干活的类,create里面做的事情不多,构造一下,返回
PassRefPtrWillBeRawPtr<MessagePort> MessagePort::create(ExecutionContext& executionContext)
{
  RefPtrWillBeRawPtr<MessagePort> port = adoptRefWillBeNoop(new MessagePort(executionContext));
  port->suspendIfNeeded();
  return port.release();
}

//构造函数做的也不多,创建一个跟document关联的DOM Object,初始化几个标志参数
MessagePort::MessagePort(ExecutionContext& executionContext)
: ActiveDOMObject(&executionContext)
, m_started(false)
    , m_closed(false)
    , m_weakFactory(this)
{
}

MessagePort::~MessagePort()
{
  close();
  if (m_scriptStateForConversion)
    m_scriptStateForConversion->disposePerContextData();
}

// 发消息
void MessagePort::postMessage(ExecutionContext* context, PassRefPtr<SerializedScriptValue> message, const MessagePortArray* ports, ExceptionState& exceptionState)
{
  // entangle,好吧,英语比较渣,google翻译下,是缠的意思,Orz
  if (!isEntangled())
    return;
  ASSERT(executionContext());
  ASSERT(m_entangledChannel);

  // 一个channel数组
  OwnPtr<MessagePortChannelArray> channels;
  // Make sure we aren't connected to any of the passed-in ports.
  // 防错代码
  if (ports) {
    for (unsigned i = 0; i < ports->size(); ++i) {
      MessagePort* dataPort = (*ports)[i].get();
      if (dataPort == this) {
        exceptionState.throwDOMException(DataCloneError, "Port at index " + String::number(i) + " contains the source port.");
        return;
      }
    }
    // 解绑
    channels = MessagePort::disentanglePorts(context, ports, exceptionState);
    if (exceptionState.hadException())
      return;
  }

  // 给channel发消息
  WebString messageString = message->toWireString();
  OwnPtr<WebMessagePortChannelArray> webChannels = toWebMessagePortChannelArray(channels.release());
  // 待看 WebMessagePortChannelArray
  m_entangledChannel->postMessage(messageString, webChannels.leakPtr());
}

// static 两组管道互相取
PassOwnPtr<WebMessagePortChannelArray> MessagePort::toWebMessagePortChannelArray(PassOwnPtr<MessagePortChannelArray> channels)
{
  OwnPtr<WebMessagePortChannelArray> webChannels;
  if (channels && channels->size()) {
    webChannels = adoptPtr(new WebMessagePortChannelArray(channels->size()));
    for (size_t i = 0; i < channels->size(); ++i)
    (*webChannels)[i] = (*channels)[i].leakPtr();
  }
  return webChannels.release();
}

// static
PassOwnPtrWillBeRawPtr<MessagePortArray> MessagePort::toMessagePortArray(ExecutionContext* context, const WebMessagePortChannelArray& webChannels)
{
  OwnPtrWillBeRawPtr<MessagePortArray> ports = nullptr;
  if (!webChannels.isEmpty()) {
    OwnPtr<MessagePortChannelArray> channels = adoptPtr(new MessagePortChannelArray(webChannels.size()));
    for (size_t i = 0; i < webChannels.size(); ++i)
    (*channels)[i] = adoptPtr(webChannels[i]);
    ports = MessagePort::entanglePorts(*context, channels.release());
  }
  return ports.release();
}

// 函数字面意思是,断开缠绕关系
PassOwnPtr<WebMessagePortChannel> MessagePort::disentangle()
{
  ASSERT(m_entangledChannel);
  // 将端口重置为0
  m_entangledChannel->setClient(0);
  return m_entangledChannel.release();
}

// Invoked to notify us that there are messages available for this port.
// This code may be called from another thread, and so should not call any non-threadsafe APIs (i.e. should not call into the entangled channel or access mutable variables).
// 通知port已经可用了,即start过了
void MessagePort::messageAvailable()
{
  ASSERT(executionContext());
  executionContext()->postTask(FROM_HERE, createCrossThreadTask(&MessagePort::dispatchMessages, m_weakFactory.createWeakPtr()));
}

// 类似于生命周期,start了通知一声
void MessagePort::start()
{
  // Do nothing if we've been cloned or closed.
  if (!isEntangled())
    return;

  ASSERT(executionContext());
  if (m_started)
    return;

  m_started = true;
  messageAvailable();
}

// 关掉缠绕关系
void MessagePort::close()
{
  if (isEntangled())
    m_entangledChannel->setClient(0);
  m_closed = true;
}

// 与远程channel缠绕
void MessagePort::entangle(PassOwnPtr<WebMessagePortChannel> remote)
{
  // Only invoked to set our initial entanglement.
  ASSERT(!m_entangledChannel);
  ASSERT(executionContext());

  m_entangledChannel = remote;
  m_entangledChannel->setClient(this);
}

const AtomicString& MessagePort::interfaceName() const
    {
        return EventTargetNames::MessagePort;
}

// 尝试从webChannel取message
static bool tryGetMessageFrom(WebMessagePortChannel& webChannel, RefPtr<SerializedScriptValue>& message, OwnPtr<MessagePortChannelArray>& channels)
{
  WebString messageString;
  WebMessagePortChannelArray webChannels;
  if (!webChannel.tryGetMessage(&messageString, webChannels))
  return false;

  if (webChannels.size()) {
    channels = adoptPtr(new MessagePortChannelArray(webChannels.size()));
    for (size_t i = 0; i < webChannels.size(); ++i)
    (*channels)[i] = adoptPtr(webChannels[i]);
  }
  message = SerializedScriptValueFactory::instance().createFromWire(messageString);
  return true;
}

bool MessagePort::tryGetMessage(RefPtr<SerializedScriptValue>& message, OwnPtr<MessagePortChannelArray>& channels)
{
  if (!m_entangledChannel)
    return false;
  return tryGetMessageFrom(*m_entangledChannel, message, channels);
}

// 分发消息
void MessagePort::dispatchMessages()
{
  // Because close() doesn't cancel any in flight calls to dispatchMessages() we need to check if the port is still open before dispatch.
  if (m_closed)
    return;

  // Messages for contexts that are not fully active get dispatched too, but JSAbstractEventListener::handleEvent() doesn't call handlers for these.
  // The HTML5 spec specifies that any messages sent to a document that is not fully active should be dropped, so this behavior is OK.
  if (!started())
    return;

  RefPtr<SerializedScriptValue> message;
  OwnPtr<MessagePortChannelArray> channels;
  // 从管道中拿到message
  while (tryGetMessage(message, channels)) {
    // close() in Worker onmessage handler should prevent next message from dispatching.
    if (executionContext()->isWorkerGlobalScope() && toWorkerGlobalScope(executionContext())->isClosing())
      return;

    // 这里有一次绑定端口
    OwnPtrWillBeRawPtr<MessagePortArray> ports = MessagePort::entanglePorts(*executionContext(), channels.release());
    RefPtrWillBeRawPtr<Event> evt = MessageEvent::create(ports.release(), message.release());

    dispatchEvent(evt.release(), ASSERT_NO_EXCEPTION);
  }
}

bool MessagePort::hasPendingActivity() const
    {
      // The spec says that entangled message ports should always be treated as if they have a strong reference.
      // We'll also stipulate that the queue needs to be open (if the app drops its reference to the port before start()-ing it, then it's not really entangled as it's unreachable).
        return m_started && isEntangled();
}

// 解除port entangle关系?
PassOwnPtr<MessagePortChannelArray> MessagePort::disentanglePorts(ExecutionContext* context, const MessagePortArray* ports, ExceptionState& exceptionState)
{
  if (!ports || !ports->size())
    return nullptr;

  // HashSet used to efficiently check for duplicates in the passed-in array.
  HashSet<MessagePort*> portSet;

  // Walk the incoming array - if there are any duplicate ports, or null ports or cloned ports, throw an error (per section 8.3.3 of the HTML5 spec).
  for (unsigned i = 0; i < ports->size(); ++i) {
  MessagePort* port = (*ports)[i].get();
  if (!port || port->isNeutered() || portSet.contains(port)) {
    String type;
    if (!port)
      type = "null";
    else if (port->isNeutered())
      type = "already neutered";
    else
      type = "a duplicate";
    exceptionState.throwDOMException(DataCloneError, "Port at index "  + String::number(i) + " is " + type + ".");
    return nullptr;
  }
  portSet.add(port);
}

  UseCounter::count(context, UseCounter::MessagePortsTransferred);

  // Passed-in ports passed validity checks, so we can disentangle them.
  // 每个port disentangle
  OwnPtr<MessagePortChannelArray> portArray = adoptPtr(new MessagePortChannelArray(ports->size()));
  for (unsigned i = 0; i < ports->size(); ++i)
  (*portArray)[i] = (*ports)[i]->disentangle();
  return portArray.release();
}

// 绑定所有ports
PassOwnPtrWillBeRawPtr<MessagePortArray> MessagePort::entanglePorts(ExecutionContext& context, PassOwnPtr<MessagePortChannelArray> channels)
{
  // https://html.spec.whatwg.org/multipage/comms.html#message-ports
  // |ports| should be an empty array, not null even when there is no ports.
  if (!channels || !channels->size())
    return adoptPtrWillBeNoop(new MessagePortArray());

  OwnPtrWillBeRawPtr<MessagePortArray> portArray = adoptPtrWillBeNoop(new MessagePortArray(channels->size()));
  for (unsigned i = 0; i < channels->size(); ++i) {
  RefPtrWillBeRawPtr<MessagePort> port = MessagePort::create(context);
  port->entangle((*channels)[i].release());
  (*portArray)[i] = port.release();
}
  return portArray.release();
}

主要就是entangle函数里面,会做一次remote.setClient(this),然后dispatchEvent就很方便了。疑问就是 为什么postMessage会做一次disentangle

就是port看的晕晕的,还是去理解一下底层通信吧.

小米Note(MIUI V7)查看程序访问网络流量排行

小米Note(MIUI V7) 连接上WIFI后,显示网络下载一直很高,不清楚是哪个应用在访问网络,网上搜索了一下,找到如何查看网络流量的方法。

  • 桌面上点击“安全中心”

desktop

  • 选择“流量剩...”这个水滴状的按钮

safe_center

  • 选择“流量排行”

flow_list

  • 点击右上角的切换图标来显示WIFI跟3G,4G上网排行

select_network

  • 详细的流量访问,排行信息

net_app_lists

什么是Web-Worker?

WebWorker类似于浏览器中的多线程操作。之前的JS中,无论你是使用setTimeout setIntever 还是 使用了XMLHttpRequest,都是在一个线程里面,前两个使用消息队列,XMLHttpRequest则是浏览器会帮你进行闲时进行,归根结底,都是在一个线程里面跑。如果用户想进行一些阻塞操作,很可能会产生卡住页面的情况。甚至于我们想实现一个类似于Android的专用 公用Service,那该怎么办?

H5新标准提出了WebWorker的概念,各个浏览器都各自实现了,先来看一下WebWorker能做什么。

 

WebWorker特点,在后台线程执行JS的能力,与页面通过send message这种方式通信。

WebWorker有两种,专用worker(dedicatedworker)与公用worker(sharedworker)。

疑问:worker与主线程如何同步,worker与主线程同时操作了一个DOM元素,会不会产生脏数据?所以,worker的能力被加以限制,不能访问DOM元素。

worker在chrome中是如何实现的?

chrome浏览器是多进程架构,分为Browser进程以及Render进程,每打开一个页面,浏览器都会为其分配一个Render进程,webkit以及js都运行在这个进程内。如果要起一个DedicatedWorker,Chrome会在Render进程中起一个线程。如果要起一个SharedWorker就稍微复杂一点,必须起一个专门的进程,并且,相同的SharedWorker不管你创建多少次,都只存在一个。

Android中的Chrome有一个限制,限定9个进程。

1.如果用户创建太多SharedWorker,可能第二个标签页都打不开?

2.SharedWorker的优先级如何定义?如果使用SharedWorker的页面都在后台,其优先级如何?

目前Android上的SharedWorker还处于讨论阶段,未实现。

下面看一下worker的基本用法,DedicatedWorker:

worker.js

this.addEventListener('message', function(e) {
    var data = e.data;
    console.log("worker: " + data);
    this.postMessage(data + 1);
});

worker中需要一个this.onmessage接收消息

postMessage发送消息

参数在 event.data中

main.js

if (window.Worker) {
    var worker = new Worker("./worker.js");
    worker.onmessage = function(e) {
        document.getElementById("worker").innerHTML = e.data;
    };

    document.addEventListener('keydown', function(evt) {
        worker.postMessage(evt.keyCode);
    });
}

使用Worker这个API来创建DedicatedWorker。

同样通过worker实例的onmessage和postMessage通信。

结束Worker:

在worker中,可以通过close()来kill掉自己

main中则调用worker.terminate()

如果worker运行中出现错误,在main中使用worker.onerror可以接收到错误消息

SharedWorker:

https://github.com/mdn/simple-shared-worker

SharedWorker跟DedicatedWorker有两个不同的地方:

1.通信不再直接通过worker,而是worker的port类

2.worker中需要实现onconnect,且其参数中有一个port列表,但目前只使用到了第一个

sharedworker.js

onconnect = function(e) {
   var port = e.ports[0];

   port.onmessage = function(e) {
     var workerResult = 'Result: ' + (e.data[0] * e.data[1]);
     port.postMessage(workerResult);
   }

}

main.js

if (!!window.SharedWorker) {
  var myWorker = new SharedWorker("worker.js");

    myWorker.port.postMessage([squareNumber.value,squareNumber.value]);

  myWorker.port.onmessage = function(e) {
    result2.textContent = e.data;
    console.log('Message received from worker');
  }
}

当然 还可以写另一个页面,同样可以使用跟main.js类似的方法跟sharedworker通信。

ServiceWorker:

ServiceWorker是WebWorker的一种,它更加复杂,所以也更加强大。

首先,ServiceWorker有独立的生命周期:

1.Register:主线程调用API注册ServiceWorker

2.Installing:浏览器启动安装过程,加载和缓存一些静态资源

有可能失败进入Error状态

3.Activated:激活阶段,此阶段可以升级ServiceWorker

4.激活后,ServiceWorker会接管页面,如果页面是刚刚注册,本次不会被接管,下次加载页面才会接管。

5.ServiceWorker接管页面后,如果有fetch和message事件,会处于onfetch和onmessage,其他情况可能被终止。

ServiceWorker的特性:

1.它是一个worker,同样不能操作dom元素,同样可以通过postMessage与调用线程通信

2.ServiceWorker增加了网络处理,onfetch

3.ServiceWorker不被使用的时候,它会自己终止,再次使用会被重新激活,不要依靠它的内存来保存信息,请用webStorage或者indexDB。

4.ServiceWorker大量使用Promise,就是封装的一个callback标准

5.ServiceWorker权限很大,即所有网络请求都经过它,可以劫持连接,伪造和过滤响应,所以只能在https网页上注册ServiceWorker(是只能么?)。

6.ServiceWorker只作用于同域的fetch。onfetch有一个缓存的例子,我们通过缓存类缓存一些request和response,下次request,直接去缓存找response,处理失败了再去网络实时请求。注:response的类型需要是basic,即同域请求。

8.ServiceWorker的自动更新。

当网页激活时,浏览器会检查ServiceWorker是否有更新(有一个字节不同就会认为有更新),浏览器后台下载。

下载完开始运行,进入install状态,之后进入waitting状态。因为此时旧的ServiceWorker仍然在运行。

当页面被杀掉,旧去新来。

当然,如果你之前缓存了request,更新后需要清理一下。

现有问题:

如果在Install时失败了,页面无法感知。

ServiceWorker主要作用是在onfetch里面缓存/处理request。

https://github.com/GoogleChrome/samples

中有大量的onfetch与cache结合使用做离线应用的例子,在此不多赘述。

来自

http://blog.csdn.net/yl02520/article/details/14446763

https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers

Android下NDK开发的动态库(.so,shared library)增加版本号信息

在Android下面开发,免不了要涉及到C/C++层的开发,这就会涉及到崩溃异常的处理问题。

随着程序的不断升级,更新,会出现多个版本的动态库同时在线上共存的问题,一旦出现崩溃日志,往往不能方便的知道到底是哪个版本出现的崩溃。

传统的Linux,可以通过编译时候指定版本号来处理,最后生成如“libc.so.6”这种形式的文件名,我们可以根据文件名来获得相应的版本信息。遗憾的是,这种命名方式,在Android上面是不支持的

目前的需求主要有三点:

  1. 不依赖文件名,查询到版本号

    版本号写在文件名中,是一个比较方便的方式,但是带来的问题却是,靠不住!文件名可以随意更改,往往传来传去,文件名中的信息就改得面目全非了。

  2. logcat的崩溃日志中能得到版本号

    系统自带的logcat会在进程崩溃的时候,打印出崩溃栈,但是信息非常的精简,只有当前的线程回退栈帧信息(backtrace)以及几个关键寄存器信息,往往不能准确提供有足够的信息。而如果想在客户的设备上面,拿到完整的core-dump信息,只能是呵呵一下了!

  3. C/C++层的版本号变动,不要改动JAVA层的代码

    一方面,如果我们把版本号写在动态库的名字里面,这样会造成每次动态库的升级,JAVA的调用代码都需要变动,小团队还好,大团队,完全不可想象。另一方面,如果是系统框架层的开发,更加悲催,一个文件名的改动,影响到一片,当然,软链接也是个不错的选择,但是这样,常用的APK开发又要折腾一番,免不了一堆的抱怨。

针对上面的需求,我们逐个来提供解决方案:

  • 不依赖文件名,查询到版本号

在任意的C/C++文件中增加如下代码

const  static __attribute__((unused,section(".SO_VERSION"))) char Version[] = VERSION;

在Android.mk文件中增加

LOCAL_CFLAGS += -DVERSION='"1.0.3"'

编译生成的".so"文件使用如下命令即可查询版本号信息:

$readelf --string-dump=".SO_VERSION"  libSo.so

String dump of section '.SO_VERSION':
  [     0]  1.0.3

解释:
上面的代码的目的,是要求GCC在一个名为".SO_VERSION"的段内,记录我们的版本号信息。“unused”属性用于告诉GCC,不要在编译的时候警告这个变量没有被任何代码引用过。
注意事项:
在定义变量"Version"的时候,不要使用"volatile"来修饰。这个关键字影响到了最后生成的段的"PROGBITS"标记位置,这个标记表明了最后加载到内存中的数据是否可修改(我们当然希望这个位置不可修改,如果可写,可能由于越界导致版本号的不准确)。

没有使用"volatile"修饰

$ readelf -S  libSo.so  | grep .SO_VERSION
  [12] .SO_VERSION   PROGBITS        0003f520 03f520 000008 00   A  0   0  8

使用"volatile"修饰

$ readelf -S  libSo.so  | grep .SO_VERSION
  [12] .SO_VERSION   PROGBITS        0003f520 03f520 000008 00   WA  0   0  8

注意两者的不同,一个属性是"A",一个属性是"WA"。

  • logcat的崩溃日志中能得到版本号,并且C/C++层的版本号变动,不要改动JAVA层的代码

目前对于这个问题的解决方法,是设置代理So。具体操作方式如下(假定我们生成的".so"项目LOCAL_MODULE:=So):

1.修改原来项目中的"JNI_OnLoad","JNI_OnUnLoad"函数,重新命名为 "So_JNI_OnLoad","So_JNI_OnUnLoad" 其他代码不变,并且在"So-jni.h"中对这两个函数进行声明。

2.所有的JNI方法都通过 "JavaVM->RegisterNatives" 方法进行动态注册。

3.建立SoStub-jni.cpp,代码如下:

#include "So-jni.h"

extern "C"
JNIEXPORT int JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) {
    return So_JNI_OnLoad(vm,reserved);
}

extern "C"
JNIEXPORT void JNICALL JNI_OnUnload(JavaVM* vm, void* reserved) {
    return So_JNI_OnUnload(vm,reserved);
}

4.原工程的"LOCAL_MODULE"修改为"LOCAL_MODULE:=So_1_0_3"(版本号根据实际情况调整即可

5.修改Android.mk,增加如下内容

include $(CLEAR_VARS)
LOCAL_MODULE    := SoStub
LOCAL_SRC_FILES := SoStub-jni.cpp

LOCAL_SHARED_LIBRARIES +=  So_1_0_3

include $(BUILD_SHARED_LIBRARY)

6.修改JAVA层的调用代码

static {
   System.loadLibrary("So");
}

为:

static {
   System.loadLibrary("SoStub");
}

解释:

由于logcat打印的崩溃栈,信息极少,因此我们只能采取这种折中的办法,这样设置之后,崩溃栈中会打印出"libSo_1_0_3.so"这样的字样,我们就可以知道版本号了。

JS处理相对路径

遇到引擎不能处理相对路径的问题,写了一个简单的相对路径转绝对路径的函数,留下备用:

function rel2Abs(path) {
    var _path = path;
    var locationStr = location.href.toString();
    console.log(locationStr);
    var result = '';

    var relative;
    while (relative = _path.substring(0, _path.indexOf("\/"))) {
        console.log(relative);
        if (relative === '.') {
            // 遇到. path去掉最前面的./
            // 遇到.当前路径去掉/后面内容
            _path = _path.substring(_path.indexOf('\/') + 1, _path.length);
            locationStr = locationStr.substring(0, locationStr.lastIndexOf('\/') + 1);
        } else if (relative === '..') {
            // 遇到.. path去掉最前面的../
            // 遇到.. 当前路径去掉/xxx,然后再去掉/后面的内容
            _path = _path.substring(_path.indexOf('\/') + 1, _path.length);
            locationStr = locationStr.substring(0, locationStr.lastIndexOf('\/'));
            locationStr = locationStr.substring(0, locationStr.lastIndexOf('\/') + 1);
        } else {
            break;
        }
    }
    var result = locationStr + _path;
    path = result;
    return result;
}

原理很简单,只支持/,且遇到错误不能自动处理

Android Studio(JAVA) 编译警告:使用了未经检查或不安全的操作

在编译Android Studio(JAVA)程序时发生了如下的警告:

使用了未经检查或不安全的操作
要了解详细信息,请使用 "-Xlint:unchecked" 重新编译。

  • 如何设置Android Stuido开启 "-Xlint:unchecked"

修改build.gradle

apply plugin: 'com.android.application'

android {
    compileSdkVersion 23
    buildToolsVersion "23.0.1"

    defaultConfig {
        applicationId "com.a.b.c"
        minSdkVersion 15
        targetSdkVersion 23
        versionCode 1
        versionName "1.0"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
        debug {
            minifyEnabled false
        }
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    testCompile 'junit:junit:4.12'
    compile 'com.android.support:appcompat-v7:23.0.1'
    compile 'com.android.support:design:23.0.1'
}

增加

tasks.withType(JavaCompile) {
   options.compilerArgs << "-Xlint:unchecked"
}

修改后的如下:

apply plugin: 'com.android.application'

android {
    compileSdkVersion 23
    buildToolsVersion "23.0.1"

    defaultConfig {
        applicationId "com.a.b.c"
        minSdkVersion 15
        targetSdkVersion 23
        versionCode 1
        versionName "1.0"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
        debug {
            minifyEnabled false
        }
    }

    tasks.withType(JavaCompile) {
       options.compilerArgs << "-Xlint:unchecked"
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    testCompile 'junit:junit:4.12'
    compile 'com.android.support:appcompat-v7:23.0.1'
    compile 'com.android.support:design:23.0.1'
}
  • 警告信息的处理例子

包含-Xlint:unchecked警告的代码

final LinkedHashMap<WeakReference<Object>,WeakReference<Object>> aWeakArray = new LinkedHashMap<>();
for (Iterator iter = aWeakArray.entrySet().iterator(); iter.hasNext(); ) {
	LinkedHashMap.Entry element = (LinkedHashMap.Entry)iter.next();
	WeakReference<Object> aWeakObj = (WeakReference<Object>)element.getKey();
	WeakReference<Object> aWeakTag = (WeakReference<Object>)element.getValue();
}

消除警告后的代码如下:

final LinkedHashMap<WeakReference<Object>,WeakReference<Object>> aWeakArray = new LinkedHashMap<>();
for (Iterator<LinkedHashMap.Entry<WeakReference<Object>,WeakReference<Object>> > iter = aWeakArray.entrySet().iterator(); iter.hasNext(); ) {
	LinkedHashMap.Entry<WeakReference<Object>,WeakReference<Object>> element = iter.next();
	WeakReference<Object> aWeakObj = element.getKey();
	WeakReference<Object> aWeakTag = element.getValue();
}

同样的方法适用于"-Xlint:deprecation"。

svnrdump dump 实现 SVN 库的远程导出

svnrdump dump 命令必须 在 SVN 1.7版本以上提供的用于远程dump SVN库的命令.

以导出libyuv的svn库为例:

svnrdump dump http://libyuv.googlecode.com/svn/ > libyuv.dump

可以在这里下载已经DUMP完成之后的版本。目前的版本号是1444。

恢复DUMP文件到Subversion工程,请参考 Could not open the requested SVN filesystem

Debian和OpenMediaVault在命令行下设置更新源

很多时候我们比较纠结的问题是,“该把哪个Debian镜像发布站点加入source.list文件?”。

Ubuntu的图形界面中有一个测试工具,命令行下面的Debian也有一个现成的程序:netselect

安装netselect

$ sudo apt-get install netselect

不带参数运行它时会显示它的帮助信息。运行它时加上以空格分隔的镜像主机列表,它会返回一个分值和列表中的一个主机名。这个分值通过评估ping time和hopsnumber(一个网络请求报文到达目标主机所经过的转发主机的个数)得出,它与镜像站点预计下载速度成反比(数值越小越好)。返回的主机名是主机列表中得分最低的那个(查看列表中所以主机的得分情况可使用-vv选项)。看出下的例子:

$ netselect http://mirrors.aliyun.com/debian/ http://mirrors.163.com/debian/

      5 http://mirrors.163.com/debian/

它表示,在netselect后列出的所有主机中,http://mirrors.163.com/debian/是下载速度最快的主机,其得分为5。

注意,最近163的服务器不知道发生了何种故障,导致各种更新失败,尽管测试的结果是163更快,但是我们建议还是使用阿里云的服务器。

把netselect找到的连接速度最快的镜像站点手工加入/etc/apt/sources.list文件.

最新版本的netselect软件包包含了netselect-apt脚本,它使上述操作自动完成。只需将发布目录树做为参数(默认为stable)输入,sources.list文件就会生成速度最快的main和non-US镜像站点列表,并保存在当前目录下

$ sudo apt-get install netselect-apt

$ netselect-apt stable

$ sudo mv /etc/apt/sources.list /etc/apt/sources.list.old

$ sudo mv sources.list /etc/apt/sources.list

$ sudo apt-get update

对于OpenMediaVault用户还是手工修改配置文件好了,主要是上面的工具生成的比较简略

#

# deb cdrom:[Debian GNU/Linux 7.0.0 _Wheezy_ - Official Snapshot amd64 LIVE/INSTALL Binary 20150108-14:12]/ wheezy contrib main non-free

#deb cdrom:[Debian GNU/Linux 7.0.0 _Wheezy_ - Official Snapshot amd64 LIVE/INSTALL Binary 20150108-14:12]/ wheezy contrib main non-free

deb http://mirrors.aliyun.com/debian/ wheezy main
deb-src http://mirrors.aliyun.com/debian/ wheezy main

deb http://security.debian.org/ wheezy/updates main contrib non-free
deb-src http://security.debian.org/ wheezy/updates main contrib non-free

# wheezy-updates, previously known as 'volatile'
deb http://mirrors.aliyun.com/debian/ wheezy-updates main contrib non-free
deb-src http://mirrors.aliyun.com/debian/ wheezy-updates main contrib non-free

JS常用方法封装

做项目是遇到一个封装的不错的Util类,写一下,长期备用:

CMD标准

define(function(require, exports, module) {
    "use strict";

    function Util() {}

    (function() {
        Util.isWindow = function(obj) {
            return obj !== null && obj === obj.window;
        };

        Util.isFunction = function(obj) {
            return typeof obj === 'function';
        };

        Util.isObject = function(obj) {
            return typeof obj === 'object';
        };

        Util.isArray = function(obj) {
            return obj instanceof Array;
        };

        Util.isPlainObject = function(obj) {
            return Util.isObject(obj) && !Util.isWindow(obj);
        };

        Util.isString = function(obj) {
            return typeof obj === 'string';
        };

        // 将其他的object统统搞到一个object中去
        // 这个函数不如直接写两个参数看着舒服 target objects,就不用各种slice shift了
        Util.extend = function(target) {
            var deep;
            var args = [].slice.call(arguments, 1);

            if (typeof target === 'boolean') {
                deep = target;
                target = args.shift();
            }

            args.forEach(function(arg) {
               extend(target, arg, deep);
            });

            return target;

            // 这个函数就是把source里面的一层一层往下展开给target,object 和 array会一直展开
            function extend(target, source, deep) {
                for (var key in source) {
                    if (deep && (Util.isPlainObject(source[key]) || Array.isArray(source[key]))) {
                        if (Util.isPlainObject(source[key]) && !Util.isPlainObject(source[key])) {
                            target[key] = {};
                        }

                        if (Array.isArray(source[key]) && !Array.isArray(target[key])) {
                            target[key] = [];
                        }
                        extend(target[key], source[key], deep);
                    } else {
                        target[key] = source[key];
                    }
                }
            }
        };

        Util.ajaxGet = function(url, data, callback) {
            // 简单学习一下XMLHttpRequest,一个JS对象,提供了封装的获得url上资源数据的方法,支持xml http ftp file
            // 步骤简单: open(method, url, sync), send(), 如果是异步的话 就调用onreadystatechange方法
            var xhr = new XMLHttpRequest();
            // 设置超时时间为30s
            xhr.timeout = 30000;

            xhr.onreadystatechange = function() {
                console.log("xhr.status is " + xhr.readyState);
                if (xhr.readyState === 4) {
                    var status = 0;
                    try {
                        status = xhr.status;
                    } catch(e) {
                        // eat and ignore the exception, since access the status of XHR might cause a exception after timeout occurs;
                        return;
                    }

                    if (status === 200) {
                        var result = null;
                        try {
                            result  = JSON.parse(xhr.responseText.replace(/\n|\r|\t|\b|\f/g, ''));
                        } catch(e) {
                            callback(null);
                            return;
                        }
                        callback(result);
                    } else {
                        callback(null);
                    }

                    xhr.onreadystatechange = null;
                    xhr = null;
                }
            };
            xhr.withCredentials = true;
            xhr.open("GET", url + "?" + data, true);
            xhr.send(null);
            return xhr;
        };

        Util.ajaxPost = function(url, data, callback) {
            var xhr = new XMLHttpRequest();
            // 原来navigator除了online,还有个connection
            // 这个connection,而且这个connection很刁,何以拿到type,还有change事件
            // 关键是一大堆浏览器都TM的不支持
            var connection = navigator.connection;
            switch(connection.type) {
                case connection.WIFI :
                case connection.ETHERNET:
                    xhr.timeout = 3000;
                    break;
                case connection.CELL_3G:
                case connection.CELL_4G:
                    xhr.timeout = 5000;
                    break;
                case connection.CELL_2G:
                    xhr.timeout = 30000;
                    break;
                default :
                    xhr.timeout = 30000;
            }

            // POST比GET复杂,首先不同网络timeout不一样,其次有很多事件需要监听
            xhr.addEventListener("error", function(e) {
                if (e && e.loaded === 60) {
                    // https error
                    if (e.total === 9) {
                        callback("CERTIFICATE TIME ERROR");
                    } else {
                        callback("CERTIFICATE ERROR");
                    }
                } else {
                    callback("NETWORK_ERROR");
                }
            });

            xhr.addEventListener("abort", function() {
                console.log("request abort!");
            });

            xhr.addEventListener("timeout", function() {
                callback("TIMEOUT");
            });

            xhr.addEventListener("readystatechange", function() {
                if (xhr.readyState === 4) {
                    if (xhr.status === 200) {
                        var result;
                        try {
                            result = JSON.parse(xhr.responseText.replace(/\n|\r|\t|\b|\f/g, ''));
                        } catch(e) {
                            callback("DATA_INVALID");
                            return;
                        }
                        callback(result);
                    } else if (xhr.status !== 0) {
                        callback("HTTP_ERROR");
                    }
                }
            });

            // withCredentials = true 将使得请求会带上cookie
            xhr.withCredentials = true;
            xhr.open("POST", url, true);
            xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
            xhr.send(data);
            return xhr;
        };

        Util.abort = function(xhr) {
            xhr.abort;
        };

        Util.getGeoLocation = function(callback) {
            var timeout = 30000;
            if (navigator.connection && navigator.connection.type) {
                switch (navigator.connection.type) {
                    // wifi
                    case 2:
                        timeout = 8000;
                        break;
                    // 2G
                    case 3:
                        timeout = 30000;
                        break;
                    // 3g:
                    case 4:
                        timeout = 20000;
                        break;
                    default:
                        timeout = 30000;
                        break;
                }
            }

            // 这里就是浏览器获取地理位置的用法
            navigator.geolocation.getCurrentPosition(
                function(position) {
                callback.call(this, position);
                }.bind(this),
                function(error) {
                    callback.call(this, null);
                }.bind(this),
                {
                    enableHighAccuracy: true,
                    maximumAge: 180000,
                    timeout: timeout
                });
        };

        // 实时定位, 返回一个ID,用来取消监听
        Util.startWatchGeoLocation = function(callback) {
            return navigator.geolocation.watchPosition(
                function(position) {
                    callback.call(this, position);
                }.bind(this),
                function() {
                    callback.call(this, null);
                }.bind(this),
                {
                    maximumAge: 600000,
                    timeout: 20000
                }
            );
        };

        Util.stopWatchGeoLocation = function(geoId) {
            navigator.geolocation.clearWatch(geoId);
        };

        // 主要用来 ajax request中把object转为字符串
        Util.paramToStr = function(params) {
            var data = '';
            var isFirstChar = true;
            for (var key in params) {
                if (params.hasOwnProperty(key)) {
                    if (isFirstChar === true) {
                        isFirstChar = false;
                        data += key + "=" + params[key];
                    } else {
                        data += "&" + key + "=" + params[key];
                    }
                }
            }
            return data;
        };

        // 预加载一个Image
        Util.preloadImage = function(images) {
            if (images && images.length) {
                for(var i = 0; i < images.length; i++) {
                    var imgContainer = new Image();
                    imgContainer.src = images[i];
                    imgContainer = null;
                }
            }
        };

        // 就是把 rgb(,,)换成了rgba(,,,)
        Util.addOpacity = function(color, opacity) {
            var newColor = color.replace(/rgb/g, "rgba").replace(/\)/g, "," + opacity + ")");
            return newColor;
        };

        Util.isOffline = function() {
            return !navigator.onLine;
        };

        Util.getStorageValue = function(key) {
            var item = window.localStorage.getItem(key);
            if (!item) {
                return;
            }

            item = JSON.parse(item);
            var data = item.data;
            var type = item.type;
            var value = null;
            switch(type) {
                case "Boolean":
                    value = Boolean(data);
                    break;
                case "String":
                    value = String(data);
                    break;
                case "Number":
                    value = Number(data);
                    break;
                case "JSON":
                    value = JSON.parse(data);
                    break;
            }

            return value;
        };

        Util.setStorageValue = function(key, value) {
            var type = null;
            var data = value;
            if (typeof value === "boolean") {
                type = "Boolean";
            } else if (typeof value === "string") {
                type = "String";
            } else if (typeof value === "number") {
                type = "Number";
            } else if (typeof value === "object") {
                type = "JSON";
                data = JSON.stringify(value);
            }

            window.localStorage.setItem(key, JSON.stringify({data: data, type: type}));
        }
    }).();

    module.exports = Util;
});

Ubuntu 12.04 + Apache2.2.22搭建SPDY服务器

SPDY是Google开发的基于传输控制协议(TCP)的应用层协议,该协议规定在一个SPDY连接内可以有无限个并行请求,服务器可以主动向客户端发起通信向客户端推送数据,通过请求优化、预加载、压缩HTTP 来达到加速的目的。

对用记而言,SPDY是基于SSL加密,它可以让网络访问更安全,用户隐私更加得到保护。对站长而言,SPDY在降低连接数目的同时,还使服务器上每个客户端占用的资源减少,从释放出更多内存和CPU ,让网站的浏览速度提升不少。

SPDY协议已经被Chrome、Firefox、Opera、IE 11以上支持,用户在访问使用SPDY协议加载的网站几乎感觉不到与普通的Https页面访问有何不同,而SPDY带来的页面加载速度提升和服务器性能优化确是有十分重要意义的。

具体的操作步骤如下:

  • 启用Apache2的HTTPS支持

参考 UBUNTU 12.04 下 APACHE 2.2.22 开启 HTTPS

  • 下载Apache2的mod_spdy模块

官网:https://developers.google.com/speed/spdy/mod_spdy/
由于众所周知的原因,本站提供下载(2015-11-6版本)
mod_spdy 64-bit .deb (Debian/Ubuntu)
mod_spdy 32-bit .deb (Debian/Ubuntu)

  • 安装mod_spdy模块(本站是64位系统)
$sudo dpkg -i mod-spdy-*.deb
$sudo apt-get -f install
  • 重启Apache2
$sudo service apache2 restart
  • 验证

目前验证貌似没有起作用啊!目前最新的chrome已经没办法进行验证了,主要是由于HTTP/2已经发布,Google放弃了spdy,转而支持HTTP/2,还是静待新版本的HTTP/2吧。

  • 卸载
$sudo dpkg -r mod-spdy-beta
$sudo dpkg -P mod-spdy-beta
  • 参考链接

Linux Mint + Apache2.2搭建SSL/HTTPS/SPDY服务器
https://developers.google.com/speed/spdy/mod_spdy/