标签 github 下的文章

Golang Channel用法简编

在进入正式内容前,我这里先顺便转发一则消息,那就是Golang 1.3.2已经正式发布了。国内的golangtc已经镜像了golang.org的安装包下载页面,国内go程序员与爱好者们可以到"Golang中 国",即golangtc.com去下载go 1.3.2版本。

Go这门语言也许你还不甚了解,甚至是完全不知道,这也有情可原,毕竟Go在TIOBE编程语言排行榜上位列30开外。但近期使用Golang 实现的一杀手级应用 Docker你却不该不知道。docker目前火得是一塌糊涂啊。你去国内外各大技术站点用眼轻瞥一下,如 果没有涉及到“docker”字样新闻的站点建 议你以后就不要再去访问了^_^。Docker是啥、怎么用以及基础实践可以参加国内一位仁兄的经验之作:《 Docker – 从入门到实践》。

据我了解,目前国内试水Go语言开发后台系统的大公司与初创公司日益增多,比如七牛、京东、小米,盛大,金山,东软,搜狗等,在这里我们可以看到一些公司的Go语言应用列表,并且目前这个列表似乎依旧在丰富中。国内Go语言的推广与布道也再稳步推进中,不过目前来看多以Go入 门与基础为主题,Go idioms、tips或Best Practice的Share并不多见,想必国内的先行者、布道师们还在韬光养晦,积攒经验,等到时机来临再厚积薄发。另外国内似乎还没有一个针对Go的 布道平台,比如Golang技术大会之类的的平台。

在国外,虽然Go也刚刚起步,但在Golang share的广度和深度方面显然更进一步。Go的国际会议目前还不多,除了Golang老东家Google在自己的各种大会上留给Golang展示自己的 机会外,由 Gopher Academy 发起的GopherCon 会议也于今年第一次举行,并放出诸多高质量资料,在这里可以下载。欧洲的Go语言大会.dotgo也即将开幕,估计后续这两个大会将撑起Golang技术分享 的旗帜。

言归正传,这里要写的东西并非原创,自己的Go仅仅算是入门级别,工程经验、Best Practice等还谈不上有多少,因此这里主要是针对GopherCon2014上的“舶来品”的学习心得。来自CloudFlare的工程师John Graham-Cumming谈了关于 Channel的实践经验,这里针对其分享的内容,记录一些学习体会和理解,并结合一些外延知识,也可以算是一种学习笔记吧,仅供参考。

一、Golang并发基础理论

Golang在并发设计方面参考了C.A.R Hoare的CSP,即Communicating Sequential Processes并发模型理论。但就像John Graham-Cumming所说的那样,多数Golang程序员或爱好者仅仅停留在“知道”这一层次,理解CSP理论的并不多,毕竟多数程序员是搞工程 的。不过要想系统学习CSP的人可以从这里下载到CSP论文的最新版本。

维基百科中概要罗列了CSP模型与另外一种并发模型Actor模型的区别:

Actor模型广义上讲与CSP模型很相似。但两种模型就提供的原语而言,又有一些根本上的不同之处:
    – CSP模型处理过程是匿名的,而Actor模型中的Actor则具有身份标识。
    – CSP模型的消息传递在收发消息进程间包含了一个交会点,即发送方只能在接收方准备好接收消息时才能发送消息。相反,actor模型中的消息传递是异步 的,即消息的发送和接收无需在同一时间进行,发送方可以在接收方准备好接收消息前将消息发送出去。这两种方案可以认为是彼此对偶的。在某种意义下,基于交 会点的系统可以通过构造带缓冲的通信的方式来模拟异步消息系统。而异步系统可以通过构造带消息/应答协议的方式来同步发送方和接收方来模拟交会点似的通信 方式。
    – CSP使用显式的Channel用于消息传递,而Actor模型则将消息发送给命名的目的Actor。这两种方法可以被认为是对偶的。某种意义下,进程可 以从一个实际上拥有身份标识的channel接收消息,而通过将actors构造成类Channel的行为模式也可以打破actors之间的名字耦合。

二、Go Channel基本操作语法

Go Channel的基本操作语法如下:

c := make(chan bool) //创建一个无缓冲的bool型Channel

c <- x        //向一个Channel发送一个值
<- c          //从一个Channel中接收一个值
x = <- c      //从Channel c接收一个值并将其存储到x中
x, ok = <- c  //从Channel接收一个值,如果channel关闭了或没有数据,那么ok将被置为false

不带缓冲的Channel兼具通信和同步两种特性,颇受青睐。

三、Channel用作信号(Signal)的场景

1、等待一个事件(Event)

等待一个事件,有时候通过close一个Channel就足够了。例如:

//testwaitevent1.go
package main

import "fmt"

func main() {
        fmt.Println("Begin doing something!")
        c := make(chan bool)
        go func() {
                fmt.Println("Doing something…")
                close(c)
        }()
        <-c
        fmt.Println("Done!")
}

这里main goroutine通过"<-c"来等待sub goroutine中的“完成事件”,sub goroutine通过close channel促发这一事件。当然也可以通过向Channel写入一个bool值的方式来作为事件通知。main goroutine在channel c上没有任何数据可读的情况下会阻塞等待。

关于输出结果:

根据《Go memory model》中关于close channel与recv from channel的order的定义:The closing of a channel happens before a receive that returns a zero value because the channel is closed.

我们可以很容易判断出上面程序的输出结果:

Begin doing something!
Doing something…
Done!

如果将close(c)换成c<-true,则根据《Go memory model》中的定义:A receive from an unbuffered channel happens before the send on that channel completes.
"<-c"要先于"c<-true"完成,但也不影响日志的输出顺序,输出结果仍为上面三行。

2、协同多个Goroutines

同上,close channel还可以用于协同多个Goroutines,比如下面这个例子,我们创建了100个Worker Goroutine,这些Goroutine在被创建出来后都阻塞在"<-start"上,直到我们在main goroutine中给出开工的信号:"close(start)",这些goroutines才开始真正的并发运行起来。

//testwaitevent2.go
package main

import "fmt"

func worker(start chan bool, index int) {
        <-start
        fmt.Println("This is Worker:", index)
}

func main() {
        start := make(chan bool)
        for i := 1; i <= 100; i++ {
                go worker(start, i)
        }
        close(start)
        select {} //deadlock we expected
}

3、Select

【select的基本操作】
select是Go语言特有的操作,使用select我们可以同时在多个channel上进行发送/接收操作。下面是select的基本操作。

select {
case x := <- somechan:
    // … 使用x进行一些操作

case y, ok := <- someOtherchan:
    // … 使用y进行一些操作,
    //
检查ok值判断someOtherchan是否已经关闭

case outputChan <- z:
    // … z值被成功发送到Channel上时

default:
    // … 上面case均无法通信时,执行此分支
}

【惯用法:for/select】

我们在使用select时很少只是对其进行一次evaluation,我们常常将其与for {}结合在一起使用,并选择适当时机从for{}中退出。

for {
        select {
        case x := <- somechan:
            // … 使用x进行一些操作

        case y, ok := <- someOtherchan:
            // … 使用y进行一些操作,
            // 检查ok值判断someOtherchan是否已经关闭

        case outputChan <- z:
            // … z值被成功发送到Channel上时

        default:
            // … 上面case均无法通信时,执行此分支
        }
}

【终结workers】

下面是一个常见的终结sub worker goroutines的方法,每个worker goroutine通过select监视一个die channel来及时获取main goroutine的退出通知。

//testterminateworker1.go
package main

import (
    "fmt"
    "time"
)

func worker(die chan bool, index int) {
    fmt.Println("Begin: This is Worker:", index)
    for {
        select {
        //case xx:
            //做事的分支
        case <-die:
            fmt.Println("Done: This is Worker:", index)
            return
        }
    }
}

func main() {
    die := make(chan bool)

    for i := 1; i <= 100; i++ {
        go worker(die, i)
    }

    time.Sleep(time.Second * 5)
    close(die)
    select {}
//deadlock we expected
}

【终结验证】

有时候终结一个worker后,main goroutine想确认worker routine是否真正退出了,可采用下面这种方法:

//testterminateworker2.go
package main

import (
    "fmt"
    //"time"
)

func worker(die chan bool) {
    fmt.Println("Begin: This is Worker")
    for {
        select {
        //case xx:
        //做事的分支
        case <-die:
            fmt.Println("Done: This is Worker")
            die <- true
            return
        }
    }
}

func main() {
    die := make(chan bool)

    go worker(die)

    die <- true
    <-die
    fmt.Println("Worker goroutine has been terminated")
}

【关闭的Channel永远不会阻塞】

下面演示在一个已经关闭了的channel上读写的结果:

//testoperateonclosedchannel.go
package main

import "fmt"

func main() {
        cb := make(chan bool)
        close(cb)
        x := <-cb
        fmt.Printf("%#v\n", x)

        x, ok := <-cb
        fmt.Printf("%#v %#v\n", x, ok)

        ci := make(chan int)
        close(ci)
        y := <-ci
        fmt.Printf("%#v\n", y)

        cb <- true
}

$go run testoperateonclosedchannel.go
false
false false
0
panic: runtime error: send on closed channel

可以看到在一个已经close的unbuffered channel上执行读操作,回返回channel对应类型的零值,比如bool型channel返回false,int型channel返回0。但向close的channel写则会触发panic。不过无论读写都不会导致阻塞。

【关闭带缓存的channel】

将unbuffered channel换成buffered channel会怎样?我们看下面例子:

//testclosedbufferedchannel.go
package main

import "fmt"

func main() {
        c := make(chan int, 3)
        c <- 15
        c <- 34
        c <- 65
        close(c)
        fmt.Printf("%d\n", <-c)
        fmt.Printf("%d\n", <-c)
        fmt.Printf("%d\n", <-c)
        fmt.Printf("%d\n", <-c)

        c <- 1
}

$go run testclosedbufferedchannel.go
15
34
65
0
panic: runtime error: send on closed channel

可以看出带缓冲的channel略有不同。尽管已经close了,但我们依旧可以从中读出关闭前写入的3个值。第四次读取时,则会返回该channel类型的零值。向这类channel写入操作也会触发panic。

【range】

Golang中的range常常和channel并肩作战,它被用来从channel中读取所有值。下面是一个简单的实例:

//testrange.go
package main

import "fmt"

func generator(strings chan string) {
        strings <- "Five hour's New York jet lag"
        strings <- "and Cayce Pollard wakes in Camden Town"
        strings <- "to the dire and ever-decreasing circles"
        strings <- "of disrupted circadian rhythm."
        close(strings)
}

func main() {
        strings := make(chan string)
        go generator(strings)
        for s := range strings {
                fmt.Printf("%s\n", s)
        }
        fmt.Printf("\n")
}

四、隐藏状态

下面通过一个例子来演示一下channel如何用来隐藏状态:

1、例子:唯一的ID服务

//testuniqueid.go
package main

import "fmt"

func newUniqueIDService() <-chan string {
        id := make(chan string)
        go func() {
                var counter int64 = 0
                for {
                        id <- fmt.Sprintf("%x", counter)
                        counter += 1
                }
        }()
        return id
}
func main() {
        id := newUniqueIDService()
        for i := 0; i < 10; i++ {
                fmt.Println(<-id)
        }
}

$ go run testuniqueid.go
0
1
2
3
4
5
6
7
8
9

newUniqueIDService通过一个channel与main goroutine关联,main goroutine无需知道uniqueid实现的细节以及当前状态,只需通过channel获得最新id即可。

五、默认情况

我想这里John Graham-Cumming主要是想告诉我们select的default分支的实践用法。

1、select  for non-blocking receive

idle:= make(chan []byte, 5) //用一个带缓冲的channel构造一个简单的队列

select {
case b = <-idle:
 //尝试从idle队列中读取
    …
default:  //队列空,分配一个新的buffer
        makes += 1
        b = make([]byte, size)
}

2、select for non-blocking send

idle:= make(chan []byte, 5) //用一个带缓冲的channel构造一个简单的队列

select {
case idle <- b: //尝试向队列中插入一个buffer
        //…
default: //队列满?

}

六、Nil Channels

1、nil channels阻塞

对一个没有初始化的channel进行读写操作都将发生阻塞,例子如下:

package main

func main() {
        var c chan int
        <-c
}

$go run testnilchannel.go
fatal error: all goroutines are asleep – deadlock!

package main

func main() {
        var c chan int
        c <- 1
}

$go run testnilchannel.go
fatal error: all goroutines are asleep – deadlock!

2、nil channel在select中很有用

看下面这个例子:

//testnilchannel_bad.go
package main

import "fmt"
import "time"

func main() {
        var c1, c2 chan int = make(chan int), make(chan int)
        go func() {
                time.Sleep(time.Second * 5)
                c1 <- 5
                close(c1)
        }()

        go func() {
                time.Sleep(time.Second * 7)
                c2 <- 7
                close(c2)
        }()

        for {
                select {
                case x := <-c1:
                        fmt.Println(x)
                case x := <-c2:
                        fmt.Println(x)
                }
        }
        fmt.Println("over")
}

我们原本期望程序交替输出5和7两个数字,但实际的输出结果却是:

5
0
0
0
… … 0死循环

再仔细分析代码,原来select每次按case顺序evaluate:
    – 前5s,select一直阻塞;
    – 第5s,c1返回一个5后被close了,“case x := <-c1”这个分支返回,select输出5,并重新select
    – 下一轮select又从“case x := <-c1”这个分支开始evaluate,由于c1被close,按照前面的知识,close的channel不会阻塞,我们会读出这个 channel对应类型的零值,这里就是0;select再次输出0;这时即便c2有值返回,程序也不会走到c2这个分支
    – 依次类推,程序无限循环的输出0

我们利用nil channel来改进这个程序,以实现我们的意图,代码如下:

//testnilchannel.go
package main

import "fmt"
import "time"

func main() {
        var c1, c2 chan int = make(chan int), make(chan int)
        go func() {
                time.Sleep(time.Second * 5)
                c1 <- 5
                close(c1)
        }()

        go func() {
                time.Sleep(time.Second * 7)
                c2 <- 7
                close(c2)
        }()

        for {
                select {
                case x, ok := <-c1:
                        if !ok {
                                c1 = nil
                        } else {
                                fmt.Println(x)
                        }
                case x, ok := <-c2:
                        if !ok {
                                c2 = nil
                        } else {
                                fmt.Println(x)
                        }
                }
                if c1 == nil && c2 == nil {
                        break
                }
        }
        fmt.Println("over")
}

$go run testnilchannel.go
5
7
over

可以看出:通过将已经关闭的channel置为nil,下次select将会阻塞在该channel上,使得select继续下面的分支evaluation。

七、Timers

1、超时机制Timeout

带超时机制的select是常规的tip,下面是示例代码,实现30s的超时select:

func worker(start chan bool) {
        timeout := time.After(30 * time.Second)
        for {
                select {
                     // … do some stuff
                case <- timeout:
                    return
                }
        }
}

2、心跳HeartBeart

与timeout实现类似,下面是一个简单的心跳select实现:

func worker(start chan bool) {
        heartbeat := time.Tick(30 * time.Second)
        for {
                select {
                     // … do some stuff
                case <- heartbeat:
                    //… do heartbeat stuff
                }
        }
}

Cocos2d-x 3.0rc2集成ShareSDK

给自己的手机游戏增加些社交分享功能,有助于游戏宣传和提升知名度,是一种不错的社交营销手段。国内这方面的第三方插件有不少,比如ShareSDK友 盟分享组件Baidu分享组件等,之前在研究2.2.2版本时,集成了ShareSDK这个组件,这次迁移到Cocos2d-x 3.0rc2依旧选择集成ShareSDK,这里就来说说集成的过程,遇到的一些问题以及解决方法。这里仅以Android平台游戏集成为例。

一、功能描述、SDK版本和帐号准备

功能大致是这样的:在游戏中设置一个按钮,点击这个按钮,弹出知名社交平台的分享图标集窗口,用户选择分享目标后,相关信息分享到对应的社交平台。分享结果通知通过Toast显示在屏幕的下方。

这次依旧使用ShareSDK for Android 2.3.7版本(ShareSDK-Android-2.3.7),Cocos2d-x的版本为3.0rc2

集成前,你需要有一个基于Cocos2d-x 3.0rc2的可运行的Android平台游戏project,我们的集成就基于该project,这里我们的project名为GameDemo,GameDemo的源码结构大致是:

GameDemo/
    – Classes/
    – proj.android/
    – Resources/
    – cocos2d/
    – CMakeLists.txt
    – … …

使用ShareSDK前,你需要在各大主流社交平台(微信微博)申请开发者帐号以及游戏接入权限(app_key、app_secret)等,当然在ShareSDK站点也应该有自己的帐号和应用AppKey,这些申请的审核需要几个工作日,甚至更长。

二、ShareSDK集成步骤

按照ShareSDK官方manual说法,Cocos2d-x集成ShareSDK有三种方式,之前在Cocos2d-x 2.2.2引擎中采用的是专用组件集成的方式,该组件(C2DXShareSDKSample)可以在这里下载(该组件近期已经fix了我之前发现的bug)。

1.  jar包集成

这次我们主要做微博、微信的社交分享,因此只需要微博、微信相关jar包。在C2DXShareSDKSample/proj.android/libs下,我们找到以下几个jar包:

  -rw-rw-r– 1 tonybai tonybai  97K  4月  8 18:10 mframework.jar
  -rw-rw-r– 1 tonybai tonybai 112K  4月  8 17:39 ShareSDK-Core-2.3.7.jar
  -rw-rw-r– 1 tonybai tonybai  19K  4月  8 17:39 ShareSDK-SinaWeibo-2.3.7.jar
  -rw-rw-r– 1 tonybai tonybai 4.3K  4月  8 17:39 ShareSDK-Wechat-2.3.7.jar
  -rw-rw-r– 1 tonybai tonybai  29K  4月  8 17:39 ShareSDK-Wechat-Core-2.3.7.jar
  -rw-rw-r– 1 tonybai tonybai 4.6K  4月  8 17:39 ShareSDK-Wechat-Favorite-2.3.7.jar
  -rw-rw-r– 1 tonybai tonybai 4.4K  4月  8 17:39 ShareSDK-Wechat-Moments-2.3.7.jar

把这些jar包文件Copy到GameDemo/proj.android/libs下。

2. 配置文件与资源部分集成

修改GameDemo/proj.android/AndroidManifest.xml文件,在application标签下,添加如下Activity标签:

        <activity
            android:name="cn.sharesdk.framework.ShareSDKUIShell"
            android:configChanges="keyboardHidden|orientation|screenSize"
            android:screenOrientation="portrait"
            android:theme="@android:style/Theme.Translucent.NoTitleBar"
            android:windowSoftInputMode="stateHidden|adjustResize" >
    </activity>
    <activity
            android:name=".wxapi.WXEntryActivity"
            android:configChanges="keyboardHidden|orientation|screenSize"
            android:exported="true"
            android:screenOrientation="portrait"
            android:theme="@android:style/Theme.Translucent.NoTitleBar" />

将C2DXShareSDKSample/proj.android/res下的如下目录中的文件复制到GameDemo/proj.android/res下:

   drawable-hdpi/  drawable-ldpi/  drawable-mdpi/ 
   drawable-xhdpi/  layout/  values/  values-en/

注意,类似icon.png这种文件就不要复制了,自己做一下判断就好。

3. C++部分代码集成

将C2DXShareSDKSample/Classes下的C2DXShareSDK文件夹Copy到GameDemo/Classes下面。

由于Cocos2d-x 3.0rc2的类命名发生了变化,我们需要对C2DXShareSDK中使用到的引擎中的类名以及方法名进行修改。但实际上Cocos2d-x 3.0rc2考虑到了一些兼容性的问题,大部分名字通过cocos2d/cocos/deprecated/CCDeprecated.h中定义的typedef得以保留,虽然这些名字已经被建议deprecated了。rc2中CCObject被改名为Ref了,这个我们需要手工在C2DXShareSDK进行修改。

另外ShareSDK组件在实现时大量使用了CCDictionaryCCArrayCCString,而这三个类在Cocos2d-x 3.0rc2中均被deprecated了,但我们依然可以使用,所以我们可以不做修改。但以后随着cocos2d-x版本的演进,这些类很可能被彻底移除出引擎,我们就需要重新使用其替代品进行实现了。

此外我们还需要手工修改一下C2DXShareSDK/Android/JSON/CCJSONConverter.cpp文件中的getObjJson方 法,因为rc2中CCDictionary、CCString、CCArray这些类的真实名称都已经换成了__Dictionary、__String 和__Array,CCDictionary、CCString、CCArray只是些typedef,因此要像下面这样做些修改(如果你是集成 cocos2d-x 2.x.x版本,则无需做下面修改):

cJSON * CCJSONConverter::getObjJson(Ref * obj)
{
    std::string s = typeid(*obj).name();
    if(s.find("__Dictionary")!=std::string::npos){
        cJSON * json = cJSON_CreateObject();
        convertDictionaryToJson((CCDictionary *)obj, json);
        return json;
    }else if(s.find("__Array")!=std::string::npos){
        cJSON * json = cJSON_CreateArray();
        convertArrayToJson((CCArray *)obj, json);
        return json;
    }else if(s.find("__String")!=std::string::npos){
        CCString * s = (CCString *)obj;
        cJSON * json = cJSON_CreateString(s->getCString());
        return json;
    }else if(s.find("CCNumber")!=std::string::npos){
        CCNumber * n = (CCNumber *)obj;
        cJSON * json = cJSON_CreateNumber(n->getDoubleValue());
        return json;
    }else if(s.find("CCNull")!=std::string::npos){
        cJSON * json = cJSON_CreateNull();
        return json;
    }
    CCLog("CCJSONConverter encountered an unrecognized type");
    return NULL;
}

CCNumber和CCNull是ShareSDK组件自己实现的类名,这里无需修改。

接下来我们需要在AppDelegate.cpp中对ShareSDK做初始化了:

bool AppDelegate::applicationDidFinishLaunching() {
    … …
    initShareSDK();
    … ..
}

void AppDelegate::initShareSDK()
{
    // sina weibo
    CCDictionary *sinaConfigDict = CCDictionary::create();
    sinaConfigDict->setObject(CCString::create("YOUR_WEIBO_APPKEY"), "app_key");
    sinaConfigDict->setObject(CCString::create("YOUR_WEBIO_APPSECRET"), "app_secret");
    sinaConfigDict->setObject(CCString::create("http://www.sharesdk.cn"), "redirect_uri");
    C2DXShareSDK::setPlatformConfig(C2DXPlatTypeSinaWeibo, sinaConfigDict);

    // wechat
    CCDictionary *wcConfigDict = CCDictionary::create();
    wcConfigDict->setObject(CCString::create("YOUR_WECHAT_APPID"), "app_id");
    C2DXShareSDK::setPlatformConfig(C2DXPlatTypeWeixiSession, wcConfigDict);
    C2DXShareSDK::setPlatformConfig(C2DXPlatTypeWeixiTimeline, wcConfigDict);
    C2DXShareSDK::setPlatformConfig(C2DXPlatTypeWeixiFav, wcConfigDict);

    C2DXShareSDK::open(CCString::create("YOUR_SHARESDK_APPKEY"), false);
}

在Share按钮的事件回调函数中调用ShareSDK的接口进行社交平台分享:

void GameScene::menuShareCallback(Ref* sender)
{
    Dictionary *content = Dictionary::create();

    content->setObject(String::create("ShareSDK for Cocos2d-x 3.0rc2社交分享测试。")
                        , "content");
    content->setObject(String::create("ShareSDK分享测试"), "title");
    content->setObject(String::create("http://tonybai.com"), "titleUrl");
    content->setObject(String::create("http://tonybai.com"), "url");
    content->setObject(String::create("Tony Bai"), "site");
    content->setObject(String::create("http://tonybai.com"), "siteUrl");
    content->setObject(String::createWithFormat("%s", YOUR_LOCAL_IMAGE_PATH)
                       , "image");
    content->setObject(String::createWithFormat("%d", C2DXContentTypeNews)
                       , "type");

    C2DXShareSDK::showShareMenu(NULL, content, CCPointMake(100, 100),
                          C2DXMenuArrowDirectionLeft, shareResultHandler);
}

void shareResultHandler(C2DXResponseState state,
                        C2DXPlatType platType,
                        Dictionary *shareInfo,
                        Dictionary *error)
{
    AppDelegate *app = (AppDelegate*)Application::getInstance();
    switch (state) {
        case C2DXResponseStateSuccess:
            CCLog("Share Ok");
            app->showShareResultToast("分享成功");
            break;
        case C2DXResponseStateFail:
            app->showShareResultToast("分享失败");
            CCLog("Share Failed");
            break;
        default:
            break;
    }
}

showShareResultToast实现如下:

void AppDelegate::showShareResultToast(const char *msg)
{
    JniMethodInfo t;
    if (JniHelper::getStaticMethodInfo(t, "YOUR_ACTIVITY_NAME",
        "showShareResultToast", "(Ljava/lang/String;)V")) {
        jstring jmsg = t.env->NewStringUTF(msg);
        t.env->CallStaticVoidMethod(t.classID, t.methodID, jmsg);
        if (t.env->ExceptionOccurred()) {
            t.env->ExceptionDescribe();
            t.env->ExceptionClear();
            return;
        }
        t.env->DeleteLocalRef(t.classID);
    }
}

4. Java部分代码集成

在GameDemo/proj.android/src下面建立cn/sharesdk路径,将C2DXShareSDKSample /proj.android/src/cn/sharesdk下的onekeyshare和ShareSDKUtils.java Copy到GameDemo/proj.android/src/cn/sharesdk下面。

将ShareSDK-Android-2.3.7.zip解压后的ShareSDK for Android/Src/wxapi Copy到GameDemo/proj.android/src/com.tonybai.game/下。

修改GameDemo/proj.android/src/com.tonybai.game/GameDemoActivity.java文件:

import android.widget.Toast;
import cn.sharesdk.ShareSDKUtils;

public class GameDemoActivity extends Cocos2dxActivity {

    private static Context context;

    private static Handler notifyHandler = new Handler() {
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case 1:
                    String message = (String) msg.obj;
                    Toast.makeText(context, message,
                      Toast.LENGTH_SHORT).show();
                    break;
                default:
                    break;
            }
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        context = this;
        ShareSDKUtils.prepare();
        ShareSDKUtils.initSDK("YOUR_SHARESDK_APPKEY", true);
    }

    public static void showShareResultToast(String result) {
        Message msg = new Message();
        msg.what = 1;
        msg.obj = result;
        notifyHandler.sendMessage(msg);
    }

    @Override
    public void onDestroy() {
        ShareSDKUtils.stopSDK();
        super.onDestroy();
    }
}

三、问题与解决方法

按照上面的集成方法修改后,通过cocos编译app,在模拟器运行GameDemo,点击Share,理论上屏幕下方会出现ShareSDK的分享窗口,选择“新浪微博”图标,会打开“图文分享”内容窗口,点击窗口右上角的“分享”即可。

问题1】“图文分享”窗口内容可编辑,并且总是弹出软键盘,影响体验。
 
 期望:内容不可编辑,默认不弹出软键盘
 解决方法:
      打开proj.android/src/cn/sharesdk/onekeyshare/EditPage.java,做如下修改:

      将窗口的软输入方式默认改为SOFT_INPUT_STATE_HIDDEN。

      public void setActivity(Activity activity) {
        super.setActivity(activity);
        if (dialogMode) {
            activity.setTheme(android.R.style.Theme_Dialog);
            activity.requestWindowFeature(Window.FEATURE_NO_TITLE);
        }
        activity.getWindow().setSoftInputMode(
                   //WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
                   WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN);//default: hidden
    }

    在initPageView中增加一行:etContent.setKeyListener(null)。让窗口内容无法修改。
    private void initPageView() {
         … …
        // 文字输入区域
        etContent = new EditText(getContext());
        etContent.setGravity(Gravity.LEFT | Gravity.TOP);
        etContent.setBackgroundDrawable(null);
        etContent.setText(String.valueOf(reqData.get("text")));
        etContent.setKeyListener(null);//make the edittext uneditable
        etContent.setLayoutParams(lpEt);
        … …
    }

【问题2】向微博分享,点击“分享”后,过一会程序异常停止。

 原因分析:
        通过调试观察,发现ShareSDK在解析从Weibo收到的Json包时出现内存违法访问。具体位置是在解析一个数组对象时出现的问题。 ShareSDK用CCArray来存储Json中的数组对象。该问题在cocos2d-x 2.2.2版本中不会出现,但在cocos2d-x 3.0rc2版本中会出现。经代码对比发现,3.0rc2版本中的CCArray的实现与2.2.2 CCArray实现有很大不同,似乎是做了较大重构,暂不能确定是否是3.0rc2版本中CCArray实现的bug。

 解决方法:由于后续的分享结果通知成功与否只需要根据分享的状态来决定,因此我们只需解析出"status"、“action”和“platform” 这三个CCNumber类型字段的值即可。CCArray类型的对象我们并不需要,因此我们只需绕过对Array类型字段的解析和存储即可,修改如下:

// Classes/C2DXShareSDK/Android/JSON/CCJSONConverter.cpp

void CCJSONConverter::convertJsonToDictionary(cJSON *json, CCDictionary *dictionary)
{
    dictionary->removeAllObjects();
    cJSON * j = json->child;
    while (j) {
        if (j->type == cJSON_Number) {
            Ref * obj = getJsonObj(j);
            dictionary->setObject(obj, j->string);
        }
        j = j->next;
    }
}

四、其他

在使用ShareSDK做社交分享时,注意下面两个现象:
1) 第一次进行微博或微信分享时,会打开授权页面,授权后才能分享成功;
2) 微信分享窗口只有在手机联网状态下才能打开。如果手机无法联网,那微信好友、朋友圈和收藏分享将无法打开分享窗口,也不会有什么提示。

如发现本站页面被黑,比如:挂载广告、挖矿等恶意代码,请朋友们及时联系我。十分感谢! Go语言第一课 Go语言精进之路1 Go语言精进之路2 Go语言编程指南
商务合作请联系bigwhite.cn AT aliyun.com

欢迎使用邮件订阅我的博客

输入邮箱订阅本站,只要有新文章发布,就会第一时间发送邮件通知你哦!

这里是 Tony Bai的个人Blog,欢迎访问、订阅和留言! 订阅Feed请点击上面图片

如果您觉得这里的文章对您有帮助,请扫描上方二维码进行捐赠 ,加油后的Tony Bai将会为您呈现更多精彩的文章,谢谢!

如果您希望通过微信捐赠,请用微信客户端扫描下方赞赏码:

如果您希望通过比特币或以太币捐赠,可以扫描下方二维码:

比特币:

以太币:

如果您喜欢通过微信浏览本站内容,可以扫描下方二维码,订阅本站官方微信订阅号“iamtonybai”;点击二维码,可直达本人官方微博主页^_^:
本站Powered by Digital Ocean VPS。
选择Digital Ocean VPS主机,即可获得10美元现金充值,可 免费使用两个月哟! 著名主机提供商Linode 10$优惠码:linode10,在 这里注册即可免费获 得。阿里云推荐码: 1WFZ0V立享9折!


View Tony Bai's profile on LinkedIn
DigitalOcean Referral Badge

文章

评论

  • 正在加载...

分类

标签

归档



View My Stats