Crosswalk入门

Crosswalk是一款开源的WEB引擎。目前Crosswalk正式支持的移动操作系统包括Android和Tizen,在Android 4.0及以上的系统中使用Crosswalk的Web应用程序在HTML5方面可以有一致的体验,同时和系统的整合交互方面(比如启动画面、权限管理、应用切换、社交分享等等)可以做到类似原生应用。现在Crosswalk已经成为众多知名HTML5平台和应用的推荐引擎,包括Google Mobile Chrome App、Intel XDK、Famo.us和Construct2等等,未来的Cordova 4.0也计划集成Crosswalk。

下载的时候有些小迷茫,不知道应该下载哪个,入门的话,还是使用下图的稳定版本好了。
CrossViewDownload

  • 集成到应用中

1.下载zip包,然后参考 Android Studio如何Import Module 即项目依赖(针对非Gradle项目,以Crosswalk为例) 中的介绍,建立Android Studio工程,并且导入到项目中。

2.在AndroidManifest.xml中增加如下权限

<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

使用XWalkView必须开启硬件加速,修改AndroidManifest.xml

<application 
android:name="android.app.Application"
android:label="XWalkUsers"
android:hardwareAccelerated="true">
  • 基本使用

Crosswalk中用来替代WebView的控件叫XWalkView

1.layout文件写法

<org.xwalk.core.XWalkView 
 android:id="@+id/activity_main"
 xmlns:android="http://schemas.android.com/apk/res/android"
 android:layout_width="fill_parent"
 android:layout_height="fill_parent">
</org.xwalk.core.XWalkView>

2.代码中使用
和其他Android的控件不同,这个类需要监听系统事件。例如:生命周期、intent、Activity result。
控件内置的Web引擎需要获取并处理这些信息。并且当XWalkView 不再需要使用的时候,在onDestroy方法中XWalkView必须显式的调用destroy方法,否则容易造成Web引擎的内存泄漏。

import android.content.Intent;
import android.support.v7.app.ActionBarActivity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import org.xwalk.core.XWalkView;

public class MainActivity extends ActionBarActivity {

    private XWalkView mXWalkView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mXWalkView = (XWalkView) findViewById(R.id.activity_main);
        mXWalkView.load("http://crosswalk-project.org/", null);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();

        //noinspection SimplifiableIfStatement
        if (id == R.id.action_settings) {
            return true;
        }

        return super.onOptionsItemSelected(item);
    }

    @Override
    protected void onPause() {
        super.onPause();
        if (mXWalkView != null) {
            mXWalkView.pauseTimers();
            mXWalkView.onHide();
        }
    }

    @Override
    protected void onResume() {
        super.onResume();
        if (mXWalkView != null) {
            mXWalkView.resumeTimers();
            mXWalkView.onShow();
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (mXWalkView != null) {
            mXWalkView.onDestroy();
        }
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode,Intent data) {
        if (mXWalkView != null) {
            mXWalkView.onActivityResult(requestCode, resultCode, data);
        }
    }

    @Override
    protected void onNewIntent(Intent intent) {
        if (mXWalkView != null) {
            mXWalkView.onNewIntent(intent);
        }
    }
}

3.loadUrl去哪了?

上面的代码中其实已经剧透了,使用load方法即可。

// url
mXWalkView.load("http://crosswalk-project.org/", null);

// this loads a file from the assets/ directory
mXWalkView.load("file:///android_asset/index.html", null);

4.WebViewClient?

对应WebView的WebViewClient,XWalkView中有XWalkResourceClient。

mXWalkView.setResourceClient(new XWalkResourceClient(mXWalkView){
	@Override
	public void onLoadFinished(XWalkView view, String url) {
		super.onLoadFinished(view, url);
	}
	@Override
	public void onLoadStarted(XWalkView view, String url) {
		super.onLoadStarted(view, url);
	}
});
  • 调用JavaScript

不像WebView一样获取setting设置setJavaScriptEnabled为true才能执行。
Crosswalk可以直接执行js。

mXWalkView.load("javascript:document.body.contentEditable=true;", null);

当然,按照Kitkat引入的方式,使用evaluateJavascript方法也是可以的。(大神们推荐)

  • JavaScript回调Java

  1. 定义js回调接口
    public class JsInterface {
        public JsInterface() {
        }
        @JavascriptInterface
        public String sayHello() {
            return "Hello World!";
        }
    }

    Caution: If you've set your targetSdkVersion to 17 or higher, you must add the @JavascriptInterface annotation to any method that you want available to your JavaScript (the method must also be public). If you do not provide the annotation, the method is not accessible by your web page when running on Android 4.2 or higher.
    From developer.android.com

    备注:这里的

    @JavaScriptInterface

    所在的包是

    import org.xwalk.core.JavascriptInterface;
  2. XWalkView设置JavaScript可用且绑定对象
    //绑定
    mXWalkView.addJavascriptInterface(new JsInterface(), "NativeInterface");
  3. 调用html执行JavaScript或直接执行Javascript调用Java
    mXWalkView.load("file:///android_asset/index.html", null);

    index.html源码:

    <a href="#" onclick="clicked()">Say Hello</a> <script>
    function clicked() {
    	alert(NativeInterface.sayHello());
    }
    </script>
  • 高级使用

调试

Kitkat开始,Android提供了和Chrome联调功能。可以很方便的在Chrome中调试WebView中的代码。
Crosswalk使用Chromium内核当然也具备这个功能。
开启调试的语句如下:

// turn on debugging 
XWalkPreferences.setValue(XWalkPreferences.REMOTE_DEBUGGING, true);

对于Crosswalk来说,这个设置是全局的。

使用动画或者设置隐藏可见注意

默认XWalkView不能使用动画,甚至setVisibility也不行。

XWalkView represents an Android view for web apps/pages. Thus most of attributes for Android view are valid for this class. Since it internally uses android.view.SurfaceView for rendering web pages by default, it can't be resized, rotated, transformed and animated due to the limitations of SurfaceView. Alternatively, if the preference key ANIMATABLE_XWALK_VIEW is set to True, XWalkView can be transformed and animated because TextureView is intentionally used to render web pages for animation support. Besides, XWalkView won't be rendered if it's invisible.

开启动画模式:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    // ANIMATABLE_XWALK_VIEW preference key MUST be set before XWalkView creation.
    XWalkPreferences.setValue(XWalkPreferences.ANIMATABLE_XWALK_VIEW, true);

    setContentView(R.layout.animatable_xwview_layout);
}
@Override
public void onDestroy() {
    super.onDestroy();

    // Reset the preference for animatable XWalkView.
    XWalkPreferences.setValue(XWalkPreferences.ANIMATABLE_XWALK_VIEW, false);
}

由于设置也像调试一样是全局的,在onDestroy时记得关闭。

暂停JS timer

html代码

<!DOCTYPE html>
<html>
<body>

<p>A script on this page starts this clock:</p>
<p id="demo"></p>

<script>
  var myVar = setInterval(function(){ myTimer(); }, 1000);

  function myTimer()
  {
    var d = new Date();
    var t = d.toLocaleTimeString();
    document.getElementById("demo").innerHTML = t;
  }
</script>

</body>
</html>

XWalkView对应方法:

mButton.setOnClickListener(new OnClickListener() {
    @Override
    public void onClick(View v) {
        if (mXWalkView != null) {
            if (!isPaused) {
                // Pause JS timer
                mXWalkView.pauseTimers();
                isPaused = true;
                mButton.setImageResource(android.R.drawable.ic_media_play);
            } else {
                // Resume JS timer
                mXWalkView.resumeTimers();
                isPaused = false;
                mButton.setImageResource(android.R.drawable.ic_media_pause);
            }
        }
    }
});

这也在防止内存泄漏,监听系统事件示例代码中提到过:

@Override
protected void onPause() {
   super.onPause();
   if (mXWalkView != null) {
       mXWalkView.pauseTimers();
       mXWalkView.onHide();
   }
}

@Override
protected void onResume() {
   super.onResume();
   if (mXWalkView != null) {
       mXWalkView.resumeTimers();
       mXWalkView.onShow();
   }
}

历史记录

mPrevButton.setOnClickListener(new OnClickListener() {
    @Override
    public void onClick(View v) {
        // Go backward
        if (mXWalkView != null &&
                mXWalkView.getNavigationHistory().canGoBack()) {
            mXWalkView.getNavigationHistory().navigate(
                    XWalkNavigationHistory.Direction.BACKWARD, 1);
        }
        XWalkNavigationItem navigationItem = mXWalkView.getNavigationHistory().getCurrentItem();
        showNavigationItemInfo(navigationItem);
    }
});

mNextButton.setOnClickListener(new OnClickListener() {
    @Override
    public void onClick(View v) {
        // Go forward
        if (mXWalkView != null &&
                mXWalkView.getNavigationHistory().canGoForward()) {
            mXWalkView.getNavigationHistory().navigate(
                    XWalkNavigationHistory.Direction.FORWARD, 1);
        }
        XWalkNavigationItem navigationItem = mXWalkView.getNavigationHistory().getCurrentItem();
        showNavigationItemInfo(navigationItem);
    }
});

private void showNavigationItemInfo(XWalkNavigationItem navigationItem){
    url = navigationItem.getUrl();// Get the url of current navigation item.
    originalUrl = navigationItem.getOriginalUrl();// Get the original url of current navigation item
    title = navigationItem.getTitle();

    text1.setText(title);
    text2.setText(url);
    text3.setText(originalUrl);
}

自动视频暂停

// The web page below will display a video.
// When home button is pressed, the activity will be in background, and the video will be paused.
mXWalkView.load("http://www.w3.org/2010/05/video/mediaevents.html", null);

loadAppFromManifest

mXWalkView.loadAppFromManifest("file:///android_asset/manifest.json", null);

manifest.json

{
  "name": "ManifestTest",
  "start_url": "index.html",
  "description": "Manifest test",
  "version": "1.0.0"
}

参考链接


Android Studio如何Import Module 即项目依赖(针对非Gradle项目,以Crosswalk为例)

我们要写一个使用CrosswalkWebView的项目,就要依赖Crosswalk的工程,在Eclipse中存在Workspace的概念,对应到Android Studio 就变成了Module.

我们下载到的Crosswalk-WebView的工程是Eclipse建立的项目,此时项目是不能被Android Studio直接引用的,因此需要导入成Android Studio项目的一个Module。

  • 导入Module

1.选择"Import Module"菜单

选择导入Android Studio项目的一个Module
选择导入Android Studio项目的一个Module

2.选择项目的路径,并且重命名 Module Name

导入项目路径并且重命名Module的名字
导入项目路径并且重命名Module的名字

3.完成导入

导入项目的最后一步
导入项目的最后一步

4.查看导入完成后的项目中,出现了新导入的 "CrosswalkWebview",实质上是拷贝了所需要的文件到工程的目录中

导入完成后的模块,出现在工程中
导入完成后的模块,出现在工程中

  • 设置Module之间项目依赖

1.选择"Open Module Settings"
Config_Module_Settings

2.设置模块依赖Module_Depends_Setting

3.选择依赖的ModuleModule_Depends_Select

4.选中依赖的模块,点击“OK”Checked_Selected_Modules

国内Android SDK 镜像

最近更新Android SDK的时候奇慢无比,频繁失败,到了无法使用的地步,搜索了一下找到一个比较好用的国内镜像。
北京化工大学镜像站 http://ubuntu.buct.edu.cn/

Android镜像设置如下
1.点击选项
android-step-1

2.代理服务器填写ubuntu.buct.edu.cn或ubuntu.buct.cn或ubuntu.buct6.edu.cn(IPv6),端口80,强制HTTP
注:该代理并非正向代理也不是反向代理,所有代理请求将被重定向至本站镜像

android-step-2
3.关闭SDK Manager

android-step-3
4.单击Reload,选择需要的组件,即可安装

android-step-4

Android Studio中创建keystore

一直在Eclipse中开发`Android`,切换到`Android Studio`中之后,各种不习惯。基本的创建`keystore`文件的操作也是找了半天才找到。

1.点击Build ,在下拉框中选择 "Generate Signed APK"

Signed_APK

2.选择 "Create new"

New_APK_Wizard

3.按照里面的内容填写即可,注意最后文件的扩展名变为".jks",而不是以前的".keystore".

New_Key_Store

注意:最新的`Android Studio 4.x`版本已经没办法按照上面的办法创建证书了,创建证书会报告如下错误:

解决方法是在`Android Studio`的命令行中执行证书创建命令,创建`pkcs12`格式的证书,如下:

$ keytool -deststoretype pkcs12 -genkeypair -alias ka -keystore ks.p12 -keyalg RSA

参考下图:

根据提示,补充内容即可。

`Android Studio`中使用证书:

signingConfigs {
	release {
		File strFile = new File("ks.p12")
		storeFile file(strFile)
		keyAlias 'ka'
		keyPassword 'password'
		storePassword 'password'
	}
	debug {
		File strFile = new File("ks.p12")
		storeFile file(strFile)
		keyAlias 'ka'
		keyPassword 'password'
		storePassword 'password'
	}
}

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

参考链接


使用keytool 生成证书

升级到OS X Yosemite后 Android Studio启动崩溃 Symbol not found: _CGContextSetAllowsAcceleration

Mac OS 升到Yosemite后, 打开 Android Studio报

Symbol not found: _CGContextSetAllowsAcceleration   
Referenced from: /Library/Java/JavaVirtualMachines/1.6.0_35-b10-428.jdk/Contents/Libraries/libawt.jnilib

解决方法,是重新安装Apple的Java支持:
Apple官网下载或者本站下载

Android通过tcpdump抓包

1. 手机要有root权限

2. 下载tcpdump

http://www.strazzere.com/android/tcpdump

3.上传tcpdump到手机

$ adb push c:\wherever_you_put\tcpdump /data/local/tcpdump

如果提示

failed to copy 'tcpdump' to '/data/local/tcpdump': Permission denied

则如下操作后重试

$ adb shell

$ su

$ chmod -R 777 /data/local

$ exit

4.给予tcpdump 执行权限

$ adb shell chmod 6755 /data/local/tcpdump

5.连接到手机的控制端,获得root权限

$ adb shell

$ su

6.切换到tcpdump 的所在目录

$ cd /data/local

7.抓包

$ ./tcpdump -i any -p -s 0 -w /sdcard/capture.pcap

tcpdump 命令参数:

# "-i any": listen on any network interface
# "-p": disable promiscuous mode (doesn't work anyway)
# "-s 0": capture the entire packet
# "-w": write packets to a file (rather than printing to stdout)
... do whatever you want to capture, then ^C to stop it ...

8.从手机端下载文件到电脑

$ adb pull /sdcard/capture.pcap d:/

9.在电脑上用wireshark打开capture.pcap即可分析log.

如果需要执行包过滤,只抓取得某些类型的报文,可以参考下面的介绍。

Execute the following if you would like to watch packets go by rather than capturing them to a file (-n skips DNS lookups. -s 0 captures the entire packet rather than just the header):

$ adb shell tcpdump -n -s 0

Typical tcpdump options apply. For example, if you want to see HTTP traffic:

$ adb shell tcpdump -X -n -s 0 port 80

根据以上的信息,写一个bat去执行(tcpdump文件必须在当前目录里)。

开始tcpdump

$ adb push tcpdump /data/local/tcpdump

$ adb shell chmod 6755 /data/local/tcpdump

$ adb shell rm -r /sdcard/capture.pcap

$ adb shell  /data/local/tcpdump -i any -p -s 0 -w /sdcard/capture.pcap

$ pause

下载tcpdump文件到电脑

$ adb pull /sdcard/capture.pcap capture.pcap

问题:有些机器root后通过adb shell 后,默认不是root用户,需要输入 su才能切换到root,这样在执行批处理会有问题,解决方法如下

$ adb shell "su -c 'sleep 1'"

$ adb start-server

$ adb push tcpdump /data/local/tcpdump

因没有root权限导致的问题

$ adb shell su -c "/data/local/tmp/tcpdump -i any -p -s 0 -w /sdcard/netCapture.pcap"

参考链接


Android通过tcpdump抓包

Eclipse 启动后被 "Android Library Update" 任务所阻塞

有时候 Eclipse 启动后,会一直阻塞在 "Android Library Update" 任务中,无法执行任何操作,包括保存文件修改、编译、运行等,甚至正常退出 Eclipse 都不行。这一般是由于上一次的不正常退出所导致的。

如果反复重启 Eclipse 依然如此,可以试试这个办法:在启动 Eclipse 后,立即执行 "Clean all projects" 任务(必须赶在 "Android Library Update" 自动执行之前,否则会被其阻塞而无法做任何事情)。执行完 "Clean all projects" 之后,应该就不会再被阻塞了。

更彻底的办法是,删掉 workspace 下面的 .metadata 目录,不过该方法比较暴力,将会清除所有的 project 信息,建议慎用。

引用链接 http://minotes.net/notes/15

Android 字体调整 fontScale 变化导致界面显示异常的问题

Android 字体调整,比如调整为超大字体,此时会导致Configuration 中的fontScale 变化导致界面显示异常。目前找到的办法为,在Application 的OnCreate 事件中增加如下代码

getResources().getDisplayMetrics().scaledDensity = getResources().getDisplayMetrics().scaledDensity/getResources().getConfiguration().fontScale;

继承并覆盖 onConfigurationChanged 方法

@Override
public void onConfigurationChanged(Configuration newConfig) {
	getResources().getDisplayMetrics().scaledDensity = getResources().getDisplayMetrics().scaledDensity/newConfig.fontScale;
	super.onConfigurationChanged(newConfig);
}

在 AndroidManifest.xml 中的 application 部分增加处理 onConfigurationChanged 事件的声明

<application
    android:configChanges="fontScale"

可以解决问题,但是会不会引起其他副作用,暂时未知。

Android 报告 java.lang.StackOverflowErrors 异常的问题分析

一般情况下,在使用比较复杂的布局的时候,尤其是 Fragment + ViewPager + SlideMenu 这种组合的情况下,会报告类似如下内容的崩溃栈信息

at android.view.View.draw(View.java:6880)
at android.view.ViewGroup.drawChild(ViewGroup.java:1646)
at android.view.ViewGroup.dispatchDraw(ViewGroup.java:1373)
at android.view.View.draw(View.java:6883)
at android.view.ViewGroup.drawChild(ViewGroup.java:1646)
at android.view.ViewGroup.dispatchDraw(ViewGroup.java:1373)
...

该异常在 2.X版本的Android系统上面表现尤为明显,往往 4.X版本的一切正常或者偶有卡顿,在 2.X版本上面直接崩溃。

分析一下,是一个 draw() -> dispatchDraw() -> drawChild() 的深度递归调用导致栈的溢出越界。对于 Android目前使用的 dalvik 虚拟机而言,系统默认的栈深度如下

Browsing for stack sizes through the dalvik source history:

也就是说,如果 draw() -> dispatchDraw() -> drawChild()  递归的深度太深,就会导致栈的不足问题。

解决方法, 目前貌似 dalvik 虚拟机 并不支持动态的修改栈的深度,这导致问题复杂化,首先,Fragment + ViewPager + SlideMenu  这种组合,即是什么都不增加,就已经有超过 10 层的递归了,这个可以在崩溃栈中的 drawChild 函数的数量就可以统计出来,这也就意味着,目前只有一条路可以走,那就是想办法减少布局的层次。有建议废弃 Fragment 来自己实现一套完整的东西,暂时还不建议如此操作。

一般方法就是

1.使用RelativeLayout 来减少尤其是 LinearLayout导致的布局深度问题,尽量在同层展开。

2.复杂布局的情况下,可以使用自定义View来实现,实在不行,可以自己计算坐标,直接绘制,比如用类似游戏的SurfaceView 之类的东西来替代。

3.利用 merge 来简化收缩布局,目前貌似FrameLayout 上面比较合适。

4.继承ViewGroup 的自定义View也是一层,这点不要忘记,能直接继承View的,就不要继承 ViewGroup

其他的方法,根据实际项目来处理好了。