Android

Golang 开发跨平台SDK(iOS, Android),Part 2

    上一篇简单的介绍了下Golang Mobile的工作原理,这一篇讲介绍如何使用Golang 做一个跨平台的RSS SDK。

RSS和HTTP

    RSS是基于XML标准,在互联网上被广泛采用的内容包装和投递协议,主要用来描述和同步网站内容。既然RSS是一种网络协议,那我们就需要使用Golang的http模块了。整个流程可以归结为,RSS的SDK提供一个方法去请求RSS源获得数据,然后通过callback获得数据,而这些数据最好是Android/iOS都可以直接使用的model集合。

    作为一个http请求在移动端最好是在子进程中执行,通过异步回调返回给主进程或UI 进程,这其中带来的明显好处就是我们的http任务不会阻塞整个UI,用户在请求RSS源获取最新网站内容的同时依然可以继续对应用进行操作。Golang 本身并没有提供一个异步执行http的库,但是我们可以利用Golang routine 简单的封装一个异步执行http的库。

func (t *Task) Runtask() {  
    r := t.create()      // 这里create是创建channel
    go func() {
        res, err := http.Get(t.Url)
        if err != nil {
            r.err <- err
        } else {
            buf, err := ioutil.ReadAll(res.Body)
            if err != nil {
                r.err <- err
            } else {
                r.data <- buf
            }
        }
    }()
}

上面的代码段摘自我实现的库go-async,其原理就是将http任务在 golang routine中去执行,当执行结束是通过channel 返回。

接口

    通过简单的继承上面的go-async我们就可以实现一个简单的RSS http请求。

func RSS() string {  
    return "http://rss.cnbeta.com/rss"
}
type Listener interface {  
    OnSuccess(buf []byte)
    OnFailure(err string)
    OnEnd()
}
type CnBeta struct {  
    listener Listener
}
func (cb *CnBeta) Run() {  
    t := async.NewTask(RSS(), cb)
    t.Runtask()
}
func (cb *CnBeta) Success(buf []byte) {  
}

func (cb *CnBeta) Failure(err error) {  
}

因为Golang现在不支持倒出constant类型的数据结构,所以我们需要定义一个function,然后利用function的返回值取得这个常量。SuccessFailure是从go-async继承的方法,当异步http callback的时候就会执行其中一个方法。Listener是定义的一个外部接口,当然在Java/swift/Objective-C中实现这个接口在应用中拿到异步callback的数据。

Model

    上面我实现了请求RSS源并且异步获取了byte数据,但是我们的目标是设计一个SDk,只提供方法并不能满足一个完整的SDK的需要,SDK需要接口返回的是Model或者Model的集合。

func NewRssXml() *RssXml {  
    return &RssXml{
        Channel: &RssFeed{},
    }
}

type RssXml struct {  
    Version string
    Channel *RssFeed
}

type RssFeed struct {  
    Title       string
    Link        string
    Description string
    Language    string
    Copyright   string
    PubDate     string

    Items []*RssItem
}

type RssItem struct {  
    Title       string
    Link        string
    Description string
    PubDate     string
}

func (rss *RssXml) Feed() *RssFeed {  
    return rss.Channel
}

上面就是使用Golang实现的RSS的model,由于Golang mobile 本身的限制只能export基本的数据类型,所以我只export需要的类型,但是我也想要直接export Items []*RssItem,这时我们可以通过实现一个类型方法来实现

type RssFeed struct {  
    Title       string
    Link        string
    Description string
    Language    string
    Copyright   string
    PubDate     string

    items []*RssItem
}

func (feed *RssFeed) Length() int64 {  
    return int64(len(feed.items))
}

func (feed *RssFeed) Item(p int64) *RssItem {  
    if p >= feed.Length() {
        return &RssItem{}
    }
    return feed.items[p]
}

通过 rssfeed.Length() 我们获得总共有多少RssItem,而通过rssfeed.Item(1) 我们变可以得到第二个RssItem,变相的实现了直接使用rssfee.Items[1]

整合

    实现了Rss Model这时我们便可以将接口改写下

type Listener interface {  
    OnSuccess(rss *RssXml)
    OnFailure(err string)
    OnEnd()
}

func (cb *CnBeta) Success(buf []byte) {  
    doc := xmlx.New()
    if err := doc.LoadBytes(buf, nil); err != nil {
        cb.Failure(err)
        return
    }
    cb.listener.OnSuccess(parser(doc))
    cb.listener.OnEnd()
}

func (cb *CnBeta) Failure(err error) {  
    cb.listener.OnFailure(err.Error())
    cb.listener.OnEnd()
}

这是一整个SDK 便是完成了,但是最关键的部分是编译后在应用开发时引入,以iOS为例通过前一篇的方法我们得到了rss.framework,这时我们只要通过XCode将这个rss.framework引入到项目中便可以使用了。

func loadRss() {  
    let cnbeta = GoCbNewCnBeta(self)
    cnbeta.run()
}

// MARK: - cnbeta lisener interface
func onEnd() {  
}

func onFailure(err: String!) {  
}

func onSuccess(rss: GoCbRssXml!) {  
    cbRss = rss

    dispatch_async(dispatch_get_main_queue(), {

        if (self.refreshControl!.refreshing) {
            self.refreshControl!.endRefreshing()
        }
        self.tableView.reloadData()
    })
}

下面的截图便是我写的一个Demo demo

结论

    作为一个跨平台的语言,Golang拥有其特有的优势,编译成机器码带来的高效的性能,而且作为一个编写SDK或者数据处理层的工具,其高效的开发效率也是值得考虑的。至于能不能用在production的项目上,我想说Google都在用。

refs

Golang 开发跨平台SDK(iOS, Android),Part 1

前一篇介绍了用React-Native做跨平台开发,其实Golang在1.5发布以后也是支持做移动端的跨平台开发的,Golang的移动端开发支持两种模式,一种是使用Golang开发整个应用,另外一种便是使用Golang开发common library。这两种各有优缺点,前者没有完善的UI库,如用来开发一个完整的应用需要的工作量着实有点不小,或者用来开发游戏可能也是一个不错的选择,亦或者寄望于Google可以开发出完善的UI库,至于后者想对于前者就方便多了,Google的GoMobile项目已经完善了大部分的工作,现在的缺点就是支持的数据类型还是远远不够,而且现只支持ARM架构。所以现阶段二者都还有些限制,但是作为一个跨平台的备选方案还是有其可取之处的。

How it works

在Google GoMobile的项目里Google提供了一个工具gobindgobind可以生成对于Java和Objective-C的bindings。下面是一段Golang代码

package mypkg

type Counter struct {  
    Value int
}

func (c *Counter) Inc() { c.Value++ }

func New() *Counter { return &Counter{ 5 } }  

生成的Java Bindings是

public abstract class Mypkg {  
    private Mypkg() {}
    public static final class Counter {
        public void Inc();
        public long GetValue();
        public void SetValue(long value);
    }
    public static Counter New();
}

Objective-C 代码如下:

@class GoMypkgCounter;

@interface GoMypkgCounter : NSObject {
}

@property(strong, readonly) GoSeqRef *ref;
- (void)Inc;
- (int64_t)Value;
- (void)setValue:(int64_t)v;
@end

FOUNDATION_EXPORT GoMypkgCounter* GoMypkgNewCounter();  

其实简单的来说就是将Golang翻译成Java和Objective-C,只不过bindings只是提供访问Golang编译后的二进制文件的接口。

How to build it

比如说你有个一个test.go文件

package mypkg  
type Counter struct {  
    Value int
}
func (c *Counter) Inc() { c.Value++ }  
func New() *Counter { return &Counter{ 5 } }  

1.在编译Android的library时可以使用
ANDROID_HOME=your_android_sdk_path gomobile bind -target=android -o file_path_for_android_library.aar -v test.go
这段代码会生成一个Java的二进制libray file_path_for_android_library.aar,其中包涵了ARM架构的.so二进制文件,.jniJava bindings classes等文件,在Android的项目中直接引用这个aar这个文件就可以用了。

Counter c = Mypkg.New();  
c.Inc();  
c.GetValue();  


2.在编译iOS的library时可以使用
gomobile bind -target=iOS -o file_path_for_android_library.framework -v test.go
这段代码会生成一个iOS可用的framework库,然后在iOS的项目中引用这个file_path_for_android_library.framework就可以了。

GoMypkgCounter *c = [GoMypkgCounter init];  
c.Inc();  
c.Value();  

更多信息可以看 https://godoc.org/golang.org/x/mobile/cmd/gobind

React-Native Native Module In Practise

React-Native 是Facebook 开发的一套移动端跨平台开发的框架,其脱胎于 ReactJS 。React-Native允许你使用JavaScript开发移动端的应用,相对于 PhoneGap React-Native 确实带来了原生应用的性能,如果之前有写过ReactJS,用React-Native开发一个移动端应用是非常容易的。不过虽然React-Native社区一天天壮大,但是仍旧可能需要自己开发一些React-Native的原生模块。

什么是React-Native原生模块

React-Native 模块就是包含原生代码库的React-Native的模块(在Android 里是包含Java代码库,在iOS里是包含Objecive-C/Swift代码库),其工作原理是将原生代码库加载到React-Native的包管理器中,然后用JS通过React-Native的bridge调用原生库里的方法,最后返回结果。

Setup
创建React-Native项目
  1. 创建工作目录mkdir hello-rct
  2. 使用npm init 初始化项目
  3. 使用android sdk自带的cli工具初始化一个android library项目
android create lib-project \  
   --name hellorct \                # Project name
   --target android-23 \            # Android SDK version(target)
   --package com.xeodou.hellorct \  # Package name
   --gradle --gradle-version 1.3+ \ # Use gradle and 2.4 later version
   --path android                   # The project's directory

4.在android/build.gradle最后添加

dependencies {  
    compile 'com.facebook.react:react-native:0.13.+'
}

5.创建Java组件
添加HelloRCTManager.java

/*
* @Author: xeodou
* @Date:   2015
*/
package com.xeodou.hellorct

import com.facebook.react.ReactPackage;  
import com.facebook.react.bridge.JavaScriptModule;  
import com.facebook.react.bridge.NativeModule;  
import com.facebook.react.bridge.ReactApplicationContext;  
import com.facebook.react.uimanager.ViewManager;  
import java.util.ArrayList;  
import java.util.Collections;  
import java.util.List;

public class HelloRCTManager implements ReactPackage {

    @Override
    public List<Class<? extends JavaScriptModule>> createJSModules() {
        return Collections.emptyList();
    }
    @Override
    public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
        return Collections.emptyList();
    }
    @Override
    public List<NativeModule> createNativeModules(
            ReactApplicationContext reactContext) {
        List<NativeModule> modules = new ArrayList<>();
        modules.add(new HelloRCT(reactContext));
        return modules;
    }
}

创建HelloRCT.java

/*
* @Author: xeodou
* @Date:   2015
*/

package com.xeodou.hellorct

import com.facebook.react.bridge.ReactApplicationContext;  
import com.facebook.react.bridge.ReactContextBaseJavaModule;  
import com.facebook.react.bridge.ReactMethod;  
import com.facebook.react.bridge.Callback;

public class HelloRCT extends ReactContextBaseJavaModule {

    public static final String REACT_CLASS = "HelloRCT";

    public HelloRCT(ReactApplicationContext reactContext) {
        super(reactContext);
    }

    @Override
    public String getName() {
        return REACT_CLASS;         //这里的getName方法是将你的原生模块注册
    }                               //到React-Native的模块包管理器中,然后
                                    //在使用JS使用相同的名字获得该原生模块
    @ReactMethod                    // ReactMethod表明该方法是暴露给JS的
    public void hello(Callback cb) {// 的外部方法,返回一个字符串`Hello RCT`
        cb.invoke("Hello RCT");     // React-Native的调用是异步的
    }                               // 所以这里我们用Callback返回结果
}

6.创建index.android.js

/*
* hello-rct - index.android.js
* Copyright(c) 2015 xeodou <[email protected]>
* MIT Licensed
*/

var { NativeModules } = require('react-native');  
module.exports = NativeModules.HelloRCT;  

7.使用react-native init Example创建一个React-Native的Example应用测试我们的模块

  • 修改Example/settings.gradle
include ':app','hellorct'  
project(':hellorct').projectDir = new File(rootProject.projectDir, '../../android')  
  • 修改Example/app/build.gradle
dependencies {  
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support:appcompat-v7:23.0.1'
    compile 'com.facebook.react:react-native:0.13.+'
    compile project(':hellorct')  // <- 添加这行
}
  • 修改Example/app/src/main/java/.../MainActivity.java
import com.xeodou.hellorct.HelloRCTManager; //<- import java class

public class MainActivity extends Activity implements DefaultHardwareBackBtnHandler {  
     ...
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mReactRootView = new ReactRootView(this);

        mReactInstanceManager = ReactInstanceManager.builder()
                .setApplication(getApplication())
                .setBundleAssetName("index.android.bundle")
                .setJSMainModuleName("index.android")
                .addPackage(new MainReactPackage())
                .addPackage(new HelloRCTManager())  //<- 注册模块
                .setUseDeveloperSupport(BuildConfig.DEBUG)
                .setInitialLifecycleState(LifecycleState.RESUMED)
                .build();

        mReactRootView.startReactApplication(mReactInstanceManager, "Example", null);

        setContentView(mReactRootView);
    }
...
}
  • 修改Example/index.android.js
...
var { NativeModules } = require('react-native');  
var HelloRCT = NativeModules.HelloRCT;

var Example = React.createClass({  
  getInitialState: function() {
    return {};
  },
  click: function() {
    HelloRCT.hello( msg => {
      this.setState({
        hello: msg
      })
    })
  },

  render: function() {
    return (
      <View style={styles.container}>
        <Text style={styles.welcome}>
          Hi, {this.state.hello}
        </Text>
        <TouchableHighlight onPress={this.click}>
            <Text >Press!</Text>
        </TouchableHighlight>
      </View>
    );
  }
});
...

8.cd Example && react-native run-android

Cheers!!

新特性SlidingPaneLayout

在上一次的android v4 support library更新中添加了一些新的特性比如:

SlidingPaneLayout`    
`DrawerLayout  

等,Google 在新版的Google+客户端中使用了不少新特性.

SlidingPaneLayout与DrawerLayout类似效果在github上也有很多优秀的开源库,如Action content view SlidingMenu...这些第三方的库质量也非常高,但是现在Google自己支持了这些特性至于哪个更好还是看各位喜好。

在xml中添加slidingPanel

  
<android.support.v4.widget.SlidingPaneLayout xmlns:android="http://schemas.android.com/apk/res/android"  
    xmlns:tools="http://schemas.android.com/tools"  
    android:id="@+id/slidingPaneLayout"  
    android:layout_width="match_parent"  
    android:layout_height="match_parent"  
    tools:context=".MainActivity" >  

    <ListView  
        android:id="@+id/left_pane"  
        android:layout_width="320dp"  
        android:layout_height="match_parent"  
        android:layout_gravity="center" />  

    <RelativeLayout  
        android:id="@+id/right_pane"  
        android:layout_width="match_parent"  
        android:layout_height="match_parent"  
        android:background="#ff333333" >  

        <TextView  
            android:id="@+id/rightText"  
            android:layout_width="match_parent"  
            android:layout_height="match_parent" />  
    </RelativeLayout>  

</android.support.v4.widget.SlidingPaneLayout>  

在Code中去使用SlidingPanel

让SlidingPane 去实现SimplePanelSlideListener这个接口

    private class SliderListener extends SlidingPaneLayout.SimplePanelSlideListener {  
        @Override  
        public void onPanelOpened(View panel) {  
            mActionBar.onPanelOpened();  
        }  

        @Override  
        public void onPanelClosed(View panel) {  
            mActionBar.onPanelClosed();  
        }  
    }  

在上面的代码里我是配合Actionbar来使用SligingPanel

实现效果

ADT 22.0 遇到的奇怪问题

自从升级ADT到 adt-22.0 版本以后出现了两个bug。

Class not found.

各种class not found 的奇怪错误
一开始以为是自己的引用问题,后来排查很久发现不是
解决办法:
project-> properties ->Order And Export -> Android Private Libraries #Check之.

Project -> clean

bug 就不见了。

Ant release taskdef not found

在使用 taskdef 时 代码如下

  
  
<path id="android.antlibs">  
    <pathelement path="${sdk.dir}/tools/lib/anttasks.jar" />  
</path>  
<taskdef name="xpath"  
    classname="com.android.ant.XPathTask"  
    classpathref="android.antlibs" />  
<xpath input="AndroidManifest.xml" expression="/manifest/@android:versionName"  
    output="versionname" default="unknown"/>  
  

在新的ADT-22.0下使用 ant release
出现了 taskdef class com.android.ant.XPathTask cannot be found
这个奇怪的bug, google以后什么都没发现。
最后排查发现 ADT 22.0 把 anttasks.jar 的名字换成了ant-tasks.jar
然后改成这样子

  
  
<path id="android.antlibs">  
    <pathelement path="${sdk.dir}/tools/lib/anttasks.jar" />  
</path>  
  

执行ant release 就可以编译。

Android自动化实施(2)—服务器上搭建Android环境

继上一篇文章说道编写android Ant编译脚本,脱离IDE编译带来的慢的痛苦。
为了偷懒,必须自动化。

2.如何在服务器中搭建Android环境

服务器中没有ui如何搭建Android环境呢?

首先将Google提供的Android linux SDK上传到服务器
或者使用curl 直接下载到服务器,高兴用啥就用啥。

解包。必须保证服务器的java环境已经配置好了。没配置好的赶快去yum install 或者 apt-get install

本人也是通过SDK中的REDEAME文件了解到的
可以使用

android update sdk --no-ui  
或者  
android update sdk -u   

这个组命令通过命令行去下载android sdk
但是时去下载会下载全部的包,有些包使我们不需要的,只选择几个必须包就ok 的。
执行

android list sdk   

会得到如下的信息

Packages available for installation or update: 19  
   1- Documentation for Android SDK, API 17, revision 1  
   2- ARM EABI v7a System Image, Android API 17, revision 1  
   3- Google APIs, Android API 17, revision 1  
   4- Google APIs, Android API 16, revision 3  
   5- Google APIs, Android API 15, revision 2  
   6- Google APIs, Android API 14, revision 2  
   7- Google APIs, Android API 13, revision 1  
   8- Google APIs, Android API 12, revision 1  
   9- Google APIs, Android API 11, revision 1  
  10- Google APIs, Android API 10, revision 2  
  11- Intel Atom x86 System Image, Android API 10, revision 1  
  12- Google APIs, Android API 8, revision 2  
  13- Google APIs, Android API 7, revision 1  
  14- Google APIs, Android API 4, revision 2  
  15- Google APIs, Android API 3, revision 3  
  16- Google Play Billing Library, revision 2  
  17- Google Play Licensing Library, revision 2  
  18- Google USB Driver, revision 7  
  19- Intel x86 Emulator Accelerator (HAXM), revision 2  

使用

android update sdk --no--ui -filter 1,2,3,4  
或者  
android update sdk -u -t 1,2,3,4  

即可下载需要的依赖包。

这时候可能会报如下这个错。

/lib/ld-linux.so.2: bad ELF interpreter: No such file or directory  

不用担心,因为Google Android SDK 不支持64位机,怎么办呢?
看这篇文章点我,这篇文章会告诉你安装几个编译依赖即可解决问题,分别是

yum whatprovides ld-linux.so.2  
yum install xxx  
对于libncurses.so.5 和 libstdc++.so.6 执行同样操作  

将上一篇中的代码上传到服务器配置好ant环境
修改local.properties的sdk路径

在项目根目录来一发

ant release  

是不是很开心。

这时候可以写一个shell 脚本 package.sh 然后在服务器端执行即可。如果使用Java则使用JavaRuntime调用这个脚本就可以让服务器自动打包。

这里要提下,我在windows里写的shell脚本放在服务器端无法执行,各种奇怪问题,最后才发现是文件在两个OS间有差别造成的,比如换行Windows下是\r\n 但是Linux下是\n,最后在服务器上重写了这个脚本才可以顺利编译。经验告诉我们开发环境要和线上环境一致。
尼玛啊。

版权为本人xeodou所有。转载请注明出处。谢谢。

Android自动化实施(1)---编写Android自动化脚本

博客主要记录人生
说一说上一周在公司搭建的Android自动化打包实施工程
实施环境
vps
内存 2g
cpu 2GHz*2
OS centos 5.1
Android sdk 21
测试主机
OS window7 RTM

1.在本机配置Android自动化编译脚本

对于未使用ant构建Android工程的project须在project的根目录执行以下命令

android update project -p .  

会在更目录生成如下两个文件

build.xml`和`local.properties  

对于使用ant构建工程可以略过此步骤,对于使用maven构建工程的可以路过这篇文章。。。

修改build.xml

project name="xxxx"`为`project   

local.properties增加app.project.name=xxx即可

添加自定义编译预处理pre_build.xml文件
主要用户批处理修改引入的R文件和AndroidManifest.xml文件
例如:

  
  
<?xml version="1.0" encoding="UTF-8"?>  
<project>  
  <macrodef name="prepackage">  
    <sequential>  
       <echo>----- change debug false ------</echo>  
     <replaceregexp  
       file='./src/com/xeodou/test/Log.java'  
       flags="g"  
       match="TEST =\s?([a-z]*)"  
       replace="TEST = ${app.debug}">    
     </replaceregexp>  
     <replaceregexp  
        file='./src/com/xeodou/test/Log.java'  
        flags="g"  
        match="LOG =\s?([a-z]*)"  
        replace="LOG = ${app.debug}">    
     </replaceregexp>  
     <echo>----- modify manifest </echo>  
     <echo>----- include package name &amp;  umeng channel ID</echo>  
     <replaceregexp  
        file='./AndroidManifest.xml'  
        flags="g"  
        match='package\s?=\s?"([\d,\w,\.]*)"'  
        replace='package="com.com/xeodou/${app.package}"'  
        byline='true'>  
     </replaceregexp>  
     <echo>----- modify .R file -----</echo>  
     <replaceregexp  
        encoding="utf-8"  
        match="import com.xeodou.test.R;"  
        replace="import com.xeodou.${app.package}.R;"  
        flags="g">  
        <fileset dir="./src">    
           <include name="**/*.java"/>    
        </fileset>   
      </replaceregexp>  
      <taskdef resource="net/sf/antcontrib/antlib.xml" />  
      <if>  
        <equals arg1="${app.icon.ldpi}" arg2="" />  
              <else>  
            <echo>----- icon will change ------</echo>     
            <echo>----- modify app icon  ----</echo>  
            <copy  
              file="${app.icon.ldpi}" tofile="./res/drawable-ldpi/ic_icon.png" overwrite="true"></copy>  
            <copy  
              file="${app.icon.mdpi}" tofile="./res/drawable-mdpi/ic_icon.png" overwrite="true"></copy>  
            <copy  
              file="${app.icon.hdpi}" tofile="./res/drawable-hdpi/ic_icon.png" overwrite="true"></copy>  
            <copy  
                  file="${app.icon.xhdpi}" tofile="./res/drawable-xhdpi/ic_icon.png" overwrite="true"></copy>  
        </else>  
        </if>  
      </sequential>  
  </macrodef>  
</project>  
  

以上问一个pre_build.xml样例主要使用正则去修改了包名 关闭log和切换线上代码,还可以增加其他的正则匹配,渠道名什么的也是可以的嘛。值得注意的是要是想在ant中使用if标签必须下载antcontrib这个第三方包,放入ant/lib目录在需要使用if标签的文件中引入
如下代码就可以尽情使用if else了

<taskdef resource="net/sf/antcontrib/antlib.xml" />

此时还需配置下local.properties文件。
增加

key.alias=  
key.alias.password=  
key.store=  
app.package=  
key.store.password=  
app.icon.xhdpi=  
app.icon.ldpi=  
app.icon.hdpi=  
app.icon.mdpi=  
app.debug=  

key.store 为keystore文件的目录,其他为需要在pre_build.xml中使用的常量。
加入诸如也是可以的嘛。

version.code=4  
version.name=2.3  

最后在build.xml中添加如下代码

<import file="pre_package.xml"/>  
<prepackage />  

使用终端切换到project的根目录运行

ant release  

泡杯咖啡,稍等片刻就会发现项目的根目录下的bin里躺着xxxx-release.apk文件了。

版权为本人xeodou所有。转载请注明出处。谢谢。