[转载]SqlServer 表分区以及自动化分区 - tyb1222 - 博客园

mikel阅读(1355)

[转载]SqlServer 表分区以及自动化分区 – tyb1222 – 博客园.

本文是我关于数据库分区的方案的一些想法,或许有些问题。仅供大家讨论。SQLServer (SQLServer 2005\SQLServer 2008)实现分区需要在企业版下进行.

SQLServer的分区分为大致有以下个过程:1、创建文件组用以存放数据文件 2、创建文件组用户数据文件 3、创建分区函数 4、创建分区方案  5、在分区方案下创建表

本文是在SQLServer2012 下完成的。

过程:

1、新建数据库,在属性中创建文件以及文件组。如下图:

可以在下图中选择文件组、或者新建文件组用户存放上图中新建的文件:

2、创建分区函数

CREATE PARTITION FUNCTION [partitionById](int) 
AS RANGE LEFT FOR VALUES (100, 200, 300)

3、创建分区方案

CREATE PARTITION SCHEME [partitionSchemeById] 
AS PARTITION [partitionById] --分区函数
TO ([FileGroup1], [FileGroup2],  [FileGroup3],[FileGroup4])

注意以上分区函数使用的是LEFT ,根据后面的值指明了数据库中如何存放。以上存放方式为:-∞,100],(100,200],(200,300],(300,+∞).此分区方案是依据分区函数

partitionById 创建的。那就是说以上Id的存储区间分别被放在[FileGroup1], [FileGroup2],  [FileGroup3],[FileGroup4]文件组的文件中。

4、依据分区方案创建表

复制代码
CREATE TABLE [dbo].[Account](
    [Id] [int] NULL,
    [Name] [varchar](20) NULL,
    [Password] [varchar](20) NULL,
    [CreateTime] [datetime] NULL
) ON partitionSchemeById(Id)
复制代码

注意:创建表的脚本中需要指明分区方案和分区依据列

查看某分区的数据:

SELECT * FROM 
[dbo].[Account]
WHERE $PARTITION.[partitionById](Id)=1

查询结果如下图:

 

至此,分区似乎已经结束了。但是看看后一个分区里的数据:Id>=400的全部放在了一个数据文件中。这样在有可能瓶颈就发生在了这个分区中。

如果数据不停的增长,希望分区也不断的自动增加。如:每天生成一个新的分区来存放分区新的数据。如到第二天时,新生成一个分区来存放(400,500 ]的数据。

这里我采用了SQL Job的方式来自动产生分区:

 

复制代码
DECLARE @maxValue INT,
    @secondMaxValue INT,
    @differ    INT,
    @fileGroupName VARCHAR(200),
    @fileNamePath    VARCHAR(200),
    @fileName   VARCHAR(200),
    @sql        NVARCHAR(1000)

SET @fileGroupName='FileGroup'+REPLACE(REPLACE(REPLACE(CONVERT(varchar, GETDATE(), 120 ),'-',''),' ',''),':','') 
PRINT @fileGroupName
SET @sql='ALTER DATABASE [Test] ADD FILEGROUP '+@fileGroupName
PRINT @sql
EXEC(@sql)

SET @fileNamePath='C:\Program Files\Microsoft SQL Server\MSSQL11.MSSQLINSTANCE\MSSQL\DATA\'+REPLACE(REPLACE(REPLACE(CONVERT(varchar, GETDATE(), 120 ),'-',''),' ',''),':','') +'.NDF'
SET @fileName=N'File'+REPLACE(REPLACE(REPLACE(CONVERT(varchar, GETDATE(), 120 ),'-',''),' ',''),':','') 

SET @sql='ALTER DATABASE [Test] ADD FILE (NAME='''+@fileName+''',FILENAME=N'''+@fileNamePath+''') TO FILEGROUP'+'    '+@fileGroupName
PRINT @sql
PRINT 1
EXEC(@sql)
PRINT 2

--修改分区方案,用一个新的文件组用于存放下一新增的数据
SET @sql='ALTER PARTITION SCHEME [partitionSchemeById] NEXT USED'+'    '+@fileGroupName
EXEC(@sql)
  --分区架构
PRINT 3 
SELECT @maxValue =CONVERT(INT,MAX(value))
FROM SYS.PARTITION_RANGE_VALUES PRV

SELECT @secondMaxValue = CONVERT(INT,MIN(value))
FROM 
(
    SELECT TOP 2 * FROM SYS.PARTITION_RANGE_VALUES ORDER BY VALUE DESC
)
 PRV 

SET @differ=@maxValue - @secondMaxValue 

ALTER PARTITION FUNCTION partitionById()  --分区函数
SPLIT RANGE (@maxValue+@differ)
复制代码

这样在计划里指定每天什么时候运行,下图:

 

参考:http://www.cnblogs.com/lyhabc/articles/2623685.html

[转载]技术贴:asp.net实现唯一账户在线 禁止同一帐号同时在线 asp.net实现您的帐号在别处登录,您已被迫下线! - 大骑士 - 博客园

mikel阅读(1071)

[转载]技术贴:asp.net实现唯一账户在线 禁止同一帐号同时在线 asp.net实现您的帐号在别处登录,您已被迫下线! – 大骑士 – 博客园.

技术要点:

Application 全局变量的使用

hashtable 的使用

Session 对应唯一sessionID 标志会话状态

webpage 继承 BasePage的技术

整体比较简单,主要是思想

 

复制代码
private void SetOnlineInfo(HttpContext context, string username)
        {
            Hashtable hOnline = (Hashtable)context.Application["Online"];//读取全局变量
            if (hOnline != null)
            {
                IDictionaryEnumerator idE = hOnline.GetEnumerator();
                string strKey = "";
                while (idE.MoveNext())
                {
                    if (idE.Value != null && idE.Value.ToString().Equals(username))//如果当前用户已经登录,
                    {
                        //already login            
                        strKey = idE.Key.ToString();
                        hOnline[strKey] = "XX";//将当前用户已经在全局变量中的值设置为XX
                        break;
                    }
                }
            }
            else
            {
                hOnline = new Hashtable();
            }

            hOnline[context.Session.SessionID] = username;//初始化当前用户的
            context.Application.Lock();
            context.Application["Online"] = hOnline;
            context.Application.UnLock();
        }
复制代码
复制代码
protected void CheckOtherLogin(HttpContext context)
        {

            Hashtable hOnline = (Hashtable)Application["Online"];//获取已经存储的application值
            if (hOnline != null)
            {
                IDictionaryEnumerator idE = hOnline.GetEnumerator();
                while (idE.MoveNext())
                {
                    if (idE.Key != null && idE.Key.ToString().Equals(context.Session.SessionID))
                    {
                        //already login
                        if (idE.Value != null && "XX".Equals(idE.Value.ToString()))//说明在别处登录
                        {
                            hOnline.Remove(Session.SessionID);
                            context.Application.Lock();
                            context.Application["Online"] = hOnline;
                            context.Application.UnLock();
                            context.Response.Write("<script>alert('你的帐号已在别处登陆,你被强迫下线!');window.location.href='login.aspx';</script>");//退出当前到登录页面
                            context.Response.End();
                        }
                    }
                }
            }
        }
复制代码

 demo源码下载 

另外推广下自己的没落的维持生计的小店,园友可以享受优惠

经天纬地的我 还不是每天要码代码?!

[转载]使用Lucene.net提升网站搜索速度整合记录 - Miao31 - 博客园

mikel阅读(1099)

[转载]使用Lucene.net提升网站搜索速度整合记录 – Miao31 – 博客园.

 

查询代码

复制代码
        //网站搜索代码
        public static void Search(string keyword,int typeid,int pageNo)
        {
            int onePage=20;//一页多少
            int TotalNum=1000;//一次加载多少

            if (pageNo < 0) pageNo = 0;
            if (pageNo * onePage > TotalNum)
                pageNo = TotalNum / onePage;

            //索引加载的目录
            DirectoryInfo INDEX_DIR = new DirectoryInfo(m_indexPath+"//Index//index1");
            IndexSearcher searcher = new IndexSearcher(FSDirectory.Open(INDEX_DIR), true);
            QueryParser qp = new QueryParser(Version.LUCENE_30, "keyContent", analyzer);
            Query query = qp.Parse(keyword); 
            //Console.WriteLine("query> {0}", query);

            //设置排序问题
            Sort sort = new Sort(new SortField[]{new SortField("recvTimes", SortField.INT, true),new SortField("updateTime", SortField.STRING, true)});

            //设置高亮显示的问题
            PanGu.HighLight.SimpleHTMLFormatter simpleHTMLFormatter = new PanGu.HighLight.SimpleHTMLFormatter("<font color=\"red\">", "</font>");
            PanGu.HighLight.Highlighter highlighter =new PanGu.HighLight.Highlighter(simpleHTMLFormatter,new Segment());
            highlighter.FragmentSize = 50;

            TopFieldDocs tds = searcher.Search(query,null, 1000, sort);
            Console.WriteLine("TotalHits: " + tds.TotalHits);

            /* 计算显示的条目 */
            int count = tds.ScoreDocs.Length;
            int start = (pageNo - 1) * onePage;
            int end = pageNo * onePage > count ? count : pageNo * onePage;
            //返回集合列表
            for (int i = start; i < end; i++)
            {
                Document doc = searcher.Doc(tds.ScoreDocs[i].Doc);
                string contentResult = highlighter.GetBestFragment(keyword, doc.Get("keyContent").ToString());
                Console.WriteLine(contentResult + ">>" + doc.Get("recvTimes") + "<<" + doc.Get("updateTime"));
            }

            searcher.Dispose();
        }
复制代码

 

 服务器部署的问题                                          

当你觉得本地都运行的好好的时候,发现到服务器上根本就运行不了,一直报错.

由于Lucene.net最新版本直接使用了net4.0,服务器是64们的WIN2003,而且运行的网站都还是32位的net2.0的DLL,所以升级到4.0怎么也出不来

1.运行显示的错误是提示没有.net4.0的框架,需要注册.net4.0

直接到网上找如何搞定显示ASP.NET选项卡的问题,最后找到文章方法是:

停止iis直接删除C:/WINDOWS/system32/inetsrv/MetaBase.xml中的Enable32BitAppOnWin64=”TRUE” 行

重启IIS后选项卡到是出来了,但net.2.0的网站全部挂掉,运行不起来,http://h31bt.com网站也运行不起来,

Enable32BitAppOnWin64的意思是允许运行32们的程序,所以此方法不行.

 

2.另外找的文章都是重新注册net 4.0

C:\WINDOWS\Microsoft.NET\Framework\v4.0.30319\aspnet_regiis.exe -i

开始执行试了好多次,没有效果,这里也允许了,重新安装了好几次Net4.0.

 

3.最后一次在停止IIS后,再次全部注册net2.0,4.0,然后

cscript %SYSTEMDRIVE%\inetpub\adminscripts\adsutil.vbs SET W3SVC/AppPools/Enable32bitAppOnWin64 1
重启IIS后,出现的错误终于不再是上面的.

新错误是:

Server Application Unavailable

4.通过网上查找资料 

解决办法: 在IIS中新建一个应用程序池,然后选中你的 基于.net
framework4.0的虚拟目录,点“属性”-》在“应用程序池” 中选择刚才新建的的应用程序池,点击“确定”。

最后服务器网站http://h31bt.com终于运行起来了.

  Lucene.net搜索的效果                                          

1.经过目前测试,目前服务器4G的内存搜索速度比以前需要5S左右的LIKE强了很多倍,基本上都在1S以内;

2.由于Lucene索引是基于文件的索引,从而使SQL数据库的使用压力减轻不少,这样给其它程序的整体压力减少不少,网站效果还行.

3.目前500万的数据重新建立一次索引需要1小时左右,但是网站运行的时候去更新索引速度目前感觉比较慢.比如要更新点击次数和更新时间等时,发现新的问题来了,这块的时间比较长.

4.目前考虑的方案是几天一次全部重新建立索引一次,平时只是添加数据.

希望有了解的朋友在此留言指教下lucene.net方面的性能优化问题,大家一起共同学习进步.

 

大家看累了,就移步到娱乐区http://h31bt.com 去看看速度如何,休息下…

希望大家多多推荐哦…大家的推荐才是下一篇介绍的动力…

[转载]Android UI设计准则 - _liushu的日志 eoe Android开发者社区_Android开发论坛

mikel阅读(817)

[转载]Android UI设计准则 – _liushu的日志 eoe Android开发者社区_Android开发论坛.

以下设计准则由Android 用户体验团队秉承用户利益至上的原则开发。当你发挥自己的创造力和思考的时候,请将它们纳入考虑之中,并有意识地加以实践。

设计准则
以下设计准则由Android 用户体验团队秉承用户利益至上的原则开发。当你发挥自己的创造力和思考的时候,请将它们纳入考虑之中,并有意识地加以实践。

以意想不到的方式取悦我
一个漂亮的界面,一个悉心摆放的动画,或者一个适时的声音效果,都是一种快乐的体验。精细的效果能产生一种轻松的氛围,感觉手中有一股强大可控的力量。
principles_delight.png

真实对象比按钮和菜单更加有趣
允许人们直接触摸和操作你应用中的对象。它减少了执行一项任务所需的认识上的力量,并使之更加令人舒心。
principles_real_objects.png

让我把它变成我的
人们喜欢加入个人手势,因为这让他们感觉自在与可控。提供可感的、漂亮的默认手势,但同时又考虑好玩、可选又不影响主要任务的定制项。
principles_make_it_mine.png

学会了解我
随着时间的推移,学习用户的偏好。不要反复地问用户同样的问题,将用户先前的选择列出来以供快捷选择。

简化我的生活

用语简洁
使用由简单词汇构成的短句。人们更倾向于跳过过长的句子。

图像比文字更能直达理解
考虑使用图像来解释观点。图像能捕获人们的注意力,往往比文字更有效率。

为我决定,但最终由我说了算
做最好的猜测,先做而非先问。太多的选择和决定会令人不愉。只当你可能会犯错时,才提供个‘撤消’,然后仍然先做后问。

只在我需要的时候显示我所要的
当一下子看到太多东西时,人们容易受打击。将任务和信息分解成小的、可消化的片段。隐藏当前非必须的选项,并指导人们如何走下去。

我应该总能知道我在哪里
给人们以清楚自己在哪的信心。把你应用中的不同场景设计得可区分,并使用变换效果来显示不同场景(窗口)之间的关系。提供任务进度反馈。

决不能丢失我的东西
保存用户花时间创建的东西,使得他们能随处访问。跨手机、平板电脑及计算机等平台,记住设置、个人手势以及作品。这将使得软件升级成为世界上最简单的事。

如果看起来一样,所做的事就该一样
通过在视觉上加以区别,来帮助人们区分出功能的差异来。避免出现雷同场景,即看上去一样,但对于同一输入结果不同的场景。

只在十分重要时打断我
尤如一个好的个人助理,应该保护上司不被不重要的小事打扰。人们需要专注,除非情况十分严重并且紧急,打扰会是十分令人不悦的。

给我点处处可行的诀窍
人们如果能够自己探索出东西来,将会兴奋不已。通过使用可视的模式和习惯操作,使你的应用更加易于学习。比如,猛击操作会是一个很好的导航快捷手势。

这不是我的错
在提示用户改正错误时,应该礼貌。他们在用你应用的时候,需要被认为是聪明的。如果出错了,给出明确的恢复指令,而不是将技术细节留给他们。如果你可以在后台修复错误,那当然最好。

挥洒鼓励
将复杂的任务分解成小的容易被完成的步骤。对用户的操作给出反馈,即使只是一小步的成功。

为我担负重任
通过使他们做成他们认为自己不可能做到的事,让新手感觉自己像专家一样。比如,综合多个照片效果的快捷操作可使得外行简单几步做出惊人的照片效果来。

让重要的事情能更快地完成
并非所有的操作都是平等的。确定你应用中最重要的操作,使它能很容易地被发现与使用,比如相机上的快门,又如音乐播放器上的暂停按钮。

[转载]图解Android - Binder 和 Service - 漫天尘沙 - 博客园

mikel阅读(1085)

[转载]图解Android – Binder 和 Service – 漫天尘沙 – 博客园.

Zygote启动过程 一 文中我们说道,Zygote一生中最重要的一件事就是生下了 System Server 这个大儿子,System Server 担负着提供系统 Service的重任,在深入了解这些Service 之前,我们首先要了解 什么是Service?它的工作原理是什么?

 

 1. Service是什么?

简单来说,Service就是提供服务的代码,这些代码最终体现为一个个的接口函数,所以,Service就是实现一组函数的对象,通常也称为组件。Android 的Service 有以下一些特点:

1.  请求Service服务的代码(Client)  和 Service本身(Server) 不在一个线程,很多情况下不在一个进程内。跨进程的服务称为远端(Remote)服务,跨进程的调用称为IPC。通常应用程序通过代理(Proxy)对象 来访问远端的Service。

2.  Service 可以运行在native 端(C/C++),也可以运行在Java 端。同样,Proxy 可以从native 端访问Java Service, 也可以从Java端访问native service, 也就是说,service的访问与语言无关。

3.  Android里大部分的跨进程的IPC都是基于Binder实现。

4.  Proxy 通过 Interface 类定义的接口访问Server端代码。

5.  Service可以分为匿名和具名Service. 前者没有注册到ServiceManager, 应用无法通过名字获取到访问该服务的Proxy对象。

所以,要了解Service,我们得先从 Binder 入手。

  2.  Binder

先给一张Binder相关的类图一瞰Binder全貌,从下面的类图可以看到跟Binder大致由几部分组成:

Native 实现:  IBinder,  BBinder, BpBinder, IPCThread, ProcessState, IInterface, etc

Java 实现:  IBinder, Binder, BinderProxy, Stub, Proxy.

Binder Driver: binder_proc, binder_thread, binder_node, etc.

我们将分别对这三部分进行详细的分析,首先从中间的Native实现开始。

通常来说,接口是分析代码的入口,Android中’I’ 打头的类统统是接口类(C++里就是抽象类), 自然,分析Binder就得先从IBinder下手。先看看他的定义。

复制代码
class IBinder : public virtual RefBase
{
public:
    ...
    virtual sp<IInterface>  queryLocalInterface(const String16& descriptor); //返回一个IInterface对象
    ...
    virtual const String16& getInterfaceDescriptor() const = 0; 
    virtual bool            isBinderAlive() const = 0;
    virtual status_t        pingBinder() = 0;
    virtual status_t        dump(int fd, const Vector<String16>& args) = 0;
    virtual status_t        transact(   uint32_t code,
                                        const Parcel& data,
                                        Parcel* reply,
                                        uint32_t flags = 0) = 0;
    virtual status_t        linkToDeath(const sp<DeathRecipient>& recipient,
                                        void* cookie = NULL,
                                        uint32_t flags = 0) = 0;
    virtual status_t        unlinkToDeath(  const wp<DeathRecipient>& recipient,
                                            void* cookie = NULL,
                                            uint32_t flags = 0,
                                            wp<DeathRecipient>* outRecipient = NULL) = 0;
    ...
    virtual BBinder*        localBinder();  //返回一个BBinder对象
    virtual BpBinder*       remoteBinder(); //返回一个BpBinder对象
};
复制代码

有接口必然有实现,从图中可以看出,BBinder和BpBinder都是IBinder的实现类,它们干啥用的,有啥区别?有兴趣同学可以去分别去读读他们的代码,分别在

  •  Bpinder: frameworks/native/lib/binder/BpBinder.cpp
  •  BBinder: frameworks/native/lib/binder/Binder.cpp

这里我们简单总结一下他们的区别:

接口 BBinder BpBinder
queryLocalInterface() 没有实现, 默认实现 IBinder 默认{reutrn NULL};   没有实现 IBinder 默认实现 {return NULL}
getInterfaceDescriptor()   {return sEmptyDescriptor;}       (this)->transact(INTERFACE_TRANSACTION, send, &reply);
     …
    mDescriptorCache = res;
isBinderAlive()   {return true;} {return mAlive != 0;}
pingBinder() {return NoError;} {transact(PING_TRANSACTION, send, &reply);
linkToDeath() {return INVALID_OPERATION;}   {self->requestDeathNotification(mHandle, this);}
unlinkToDeath()   {return INVALID_OPERATION;} {self->clearDeathNotification(mHandle, this);}
localBinder() {return this;} 没有实现, IBinder默认实现 {return NULL};
remoteBinder() 没有实现,IBinder默认实现 {return NULL;} {return this};
transact() {err = onTransact(code, data, reply, flags);} IPCThreadState::self()->transact(mHandle, code, data, reply, flags);
onTransact()       switch (code) {
        case INTERFACE_TRANSACTION:
            reply->writeString16(getInterfaceDescriptor());
            return NO_ERROR;        …
没有实现

 

 

 

 

 

 

 

 

 

 

 

 

看出来了吧,它们的差异在于它们是通信两端的不同实现,BBinder是服务端,而BpBinder是客户端,为什么这么说?

  1.  pingBinder, BBinder直接返回OK,而BpBinder需要运行一个transact函数,这个函数具体做什么,我们后面会介绍。
  2.  linkToDeath()是用来在服务挂的时候通知客户端的,那服务端当然不需要自己监视自己咯,所以BBinder直接返回非法,而 Bpbinder需要通过requestDeathNotification()要求某人完成这个事情,究竟是谁提供这个服务?答案后面揭晓。
  3.  在Android中,remote一般代表某个远端对象的本地代理,想象一下航空公司和机票代理,BBinder是航空公司,当然没有remote的了,那BpBinder就是机票代理了,所以remote()自然返回自己了。
  4.  Transact的英文意思是交易,就是买卖嘛,那自然transact()就是买的操作,而onTransact()就是卖的操 作,BBinder的transact()的实现就是onTransact(), 航空公司的买票当然不用通过机票代理了,直接找自己人就好了。

所以结论是,BBinder代表着服务端,而BpBinder则是它在客户端的代理,客户程序通过BpBinder的transact()发起请求,而服务器端的BBinder在onTranscat()里响应请求,并将结果返回。

可是交易肯定有目标的吧,回到航空公司和机票代理的例子,如果要订去某个地方的机票,我们怎么也得先查询一下都有那些航班,然后才能告诉机票代理订具体的航班号吧。这里的查询和预订可以看成服务的接口函数,而航班号就是我们传递给机票代理的参数。客户程序通过queryLocalInterface() 可以知道航空公司都提供哪些服务。

可是奇怪的是BBinder和BpBinder都没有实现这个接口啊,那肯定另有他人实现这个类了,这个人就是IInterface.h, 看看代码

复制代码
template<typename INTERFACE>
inline sp<IInterface> BnInterface<INTERFACE>::queryLocalInterface(
        const String16& _descriptor)
{
    if (_descriptor == INTERFACE::descriptor) return this;
    return NULL;
}
复制代码

BnInterface<INTERFACE> 对象将自己强制转换成 IInterface对象返回,看看BnInterface的定义:

复制代码
template<typename INTERFACE>
class BnInterface : public INTERFACE, public BBinder
{
public:
    virtual sp<IInterface>      queryLocalInterface(const String16& _descriptor);
    virtual const String16&     getInterfaceDescriptor() const;

protected:
    virtual IBinder*            onAsBinder();
};
复制代码

 

是一个模板类,继承了BBinder, 还有模板 INTERFACE。我们刚才已经看过,BBinder没有实现queryLocalInterface(), 而BnInterface 返回自己,可以他并没有继承IInterface, 怎么可以强制转换呢,唯一的解释就是 INTERFACE模板必须继承和实现IInterface.

复制代码
class IInterface : public virtual RefBase
{
public:
            IInterface();
            sp<IBinder>         asBinder();
            sp<const IBinder>   asBinder() const;           
protected:
    virtual                     ~IInterface();
    virtual IBinder*            onAsBinder() = 0;
};
复制代码

 

这也太简单了吧,只是定义了 从Interface 到 IBinder的转换接口 asBinder, 而刚才我们研究的queryLocalInterface() 正好反过来,说明IBinder 和 IInterface 之间是可以互转的,一个人怎么可以变成另外一个人呢?唯一的解释就是这个人有双重性格,要么他同时继承 IInterface 和 IBinder, 要么他体内拥有这两个对象同时存在,不卖关子了,在服务端,这个双重性格的人就是BnXXX, XXX 代表某个具体的服务,我们以图中的BnMediaPlayer为例,看看他的定义

复制代码
class BnMediaPlayer: public BnInterface<IMediaPlayer>
{
public:
    virtual status_t    onTransact( uint32_t code,
                                    const Parcel& data,
                                    Parcel* reply,
                                    uint32_t flags = 0);
};

class IMediaPlayer: public IInterface
{
public:
    DECLARE_META_INTERFACE(MediaPlayer);
    ...

}
复制代码

 

这下本性都露出来了,IBinder 和 IInterface 的影子都露出来了,让我们用图梳理一下 (箭头代表继承关系)

归纳一下,

  1.  BBinder 实现了大部分的IBinder 接口,除了onTransact() 和 queryLocalInterface(), getInterfaceDescriptor();
  2.  BnInterface 实现了IBinder的queryLocalInterface()和getInterfaceDescriptor(), 但是其必须借助实际的接口类。
  3.  BnMediaPlayer只是定义了onTransact(), 没有实现。
  4.  onTransact()的具体实现在Client类。

为什么搞得那么复杂?Google 是希望通过这些封装尽可能减少开发者的工作量,开发一个native的service 开发者只需要做这么几件事:

  1. 定义一个接口文件, IXXXService, 继承IInterface
  2. 定义BnXXX(), 继承 BnInterface<IXXXService)
  3. 实现一个XXXService类,继承BnXXX(), 并具体实现onTransact() 函数。

那客户端呢? 我们的目标是找到一个类,它必须同时拥有IBinder 和 IIterface的特性, 先看看BpBinder 吧

class BpBinder : public IBinder

 

跟IInterface 没有关系,那一定是别人,看看BpInterface 吧,

复制代码
template<typename INTERFACE>
class BpInterface : public INTERFACE, public BpRefBase
{
public:
    BpInterface(const sp<IBinder>& remote);
protected:
    virtual IBinder*            onAsBinder();
};
复制代码

 

我们刚才已经知道了,INTERFACE 是 IMediaPlayer, 它继承了IInterface, IInterface 的对象找到了, 但跟IBinder 没关系?只剩下BpRefBase 了,

复制代码
class BpRefBase : public virtual RefBase
{
protected:
    ...
    inline  IBinder*        remote()                { return mRemote; }
    ...
private:
    ...
    IBinder* const          mRemote;
    RefBase::weakref_type*  mRefs;
    volatile int32_t        mState;
};
复制代码

 

有了,BpRefBase 里有IBinder 成员变量,看来在客户端,没有一个类同时继承IBinder 和 IInterface, 但是有一个类继承了其一,但包含了另外一个,这种在设计模式里成为组合(Composition).

还是不太明白?还是用图解释吧,

看明白了?从BpInterface开始,通过BpRefBase 我们可以找到IBinder, 这个转换就在 asBinder() 的实现里,看看代码

复制代码
sp<IBinder> IInterface::asBinder(){
    return this ? onAsBinder() : NULL;
}

sp<const IBinder> IInterface::asBinder() const{
    return this ? const_cast<IInterface*>(this)->onAsBinder() : NULL;
}

template<typename INTERFACE>
inline IBinder* BpInterface<INTERFACE>::onAsBinder()
{
    return remote();
}

template<typename INTERFACE>
IBinder* BnInterface<INTERFACE>::onAsBinder()
{
    return this;
}
复制代码

这里印证我们上面两张图的正确性,onAsBinder是转换的发生的地方,服务端(BnInterface)的实现直接返回了自己,因为它继承了 两者,而客户端(BpInterface)则需要通过remote()(返回mRemote 成员变量)获取,因为他自己本身不是IBinder,

那个BpRefbase的mRemote是如何被赋值的?看看以下代码

//frameworks/native/libs/binder/binder.cpp
BpRefBase::BpRefBase(const sp<IBinder>& o)
    : mRemote(o.get()), mRefs(NULL), mState(0)
{
   ...
}

 

//frameworks/native/include/binder/iinterface.h
template<typename INTERFACE>
inline BpInterface<INTERFACE>::BpInterface(const sp<IBinder>& remote)
    : BpRefBase(remote)
{
}

 

复制代码
//frameworks/av/media/libmedia/IMediaPlayer.cpp
class BpMediaPlayer: public BpInterface<IMediaPlayer>
{
public:
    BpMediaPlayer(const sp<IBinder>& impl)
        : BpInterface<IMediaPlayer>(impl)
    {
    }
    ...
}
复制代码

 

原来是从子类一级一级注入的,那唯一的问题就是在哪里完成这个注入操作, 马上搜索”new BpMediaPlayer”, 奇怪,竟然没有,试试搜索”IMediaPlayer“,发现了一点线索

复制代码
   //av/media/libmedia/IMediaPlayerService.cpp

   70:     virtual sp<IMediaPlayer> create(
   71:             const sp<IMediaPlayerClient>& client, int audioSessionId) {
   72          Parcel data, reply;
   73:         ...          
   77          remote()->transact(CREATE, data, &reply); 
   78:         return interface_cast<IMediaPlayer>(reply.readStrongBinder()); //reply里读出IBinder,然后转成IMediaPlayer接口对象
   79      }
复制代码

 

这里通过interface_cast 直接把IBinder 转换成了 IMediaPlayer, interface_cast 到底有什么魔力?

template<typename INTERFACE>
inline sp<INTERFACE> interface_cast(const sp<IBinder>& obj)
{
    return INTERFACE::asInterface(obj);
}

 

继续跟进 asInterface, 结果发现里以下代码

复制代码
#define DECLARE_META_INTERFACE(INTERFACE)                               \
    static const android::String16 descriptor;                          \
    static android::sp<I##INTERFACE> asInterface(                       \
            const android::sp<android::IBinder>& obj);                  \
    virtual const android::String16& getInterfaceDescriptor() const;    \
    I##INTERFACE();                                                     \
    virtual ~I##INTERFACE();                                            \

#define IMPLEMENT_META_INTERFACE(INTERFACE, NAME)                       \
    const android::String16 I##INTERFACE::descriptor(NAME);             \
    const android::String16&                                            \
            I##INTERFACE::getInterfaceDescriptor() const {              \
        return I##INTERFACE::descriptor;                                \
    }                                                                   \
    android::sp<I##INTERFACE> I##INTERFACE::asInterface(                \
            const android::sp<android::IBinder>& obj)                   \
    {                                                                   \
        android::sp<I##INTERFACE> intr;                                 \
        if (obj != NULL) {                                              \
            intr = static_cast<I##INTERFACE*>(                          \
                obj->queryLocalInterface(                               \
                        I##INTERFACE::descriptor).get());               \
            if (intr == NULL) {                                         \
                intr = new Bp##INTERFACE(obj);                          \
            }                                                           \
        }                                                               \
        return intr;                                                    \
    }                                                                   \
复制代码

 

恍然大悟,原来在DECLARE_META_INTERFACE 这个宏里定义了asInterface, 在IMPLEMENT_META_INTERFACE 里实现了它,这里果然有一个new BpMediaPlayer! 然后把它转换成父父类 IMediaPlayer。

一切都清楚了,用一张图来表示

客户端从远端获取一个IBinder对象,接着生成BpMediaPlayer, 将其转成 IMediaPlayer 接口对象,这是用户程序看到的对象,并通过其调用接口方法,最终调到BpBinder的transact()。

问题又来了,这个transact() 怎么传递到服务端,并最终调到 onTransact()?

回想一下,onTransact() 是IBinder的接口函数吧,而且Server的IBinder实现是BBinder, 那一定有人通过某种方式得到了BBinder对象。

这个人就是Binder Driver. 为了找到真相,必须用源头开始,那就是transact()

复制代码
status_t BpBinder::transact(
    uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
{
     ...
        status_t status = IPCThreadState::self()->transact(
            mHandle, code, data, reply, flags);
    ...
    return DEAD_OBJECT;
}
复制代码

 

IPCThreadState的transact()函数相比IBinder 多了一个mHandle, 啥来历?

BpBinder::BpBinder(int32_t handle)
    : mHandle(handle)

 

构造带进来的,赶紧找“new BpBinder”, 结果在ProcessState.cpp 看到了

sp<IBinder> ProcessState::getStrongProxyForHandle(int32_t handle)
{
    ...
        IBinder* b = e->binder;
        if (b == NULL || !e->refs->attemptIncWeak(this)) {
            b = new BpBinder(handle);

 

找谁call了getStrongProxyForHandle?为了快速找到调用栈,我们在BpBinder的构造函数里加了这么几句话:

#include <utils/CallStack.h>
...
CallStack cs;
cs.update();
cs.dump("BpBinder")

 

然后得到了下面的打印

09-29 07:11:14.363  1624  1700 D BpBinder: #00  pc 0001eb34  /system/lib/libbinder.so (android::BpBinder::BpBinder(int)+260)
09-29 07:11:14.363  1624  1700 D BpBinder: #01  pc 0003b9a2  /system/lib/libbinder.so (android::ProcessState::getStrongProxyForHandle(int)+226)
09-29 07:11:14.363  1624  1700 D BpBinder: #02  pc 00032b8c  /system/lib/libbinder.so (android::Parcel::readStrongBinder() const+316) //frameworks/native/libs/binder/Parcel.cpp:247
09-29 07:11:14.363  1624  1700 D BpBinder: #03  pc 000ad9d2  /system/lib/libandroid_runtime.so //frameworks/base/core/jni/android_os_Parcel.cpp:355
09-29 07:11:14.363  1624  1700 D BpBinder: #04  pc 00029c5b  /system/lib/libdvm.so (dvmPlatformInvoke+79) //dalvik/vm/arch/x86/Call386ABI.S:128

 

#04 dvmPlatformInvork 说明这是一个Jni调用,#03 对应的代码是

return javaObjectForIBinder(env, parcel->readStrongBinder());

 

应该是Java传下来一个Parcel对象,然后由本地代码进行解析,从中读出IBinder对象,并最终返回。也就是说,远端有人将这个IBinder对象封在Parcel里。还是没有头绪?继续顺着调用栈往前看,

#02  对应于下面的代码

复制代码
status_t unflatten_binder(const sp<ProcessState>& proc,
    const Parcel& in, sp<IBinder>* out)
{
    const flat_binder_object* flat = in.readObject(false);
    ...case BINDER_TYPE_HANDLE:
                *out = proc->getStrongProxyForHandle(flat->handle);
                return finish_unflatten_binder(
                    static_cast<BpBinder*>(out->get()), *flat, in);
        }        
    }
    return BAD_TYPE;
}
复制代码

 

复制代码
#bionic/libc/kernel/common/linux/binder.h
struct flat_binder_object {
 unsigned long type;
 unsigned long flags;
 union {
    void *binder;
    signed long handle;
 };
 void *cookie;
};
复制代码

 

原来mHandle就是flat_binder_object里面的handle, 它只是一个数字!这个数据结构定义在Kernel里,是经过Kernel转手的。越来越乱了,赶紧整理一下思路:

1.  Kernel 封装了一个数据结构(flat_binder_object),里面带有一个数字(mHandle)。

2.  客户端获取这个数字后,生成一个BpBinder的对象。

3.  然后当客户端需要访问远端服务的时候,将这个数字附上。

回到现实生活,机票代理需要向航空公司查询或订票的话,一定要知道是哪个航空公司,莫非这个号就是航空公司的编号?

恭喜你,就是那么简单,这个号就对应了服务器端的提供的某一个服务,Android 中每个Service都有一个号码(根据创建时间递增,0号Service 是ServiceManager,让我用下面的图来描述整个过程吧,

 

1. 在已知服务名的情况,App 通过getService() 从ServiceManager 获取该服务的信息,该信息封装在Parcel里。

2. 应用程序收到返回的这个Parcel对象(通过Kernel), 从中读取出flat_binder_object 对象,最终从对象中得到服务对应的服务号,mHandle.

3. 以该号码作为参数输入生成一个IBinder对象(实际是BpBinder)。

4. 应用获取该对象后,通过asInterface(IBinder*) 生成服务对应的Proxy对象(BpXXX),并将其强转为接口对象(IXXX),然后直接调用接口函数。

5. 所有的接口对象调用最终会走到BpBinder->transact()函数,这个函数调用IPCThreadState->transact()并以Service号作为参数之一。

6. 最终通过系统调用ioctrl() 进入内核空间,Binder驱动根据传进来的Service 号寻找该Service正处于等待状态的Binder Thread, 唤醒它并在该线程内执行相应的函数,并返回结果给APP。

强调一下:

1. 从应用程序的角度来看,他只认识IBinder 和 IMediaPlayer 这两个类,但实际的实现在BpBinder 和 BpMediaPlayer, 这正是设计模式所推崇的“ Programs to interface, not implementations”, 可以说Android 一个严格遵循设计模式思想精心设计的系统,我们将来会就这个话题进行深入的探讨。

2. 客户端应该层层的封装,最终的目的就是获取和传递这个mHandle 值,从图中,我们看到,这个mHandle至来自与IServiceManager, 他是一个管理其他服务的服务,通过服务的名字我们可以拿到这个服务对应的Handle号,类似网络域名服务系统。但是我们说 了,IServiceManager也是服务啊,要访问他我们也需要一个Handle号啊,对了,就如同你必须为你的机器设置DNS 服务器地址,你才能获得DNS 服务。在Android系统里, 默认的将ServiceManger的Handler号设为0,这样,我们通过调用 getStrongProxyForHandle(0) 就可以拿到ServiceManager 的IBinder 对象,当然,系统提供一个 getService(char *)函数来帮助完成这个过程。

3.  Android Binder 的设计目标就是让访问远端服务就像调用本地函数一样简单,但是远端的对象不在本地控制之内,我们必须保证调用过程中远端的对象不能被析构,否则本地应用程 序将很有可能崩溃。同时,万一远端服务异常退出,如Crash, 本地对象必须知晓从而避免后续的错误。Android 通过 智能指针 和 DeathNotification 来支持这两个要求,我们会有专门的章节介绍智能指针,这里我们会在后面简单介绍 DeathNotifycation的实现原理。

Binder的上层设计逻辑简单介绍完毕。我们接下来看看Binder的底层设计。

3. Binder Driver

我们知道,Linux的进程空间相互独立,两个进程只能通过Kernel space 进行互访,所有的IPC 机制,最底层的实现都是在Kernel space.  Binder 也是如此,通过系统调用切入内核态,内核寻找到提供服务的进程,唤醒他并进入用户空间,然后在某个线程里调用onTransact(), 完成特定操作,并将结果返回到应用程序。那Binder Driver是如何搭起连接服务端和客户端的这座桥梁呢?

先看看binder driver 内部的数据结构吧:

下面一一进行解释:

1.  Binder node:

我们前面说过Service 其实是一个存在于某个进程里的对象,因此,进程PID 和 对象地址可以唯一的标识一个Service 对象,除此之外,因为这个对象可能被很多应用所使用,必须有引用计数来管理他的生命周期。这些工作都必须在内核里完成,Binder node 就是这样一个结构体来管理每个Service 对象。

复制代码
struct binder_node {
    int debug_id;              //kernel内部标识node的id
    struct binder_work work;   
    union {
        struct rb_node rb_node;
        struct hlist_node dead_node;
    };
    struct binder_proc *proc;  //Service所在进程的结构体
    struct hlist_head refs;    //双向链表头,链表里存放一系列指针,指向引用该Service的binder_ref对象,
    int internal_strong_refs;  //内部强引用计数
    int local_weak_refs;       //弱引用计数
    int local_strong_refs;     //强引用计数
    binder_ptr __user ptr;     //Service对象地址
    binder_ptr __user cookie;  
    unsigned has_strong_ref:1; 
    unsigned pending_strong_ref:1; 
    unsigned has_weak_ref:1;
    unsigned pending_weak_ref:1;
    unsigned has_async_transaction:1;
    unsigned accept_fds:1;
    unsigned min_priority:8;
    struct list_head async_todo;
};
复制代码

 

 2. binder_ref

binder_ref 描述了每个对服务对象的引用,对应与Client端。如上图所示,每个Ref通过node指向binder_node. 一个进程所有的binder_ref通过两个红黑树(RbTree)进行管理,通过binder_get_ref() 和 binder_get_ref_for_node快速查找。

复制代码
struct binder_ref {
    /* Lookups needed: */
    /*   node + proc => ref (transaction) */
    /*   desc + proc => ref (transaction, inc/dec ref) */
    /*   node => refs + procs (proc exit) */
    int debug_id;
    struct rb_node rb_node_desc; 
    struct rb_node rb_node_node;
    struct hlist_node node_entry;   
    struct binder_proc *proc;           //应用进程
    struct binder_node *node;
    uint32_t desc;
    int strong;
    int weak;
    struct binder_ref_death *death;  //如果不为空,则client想获知binder的死亡
};
复制代码

 

 3. binder_proc

一个进程既包含的Service对象,也可能包含对其他Service对象的引用. 如果作为Service对象进程,它可能会存在多个Binder_Thread。这些信息都在binder_proc结构体进行管理。

复制代码
struct binder_proc {
    struct hlist_node proc_node; //全局链表 binder_procs 的node之一
    struct rb_root threads; //binder_thread红黑树,存放指针,指向进程所有的binder_thread, 用于Server端
    struct rb_root nodes;   //binder_node红黑树,存放指针,指向进程所有的binder 对象
    struct rb_root refs_by_desc; //binder_ref 红黑树,根据desc(service No) 查找对应的引用
    struct rb_root refs_by_node; //binder_ref 红黑树,根据binder_node 指针查找对应的引用
    int pid;
    struct vm_area_struct *vma;
    struct mm_struct *vma_vm_mm;
    struct task_struct *tsk;
    struct files_struct *files;
    struct hlist_node deferred_work_node;
    int deferred_work;
    void *buffer;
    ptrdiff_t user_buffer_offset;

    struct list_head buffers;
    struct rb_root free_buffers;
    struct rb_root allocated_buffers;
    size_t free_async_space;

    struct page **pages;
    size_t buffer_size;
    uint32_t buffer_free;
    struct list_head todo; //task_list, binder_work链表,存放指针最终指向某个binder_transaction对象
    wait_queue_head_t wait;
    struct binder_stats stats;
    struct list_head delivered_death;
    int max_threads;
    int requested_threads;
    int requested_threads_started;
    int ready_threads;
    long default_priority;
    struct dentry *debugfs_entry;
};
复制代码

 

为了实现快速的查找,binder_proc内部维护了若干个数据结构,如图中黄色高亮所示,

4. binder_transaction

每个transact() 调用在内核里都会生产一个binder_transaction 对象,这个对象会最终送到Service进程或线程的todo队列里,然后唤醒他们来最终完成onTransact()调用。

复制代码
struct binder_transaction {
    int debug_id;             //一个全局唯一的ID
    struct binder_work work; // 用于存放在todo链表里
    struct binder_thread *from; //transaction 发起的线程。如果BC_TRANSACTION, 则为客户端线程,如果是BC_REPLY, 则为服务端线程。
    struct binder_transaction *from_parent; //上一个binder_transaction. 用于client端
    struct binder_proc *to_proc; //目标进程
    struct binder_thread *to_thread; //目标线程
    struct binder_transaction *to_parent; //上一个binder_transaction, 用于server端
    unsigned need_reply:1;
    /* unsigned is_dead:1; */    /* not used at the moment */

    struct binder_buffer *buffer;
    unsigned int    code;
    unsigned int    flags;
    long    priority;
    long    saved_priority;
    kuid_t    sender_euid;
};
复制代码

 

5. binder_thread

binder_proc里的threads 红黑树存放着指向binder_thread对象的指针。这里的binder_thread 不仅仅包括service的binder thread, 也包括访问其他service的调用thread. 也就是说所有与binder相关的线程都会在binder_proc的threads红黑树里留下记录。binder_thread里最重要的两个成员变 量是 transaction_stack 和 wait.

复制代码
struct binder_thread {
    struct binder_proc *proc;
    struct rb_node rb_node; //红黑树节点
    int pid;
    int looper;  //
    struct binder_transaction *transaction_stack; //transaction栈
    struct list_head todo;
    uint32_t return_error; 
    uint32_t return_error2; 
    wait_queue_head_t wait; //等待队列,用于阻塞等待
    struct binder_stats stats;
};
复制代码

在binder_proc里面我们也能看到一个wait 队列,是不是意味着线程既可以在proc->wait上等待,也可以在thread->wait上等待?binder driver 对此有明确的用法,所有的binder threads (server 端)都等待在proc->wait上。因为对于服务端来说,用哪个thread来响应远程调用请求都是一样的。然而所有的ref thread(client端)的返回等待都发生在调用thread的wait 队列,因为,当某个binder thread 完成服务请求后,他必须唤醒特定的等待返回的线程。但是有一个例外,在双向调用的情况下,某个Server端的thread将会挂在 thread->wait上等待,而不是proc->wait. 举个例子,假设两个进程P1 和 P2,各自运行了一个Service, S1,S2, P1 在 thread T1 里调用S2提供的服务,然后在T1->wait里等待返回。S2的服务在P2的binder thread(T2)里执行,执行过程中,S2又调到S1里的某个接口,按理S1 将在P1的binder thread T3里执行, 如果P1接下来又调到了P2,那又会产生新的进程 T4, 如果这个反复调用栈很深,需要耗费大量的线程,显然这是非常不高效的设计。所以,binder driver 里做了特殊的处理。当T2 调用 S1的接口函数时,binder driver 会遍历T2的transaction_stack, 如果发现这是一个双向调用(binder_transaction->from->proc 等于P1), 便会唤醒正在等待reply的T1,T1 完成这个请求后,继续等待S2的回复。这样,只需要最多两个Thread就可以完成多层的双向调用。

binder_thread里的transaction_stack 是用链表实现的堆栈, 调用线程和服务线程的transaction有着不同的堆栈。下图是上面这个例子的堆栈情形:

 

6. binder_ref_death

binder_ref 记录了从client进程到server进程某个service的引用,binder_ref_death 是binder_ref的一个成员变量,它的不为空说明了client进程想得到这个service的死亡通知(严格意义上讲,是service所在进程 的死亡通知,因为一个进程一个/dev/binder的fd, 只有进程死亡了,driver才会知晓,通过 file_operations->release 接口)。

struct binder_ref_death {
    struct binder_work work;
    binder_ptr __user cookie;
};

 

我们可以下面一张时序图来了解binder death notifycation 的全过程。

 

7. binder_work

从应用程序角度来看,所有的binder调用都是同步的。但在binder driver 内部,两个进程间的交互都是异步的,一个进程产生的请求会变成一个binder_work, 并送入目标进程或线程的todo 队列里,然后唤醒目标进程和线程来完成这个请求,并阻塞等待结果。binder_work的定义如下:

复制代码
struct binder_work {
    struct list_head entry;
    enum {
        BINDER_WORK_TRANSACTION = 1,
        BINDER_WORK_TRANSACTION_COMPLETE,
        BINDER_WORK_NODE,
        BINDER_WORK_DEAD_BINDER,
        BINDER_WORK_DEAD_BINDER_AND_CLEAR,
        BINDER_WORK_CLEAR_DEATH_NOTIFICATION,
    } type;
};
复制代码

 

很简单,其实只定义了一个链表的节点和work的类型。

8. binder_buffer

进程间通信除了命令,还有参数和返回值的交换,要将数据从一个进程的地址空间,传到另外一个进程的地址空间,通常需要两次拷贝,进程A -> 内核 -> 进程B。binder_buffer 就是内核里存放交换数据的空间(这些数据是以Parcel的形式存在)。为了提高效率,Android 的 binder 只需要一次拷贝,因为binder 进程通过mmap将内核空间地址映射到用户空间,从而可以直接访问binder_buffer的内容而无需一次额外拷贝。binder_buffer由内 核在每次发起的binder调用创建,并赋给binder_transaction->buffer. binder driver 根据binder_transaction 生产 transaction_data(包含buffer的指针而非内容), 并将其复制到用户空间。

9. flat_binder_obj

前面我们说过,<proc, handle> 可以标识一个BpBinder 对象,而<proc, ptr> 可以标识一个BBinder对象。Binder Driver 会收到来自与BpBinder 和 BBinder的系统调用,它是如何判别它们的身份呢?答案就在flat_binder_obj里,先看看它的定义,

复制代码
struct flat_binder_object {
 unsigned long type;  //见下面定义
 unsigned long flags;
 union {
 void *binder;            //BBinder,通过它driver可以找到对应的node
 signed long handle; //BpBinder,根据它driver可以找到对应的ref
 };
 void *cookie;
};

enum {
 BINDER_TYPE_BINDER = B_PACK_CHARS('s', 'b', '*', B_TYPE_LARGE),
 BINDER_TYPE_WEAK_BINDER = B_PACK_CHARS('w', 'b', '*', B_TYPE_LARGE),
 BINDER_TYPE_HANDLE = B_PACK_CHARS('s', 'h', '*', B_TYPE_LARGE),
 BINDER_TYPE_WEAK_HANDLE = B_PACK_CHARS('w', 'h', '*', B_TYPE_LARGE),
 BINDER_TYPE_FD = B_PACK_CHARS('f', 'd', '*', B_TYPE_LARGE),
};
复制代码

union表明了在Server端和Client端它有着不同的解读。type则表明了它的身份。binder driver 根据它可以找到BpBinder 和 BBinder 在内核中相对应的对象 (ref 或 node). flat_binder_obj 封装在parcel里,详见Parcel.cpp.

至此,binder driver里面重要的数据结构都介绍完了,大家对binder driver的工作原理也有了大致的了解,这里再稍作总结:

1.  当一个service向binder driver 注册时(通过flat_binder_object), driver 会创建一个binder_node, 并挂载到该service所在进程的nodes红黑树。

2.  这个service的binder线程在proc->wait 队列上进入睡眠等待。等待一个binder_work的到来。

3.  客户端的BpBinder 创建的时候,它在driver内部也产生了一个binder_ref对象,并指向某个binder_node, 在driver内部,将client和server关联起来。如果它需要或者Service的死亡状态,则会生成相应的 binfer_ref_death.

4.  客户端通过transact() (对应内核命令BC_TRANSACTION)请求远端服务,driver通过ref->node的映射,找到service所在进程,生产一个 binder_buffer, binder_transaction 和 binder_work 并插入proc->todo队列,接着唤醒某个睡在proc->wait队列上的Binder_thread. 与此同时,该客户端线程在其线程的wait队列上进入睡眠,等待返回值。

5.  这个binder thread 从proc->todo 队列中读出一个binder_transaction, 封装成transaction_data (命令为 BR_TRANSACTION) 并送到用户空间。Binder用户线程唤醒并最终执行对应的on_transact() 函数。

6.  Binder用户线程通过transact() 向内核发送 BC_REPLY命令,driver收到后从其thread->transaction_stack中找到对应的 binder_transaction, 从而知道是哪个客户端线程正在等待这个返回。

7.  Driver 生产新的binder_transaction (命令 BR_REPLY), binder_buffer, binder_work, 将其插入应用线程的todo对立,并将该线程唤醒。

8.  客户端的用户线程收到回复数据,该Transaction完成。

9.  当service所在进程发生异常退出,driver 的 release函数被调到,在某位内核work_queue 线程里完成该service在内核态的清理工作(thread,buffer,node,work…), 并找到所有引用它的binder_ref, 如果某个binder_ref 有不为空的binder_ref_death, 生成新的binder_work, 送人其线程的todo 对立,唤醒它来执行剩余工作,用户端的DeathRecipient 会最终被调用来完成client端的清理工作。

下面这张时序图描述了上述一个transaction完成的过程。不同的颜色代表不同的线程。注意的是,虽然Kernel和User space 线程的颜色是不一样的,但所有的系统调用都发生在用户进程的上下文里(所谓上下文,就是Kernel能通过某种方式或者当前关联的进程(通过Kernel 的current 宏),并完成进程相关的操作,比如说唤醒某个睡眠的线程,或跟用户空间交换数据,copy_from, copy_to, 与之相对应的是中断上下文,其完全异步触发,因此无法做任何与进程相关的操作,比如说睡眠,锁等)。

 

4. Java Binder

Binder 的学习已经接近尾声了,我们已经研究了Binder Driver, C/C++的实现,就差最后一个部分了,Binder在Java端的实现了。Java端的实现与Native端类似,我们用下面的表格和类图概括他们的关系

Native Java Note
IBinder IBinder
IInterface IInterface
IXXX IXXX aidl文件定义
BBinder Binder  通过JavaBBinder类作为桥梁
BpBinder BinderProxy  通过JNI访问Native的实现
BnInterface N/A
BpInterface   N/A
BnXXX Stub aidl工具自动生成   
BpXXX     Proxy aidl工具自动生成 

 

 

 

 

 

 

 

 

 

可见,Java较Native端实现简单很多,通过Aidl工具来实现类似功能。所以,要实现一个Java端的service,只需要做以下几件事情:

1. 写一个.aidl文件,里面用AIDL语言定义一个接口类IXXX。

2.在Android.mk里加入该文件,这样编译系统会自动生成一个IXXX.java, 放在out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/core 下面。

3. 在服务端,写一个类,扩展IXXX.Stub,具体实现IXXX的接口函数。

 

[转载]cocos2d-x 大型ARPG手游研发----精灵的八面玲珑 - zisouTags - 博客园

mikel阅读(919)

[转载]【cocos2d-x 大型ARPG手游研发—-精灵的八面玲珑】 – zisouTags – 博客园.

  继续上一篇文章继续聊吧,这章内容会比较多,也会附上代码,很多朋友加了群, 大家在群里面探讨了很多东西,这让大家都觉得受益匪浅,这便是极好的,废话不多了,精灵是游戏的重要组成部分,那ARPG里面的精灵必然是要做得很细的, 因为精灵要能动,能跑,能打,甚至要能做各种交互动作等等。

      大家可以看一下下面的题,是精灵制作的流程思路:

 

  上图的人物素材来自于网络流传的梦幻西游,地图还有其他素材是以前公司同事制 作的,如果游戏正式上线,会换一套完整的自制的素材。图中大家可以看到一个人物有很多部件组合而成,高端一点的游戏甚至部件多达几十上百种,甚至做出骨骼 动画。不过以我现在的做法是按照帧动画方式实现各个人物的动作,人物的body部分细节处理会很多,还有大家看到图中的字体(字体渲染描边,我做的是最简 单的了,两个LABEL合成在一起,效果能达到就行),由于人物要有方向,而且我们一次性就做8方向的人物动画,那就需要8个方向的连帧图片:

  首先,要组装起来我们首先要建一个实体角色类,这个类里面不放别的,就统一放角色的部件属性:

MainRoledata.h类
复制代码
#ifndef _MAIN_ROLE_DATA_
#define _MAIN_ROLE_DATA_

#include "cocos2d.h"
#include "../Commen_ActionToDo.h"
#include "../Commen_Direction.h"
USING_NS_CC;

class MainRoledata
{
public :

    //人物的TAG
    int tags;
    //人物姓名
    CCString* spiritname;
    //人物初始坐标
    CCPoint nowpoint;
    //人物默认像素图
    CCString* spiritUrl;
    //人物站立像素图路径
    CCString* spiritUrl_zhan;
    //人物跑动像素图路径
    CCString* spiritUrl_pao;
    //人物攻击像素图路径
    CCString* spiritUrl_attack;
    //人物施法像素图路径/增加人物BUF
    CCString* spiritUrl_magic;
    //人物站立最大帧
    int maxcut_zhan;
    //人物跑动最大帧
    int maxcut_pao;
    //人物战斗最大帧
    int maxcut_attack;
    //人物施法最大帧
    int maxcut_magic;
    //人物当前动作
    Commen_ActionToDo acttodo;
    //人物当前朝向
    Commen_Direction dir;
    //动画时间
    float actiontime;

};

#endif
复制代码

枚举几个方向,和动作的类:

复制代码
enum Commen_Direction
{
    up=0,
    down=1,
    lefts=2,
    rigth=3,
    rigth_up=4,
    rigth_down=5,
    left_down=6,
    left_up=7
};
复制代码
复制代码
enum Commen_ActionToDo
{
    run=1,
    stand=2,
    attack=3,
    death=4,
    funny=5,
    magicup=6
};
复制代码

OK,然后配置精灵数据,建了一个GetNPCData.cpp,GetNPCData.h,主要就是拿来初始化数据,大致的思路是要将上面的Model填充数据,相信大家

能够用很多种方式去实现,填充数据(读取XML配置文件,直接写在代码中配置);

接下来我们正式组装完整的八面玲珑的精灵,建立SpiritsPlayer.cpp,SpiritsPlayer.h;

 文件内容如下:

复制代码
#ifndef _SPIRIT_PLAYER_
#define _SPIRIT_PLAYER_

#include "cocos2d.h"
#include "../Commen_ActionToDo.h"
#include "../Commen_Direction.h"
#include "../GameData/MainRoledata.h"
#include "../Commen/PublicShowUI.h"
#include "../Effects/EffectsCommen.h"

USING_NS_CC;

class SpiritsPlayer : cocos2d::CCSprite 
{
public: 

    CCSprite* npc;
    CCSprite* yinzi;
    CCSprite* sp_liaotianbd;

    PublicShowUI* p_ui_name;

    CCArray *stringArray;
    CCAnimate* playdonghua;
    CCAnimate* playdonghua2;
    Commen_Direction move_dir;

    bool endflag;
    bool endflag2;
    bool thiszhujiao_flag;

    void Spirits_talkabout_hid();

    SpiritsPlayer(MainRoledata roledata,int zOrder,bool zhujiaoflag);
    ~SpiritsPlayer(void);
    CCAnimation* getNowAnt(MainRoledata roledata);
    CCAnimate* updateNowAnt(MainRoledata roledata);
    void updateNpcPoint(CCPoint newpoint);
    void moveTomap_dir(CCPoint newpoint);
    void moveTomap_move(int uestime,CCPoint newpoint,bool npcflag);
    //人物移动完成的回调
    void moveoverCallBack(void);
    //普通NPC移动完成的回调
    void moveoverCallBackforNpc(void);
    //根据点击坐标获得人物的朝向
    Commen_Direction getNowPointDir(CCPoint newpoint);
    // 触摸点是否在精灵上
    bool isTouchInside(CCPoint thisPos);
    //移动方式
    void movemethod(int uestime,CCPoint newpoint);

private:
    //角色基本数据
    MainRoledata thisroledata;
    CCFiniteTimeAction *actall;
    CCActionInterval* act_moveto_zi;
    CCActionInterval* act_moveto_npc;
    CCActionInterval* act_moveto_yinzi;
    CCActionInterval* act_moveto_eff;
    CCActionInterval* act_moveto_eff_zhujiao;
    CCFiniteTimeAction *actbackfun; 
    int flag ;

private:
    CCRect rect();

};
#endif//_SPIRIT_PLAYER_


  
复制代码
复制代码
#include "../ImagePaths.h"
#include "../GameData/GetNPCData.h"
#include "../Commen/FontChina.h"

SpiritsPlayer::SpiritsPlayer(MainRoledata roledata,int zOrder,bool zhujiaoflag)
{

    //先初始化部分数据
    thisroledata = roledata;
    act_moveto_zi =NULL;
    act_moveto_npc =NULL;
    act_moveto_yinzi =NULL;
    actall=NULL;
    thiszhujiao_flag = zhujiaoflag;
    p_ui_name = new PublicShowUI();
    flag = 0;

    npc = SpiritsPlayer::create(roledata.spiritUrl->getCString());
    if(npc==NULL)
    {
        CCLog("图层路径有误,请检查路径");
        return;
    }
    //设置NPC初始位置坐标(该坐标取决于当前画层)
    npc->setPosition(roledata.nowpoint);
    //NPC动画设置
    playdonghua = SpiritsPlayer::updateNowAnt(roledata);
    npc->runAction(playdonghua);

    /**开始添加角色各部件**/
    //添加角色名称
    CCLabelTTF* label = CCLabelTTF::create(roledata.spiritname->getCString(), "微软雅黑",12);
    label->setColor(ccWHITE);
    label->setDirty(true);
    label->setPosition(ccp(npc->getContentSize().width/2,npc->getContentSize().height+6));

    CCLabelTTF* labelback = CCLabelTTF::create(roledata.spiritname->getCString(), "微软雅黑",12);
    labelback->setColor(ccBLACK);
    labelback->setDirty(true);
    labelback->setPosition(ccp(npc->getContentSize().width/2+1,npc->getContentSize().height+6-1));

    //添加NPC人物脚下阴影
    yinzi = CCSprite::create(p_yinzi);
    if(yinzi==NULL)
    {
        CCLog("图层路径有误,请检查路径");
        return;
    }
    if(zhujiaoflag==true)
    {
        yinzi->setPosition(ccp(npc->getContentSize().width/2,12));
    }
    else
    {
        yinzi->setPosition(ccp(npc->getContentSize().width/2,1));
    }

    npc->addChild(yinzi,-1,110);
    npc->addChild(label,2,111);
    npc->addChild(labelback,1,112);

}

cocos2d::CCRect SpiritsPlayer::rect()
{
    //获取精灵区域大小
    return CCRectMake(npc->getPositionX()- npc->getContentSize().width  * npc->getAnchorPoint().x,npc->getPositionY()-npc->getContentSize().height* npc->getAnchorPoint().y,npc->getContentSize().width, npc->getContentSize().height); 

}

bool SpiritsPlayer::isTouchInside(CCPoint thisPos)
{
    CCPoint localPos = thisPos;
    CCRect rc = rect();
    bool isTouched = rc.containsPoint(localPos);
    if (isTouched == true) {
        CCLog(FontChina::G2U("触发点击"));

    }else
    {
        CCLog(FontChina::G2U("未点击"));
    }
    return isTouched;
}

void SpiritsPlayer::Spirits_talkabout_hid()
{
    CCLog(FontChina::G2U("************调用了*****************"));
}

CCAnimate* SpiritsPlayer::updateNowAnt(MainRoledata roledata)
{
    //NPC动画
    CCAnimation* donghua = SpiritsPlayer::getNowAnt(roledata);
    if(roledata.actiontime>0)
    {
        donghua->setDelayPerUnit(roledata.actiontime/roledata.maxcut_zhan);
    }
    else  
    {
        donghua->setDelayPerUnit(2.0f/15.0f);//执行默认时间
    }
    donghua->setRestoreOriginalFrame(true);
    donghua->setLoops(-1);
    CCAnimate* playdonghua = CCAnimate::create(donghua);

    return playdonghua;
}

/*************
* 主角位移移动
*************/
void SpiritsPlayer::moveTomap_move(int uestime, CCPoint newpoint,bool npcflag)
{
    if(npcflag==true)
    {
        actbackfun = CCCallFunc::create(this, callfunc_selector(SpiritsPlayer::moveoverCallBackforNpc));
    }
    else
    {
        actbackfun = CCCallFunc::create(this, callfunc_selector(SpiritsPlayer::moveoverCallBack));
    }
    movemethod(uestime,newpoint);
}

void SpiritsPlayer::movemethod(int uestime,CCPoint newpoint)
{
    npc->stopAction(actall);
    act_moveto_npc = CCMoveTo::create(uestime,ccp(newpoint.x,newpoint.y+20));
    actall = CCSequence::create(act_moveto_npc,actbackfun,NULL);
    npc->runAction(actall);
}

/*************
* 改变移动方向
*************/
void SpiritsPlayer::moveTomap_dir(CCPoint newpoint)
{
    GetNPCData npcdata = GetNPCData();
    npcdata.GetNPCchapter1();
    move_dir=SpiritsPlayer::getNowPointDir(newpoint);
    npcdata.role_player.dir=move_dir;
    npcdata.role_player.acttodo = run;
    npcdata.role_player.actiontime=0.5;
    npc->stopAction(playdonghua);
    playdonghua = SpiritsPlayer::updateNowAnt(npcdata.role_player);
    npc->runAction(playdonghua);
}

/*************
* 根据点击坐标获得人物的朝向
*************/
Commen_Direction SpiritsPlayer::getNowPointDir(CCPoint newpoint)
{
    Commen_Direction thisdir = rigth_down; //默认为右下
    //计算移动数据
    float center_x,center_y,npc_x,npc_y;
    int move_x,move_y;
    //更新NPC方向,状态
    CCPoint origin = CCDirector::sharedDirector()->getVisibleOrigin();
    CCSize size = CCDirector::sharedDirector()->getWinSize();

    center_x = size.width/2;
    center_y = size.height/2;
    npc_x = npc->getPositionX();
    npc_y = npc->getPositionY();

    move_x =  (int)(npc_x -newpoint.x );
    move_y =  (int)(npc_y -newpoint.y - 20);

    if(move_x>=10&&move_y<=-10)
    {
        //左上
        thisdir = left_up;
    }
    else if(move_x>=10&&move_y>=10)
    {
        //左下
        thisdir = left_down;
    }
    else if(move_x<=-10&&move_y<=-10)
    {
        //右上
        thisdir = rigth_up;
    }
    else if(move_x<=-10&&move_y>=10)
    {
        //右下
        thisdir =rigth_down;
    }
    else if(move_x>-10&&move_x<10&&move_y>0)
    {
        //
        thisdir =down;
    }
    else if(move_x>-10&&move_x<10&&move_y<0)
    {
        //
        thisdir =up;
    }
    else if(move_x>0&&move_y>-10&&move_y<10)
    {
        //
        thisdir = lefts;
    }
    else if(move_x<0&&move_y>-10&&move_y<10)
    {
        //
        thisdir =rigth;
    }
    return thisdir;
}

/*************
* 移动完成后的回调
*************/
void SpiritsPlayer::moveoverCallBack()
{
    //移动完成之后恢复站立状态
    GetNPCData npcdata = GetNPCData();
    npcdata.GetNPCchapter1();
    npcdata.role_player.dir=move_dir;
    npcdata.role_player.acttodo = stand;
    npcdata.role_player.actiontime=1.1f;
    npc->stopAction(playdonghua);
    playdonghua = SpiritsPlayer::updateNowAnt(npcdata.role_player);
    npc->runAction(playdonghua);
}

/*************
* 普通NPC移动完成后的回调
*************/
void SpiritsPlayer::moveoverCallBackforNpc()
{

}

/*************
* 点击瞬移至此
*************/
void SpiritsPlayer::updateNpcPoint(CCPoint newpoint)
{
    p_ui_name->updataGameText(ccp(newpoint.x,newpoint.y+npc->getContentSize().height/2+10));
    npc->setPosition(newpoint);
    yinzi->setPosition(ccp(newpoint.x,newpoint.y-npc->getContentSize().height/2+5));
}

/*********************
* 八方向人物动作合成器
*********************/
CCAnimation* SpiritsPlayer::getNowAnt(MainRoledata roledata)
{
    CCAnimation* thisdonghua = CCAnimation::create();
    switch (roledata.dir)
    {
    case up:

        switch (roledata.acttodo)
        {
        case run:
            for(int i = 0; i<=roledata.maxcut_pao ; i++)
            {
                char donghuaurl[100] = {0};
                sprintf(donghuaurl,"%s06%03d.png",roledata.spiritUrl_pao->getCString(),i);
                thisdonghua->addSpriteFrameWithFileName(donghuaurl);
            }
            break;
        case stand:
            for(int i = 0; i<=roledata.maxcut_zhan ; i++)
            {
                char donghuaurl[100] = {0};
                sprintf(donghuaurl,"%s06%03d.png",roledata.spiritUrl_zhan->getCString(),i);
                thisdonghua->addSpriteFrameWithFileName(donghuaurl);
            }
            break;
        case attack:
            break;
        case death:
            break;
        case funny:
            break;
        default:
            break;
        }

        break;
    case down:
        switch (roledata.acttodo)
        {
        case run:
            for(int i = 0; i<=roledata.maxcut_pao ; i++)
            {
                char donghuaurl[100] = {0};
                sprintf(donghuaurl,"%s04%03d.png",roledata.spiritUrl_pao->getCString(),i);
                thisdonghua->addSpriteFrameWithFileName(donghuaurl);
            }
            break;
        case stand:
            for(int i = 0; i<=roledata.maxcut_zhan ; i++)
            {
                char donghuaurl[100] = {0};
                sprintf(donghuaurl,"%s04%03d.png",roledata.spiritUrl_zhan->getCString(),i);
                thisdonghua->addSpriteFrameWithFileName(donghuaurl);
            }
            break;
        case attack:
            break;
        case death:
            break;
        case funny:
            break;
        default:
            break;
        }
        break;
    case lefts:
        switch (roledata.acttodo)
        {
        case run:
            for(int i = 0; i<=roledata.maxcut_pao ; i++)
            {
                char donghuaurl[100] = {0};
                sprintf(donghuaurl,"%s05%03d.png",roledata.spiritUrl_pao->getCString(),i);
                thisdonghua->addSpriteFrameWithFileName(donghuaurl);
            }
            break;
        case stand:
            for(int i = 0; i<=roledata.maxcut_zhan ; i++)
            {
                char donghuaurl[100] = {0};
                sprintf(donghuaurl,"%s05%03d.png",roledata.spiritUrl_zhan->getCString(),i);
                thisdonghua->addSpriteFrameWithFileName(donghuaurl);
            }
            break;
        case attack:
            break;
        case death:
            break;
        case funny:
            break;
        default:
            break;
        }
        break;
    case rigth:
        switch (roledata.acttodo)
        {
        case run:
            for(int i = 0; i<=roledata.maxcut_pao ; i++)
            {
                char donghuaurl[100] = {0};
                sprintf(donghuaurl,"%s07%03d.png",roledata.spiritUrl_pao->getCString(),i);
                thisdonghua->addSpriteFrameWithFileName(donghuaurl);
            }
            break;
        case stand:
            for(int i = 0; i<=roledata.maxcut_zhan ; i++)
            {
                char donghuaurl[100] = {0};
                sprintf(donghuaurl,"%s07%03d.png",roledata.spiritUrl_zhan->getCString(),i);
                thisdonghua->addSpriteFrameWithFileName(donghuaurl);
            }
            break;
        case attack:
            break;
        case death:
            break;
        case funny:
            break;
        default:
            break;
        }
        break;
    case rigth_up:
        switch (roledata.acttodo)
        {
        case run:
            for(int i = 0; i<=roledata.maxcut_pao ; i++)
            {
                char donghuaurl[100] = {0};
                sprintf(donghuaurl,"%s03%03d.png",roledata.spiritUrl_pao->getCString(),i);
                thisdonghua->addSpriteFrameWithFileName(donghuaurl);
            }
            break;
        case stand:
            for(int i = 0; i<=roledata.maxcut_zhan ; i++)
            {
                char donghuaurl[100] = {0};
                sprintf(donghuaurl,"%s03%03d.png",roledata.spiritUrl_zhan->getCString(),i);
                thisdonghua->addSpriteFrameWithFileName(donghuaurl);
            }
            break;
        case attack:
            for(int i = 0; i<=roledata.maxcut_attack ; i++)
            {
                char donghuaurl[100] = {0};
                sprintf(donghuaurl,"%s03%03d.png",roledata.spiritUrl_attack->getCString(),i);
                thisdonghua->addSpriteFrameWithFileName(donghuaurl);
            }
            break;
        case magicup:
            for(int i = 0; i<=roledata.maxcut_magic ; i++)
            {
                char donghuaurl[100] = {0};
                sprintf(donghuaurl,"%s03%03d.png",roledata.spiritUrl_magic->getCString(),i);
                thisdonghua->addSpriteFrameWithFileName(donghuaurl);
            }
            break;
        case death:
            break;
        case funny:
            break;
        default:
            break;
        }
        break;
    case rigth_down:
        switch (roledata.acttodo)
        {
        case run:
            for(int i = 0; i<=roledata.maxcut_pao ; i++)
            {
                char donghuaurl[1000] = {0};
                sprintf(donghuaurl,"%s00%03d.png",roledata.spiritUrl_pao->getCString(),i);
                thisdonghua->addSpriteFrameWithFileName(donghuaurl);
            }
            break;
        case stand:
            for(int i = 0; i<=roledata.maxcut_zhan ; i++)
            {
                char donghuaurl[100] = {0};
                sprintf(donghuaurl,"%s00%03d.png",roledata.spiritUrl_zhan->getCString(),i);
                thisdonghua->addSpriteFrameWithFileName(donghuaurl);
            }
            break;
        case attack:
            for(int i = 0; i<=roledata.maxcut_attack ; i++)
            {
                char donghuaurl[100] = {0};
                sprintf(donghuaurl,"%s00%03d.png",roledata.spiritUrl_attack->getCString(),i);
                thisdonghua->addSpriteFrameWithFileName(donghuaurl);
            }
            break;
        case magicup:
            for(int i = 0; i<=roledata.maxcut_magic ; i++)
            {
                char donghuaurl[100] = {0};
                sprintf(donghuaurl,"%s00%03d.png",roledata.spiritUrl_magic->getCString(),i);
                thisdonghua->addSpriteFrameWithFileName(donghuaurl);
            }
            break;
        case death:
            break;
        case funny:
            break;
        default:
            break;
        }
        break;
    case left_down:
        switch (roledata.acttodo)
        {
        case run:
            for(int i = 0; i<=roledata.maxcut_pao ; i++)
            {
                char donghuaurl[100] = {0};
                sprintf(donghuaurl,"%s01%03d.png",roledata.spiritUrl_pao->getCString(),i);
                thisdonghua->addSpriteFrameWithFileName(donghuaurl);
            }
            break;
        case stand:
            for(int i = 0; i<=roledata.maxcut_zhan ; i++)
            {
                char donghuaurl[100] = {0};
                sprintf(donghuaurl,"%s01%03d.png",roledata.spiritUrl_zhan->getCString(),i);
                thisdonghua->addSpriteFrameWithFileName(donghuaurl);
            }
            break;
        case attack:
            for(int i = 0; i<=roledata.maxcut_attack ; i++)
            {
                char donghuaurl[100] = {0};
                sprintf(donghuaurl,"%s01%03d.png",roledata.spiritUrl_attack->getCString(),i);
                thisdonghua->addSpriteFrameWithFileName(donghuaurl);
            }
            break;
        case magicup:
            for(int i = 0; i<=roledata.maxcut_magic ; i++)
            {
                char donghuaurl[100] = {0};
                sprintf(donghuaurl,"%s01%03d.png",roledata.spiritUrl_magic->getCString(),i);
                thisdonghua->addSpriteFrameWithFileName(donghuaurl);
            }
            break;
        case death:
            break;
        case funny:
            break;
        default:
            break;
        }
        break;
    case left_up:
        switch (roledata.acttodo)
        {
        case run:
            for(int i = 0; i<=roledata.maxcut_pao ; i++)
            {
                char donghuaurl[100] = {0};
                sprintf(donghuaurl,"%s02%03d.png",roledata.spiritUrl_pao->getCString(),i);
                thisdonghua->addSpriteFrameWithFileName(donghuaurl);
            }
            break;
        case stand:
            for(int i = 0; i<=roledata.maxcut_zhan ; i++)
            {
                char donghuaurl[100] = {0};
                sprintf(donghuaurl,"%s02%03d.png",roledata.spiritUrl_zhan->getCString(),i);
                thisdonghua->addSpriteFrameWithFileName(donghuaurl);
            }
            break;
        case attack:
            for(int i = 0; i<=roledata.maxcut_attack ; i++)
            {
                char donghuaurl[100] = {0};
                sprintf(donghuaurl,"%s02%03d.png",roledata.spiritUrl_attack->getCString(),i);
                thisdonghua->addSpriteFrameWithFileName(donghuaurl);
            }
            break;
        case magicup:
            for(int i = 0; i<=roledata.maxcut_magic ; i++)
            {
                char donghuaurl[100] = {0};
                sprintf(donghuaurl,"%s02%03d.png",roledata.spiritUrl_magic->getCString(),i);
                thisdonghua->addSpriteFrameWithFileName(donghuaurl);
            }
            break;
        case death:
            break;
        case funny:
            break;
        default:
            break;
        }
        break;
    default:
        break;
    }

    return thisdonghua;
}

SpiritsPlayer::~SpiritsPlayer(void)
{
}
复制代码

sprintf(donghuaurl,”%s06%03d.png”,roledata.spiritUrl_pao->getCString(),i);

总体思路就是,我们通过了帧连接的拼接来构成动画,通过我们之前写好的model数据来定义我们任务的朝向等问题

比如00000代表右,01000就代表右上,这个也得根据自己素材的模型来写不同的处理逻辑,万变不离其中;

如果我们的图片是在一张大图的集合中,我们可以同过CCRect来处理获取帧图片方式!

CCAnimation* getNowAnt(MainRoledata roledata);
CCAnimate* updateNowAnt(MainRoledata roledata);

通过这两个方法集合,我们就能获取到八面玲珑的朝向,甚至我们连,后续的动画机制也加入了,比如,跑动,打斗,做各种动作等!

Commen_Direction SpiritsPlayer::getNowPointDir(CCPoint newpoint); //获取朝向

void SpiritsPlayer::moveTomap_dir(CCPoint newpoint) //改变朝向方法

以上两个方法,我们是专门处理,之后精灵移动,移动完之后还得恢复到站立状态,移动过程中改变人物朝向问题!所以要考虑的很清楚

很清晰,思路清楚了,才能方便以后的拓展。

OK了,有了以上的精灵类,将其实例化到一个简单的图片地图上,他就会动了;

添加的时候我们直接addchild方法!!

SpiritsPlayer* role_main = new SpiritsPlayer(basedatas->role_player,1,false);

nowmap->addChild(role_main->npc, 999);

nowmap 暂且可以用一张图片CCSprite代替!!!

下一篇博客我会非常详细的将地图制作方法写出来给大家一起分享,由于其实我之前都是C#,JAVA做的很多,很多地方和细节还需要重构

还需要大家多指点一下,这也是我学习的好机会;

 

游戏demo及素材下载地址(demo里面包含了所有的素材资料);

http://pan.baidu.com/share/link?shareid=4012433582&uk=4097703620&third=15

 

ps:广告我建了一个QQ群:和大家一起分享cocos2dx开发经验【41131516】

[转载]JQuery EasyUI 引用加载分析 - for certain - 博客园

mikel阅读(1212)

[转载]JQuery EasyUI 引用加载分析 – for certain – 博客园.

easyui是什么,就不介绍了,接触到前端的就算没用过,肯定也应该听说过。其次,本文不是介绍它提供如calendar、tree等这些功能如 何使用的,这些官网上介绍都很详细,中文的网上也不少。本文是从easyui的文件目录结构上来说起,谈一下easyui的两种引用加载方式。

 

引用加载方式

easyui的加载可以分为两种方式,方式一:饿汉式加载;2:懒汉式加载。在具体讨论这两种加载方式之前,我们先看下easyui目录结构。

 

目录结构

demo文件夹:一些示例,(正式项目中删除)

locale:一些不同语言的文件,其实是对easyui的扩展。(只须保留你想用的语言相对应的文件)

plugins:easyui提供的各个功能的文件。(使用方式二加载必须保留,方式一加载可以删除)

src :各个插件的源文件,不是全部,其实有些功能不是开源的,是商业授权,因此没有源文件。(可以删除)

themes:主题,就是css文件和要用到的图标文件,里边提供5种风格。(可以只保留要使用的风格,并且具体到一个风格里,又分为两部 分:easyui.css和其它所有css。easyui.css是其它所有css的合并后结果,在不同的加载方式中只会用到一部分。)

easyloader.js:暂且称之为加载器文件。在使用方式一加载也不会被使用。

JQuery.easyui.min.js:easyui的主文件。它是plugins下所有文件合并后的结果,因些它在使用方式一加载必须保留,方式二加载不会使用到,可以删除(主文件可以删除,没听错吧??哈哈,相信我,可以的。)。

JQuery.min.js:JQuery文件,easyui是基于jQuery的,因此是必须的。

 

其它的是一些授权文件和更新日志之类的,就不说了。

 

这里有两个重点:jQuery.easyui.min.js是plugins下所有文件合并后的结果,具体到某个风格下如default下所有css:easyui.css是余下的其它所有css合并之后的结果

这样就导致了不同的两种加载方式。

 

饿汉式加载

<link rel="stylesheet" type="text/css" href="easyui/themes/default/easyui.css">
<link rel="stylesheet" type="text/css" href="easyui/themes/icon.css">
<script type="text/javascript" src="easyui/jquery.min.js"></script>
<script type="text/javascript" src="easyui/jquery.easyui.min.js"></script>

这种也是最常用的方式。这种方式会加载easyui提供的所有功能,不管你页面上会不会用的到,简单暴力。

 

懒汉式加载

<link rel="stylesheet" type="text/css" href="easyui/themes/icon.css">
<script type="text/javascript" src="easyui/jquery.min.js"></script>
<script type="text/javascript" src="easyui/jquery.easyloader.js"></script>

这种方式就相对比较懒,只会在使用特定功能的时候才会加载相对应的plugins下的js和风格里css文件,而其它不使用的功能对应的js和css永远不会被下载。而easyloader.js就是负责用于加载各个插件的。

 

调用

同样,都是两种使用方式,我们这里只讨论下在方式2调用过程。

当页面中包含如:

<a href="#" class="easyui-linkbutton" onclick="load1()">Load Calendar</a>

html标记中class指定easyui-linkbutton之类easyui特定的标记时,easyui会检测到并自动的去下载相对应的文 件,并应用相应的样式和功能。对于easyui-linkbutton标记就是去下载themes/default/linkbutton.css和 plugins/jquery.linkbutton.js这两个文件,然后改变a的显示样式。

 

当然我们可以手动通过js去调用相关功能:

复制代码
using('calendar', function(){
                $('#cc').calendar({
                    width:180,
                    height:180
                });
            });
复制代码

这样就会去加载calendar对就的css和js文件,并在id为cc的div上创建一个日历控件。

 

问题

很快,我们就会发现一些问题。

 

风格

方式1的加载我们通过引用 themes/default/easyui.css来指定,引用不同风格下的easyui.css来使用不同的风格,但是方式2我们并没用指定风格?如何指定风格?

不指定时,默认是引用default风格下的样式,就会去下载该风格下的样式。如果你使用方式2时且没有指定风格,不存在default风格就会出现问题。

当然我们可以在页面加载后通过

easyloader.theme = "gray";

来指定风格。

 

语言

同样的对于语言文件,对于方式1我们可以直接在页面引用jquery.easyui.min.js的后面引用。

<script type="text/javascript" src="easyui/locale/easyui-lang-zh_CN.js"></script>

但是这对于方式2的加载,这是不起作用的。

因些语言文件其实是对各个插件默认配置的修改,当插件都还没有加载时,语言文件对它的修改当然是不会起作用的。

因此我们可以通过下面

easyloader.locale = "zh_CN";

来指定使用何种语言。

 

完毕。

[转载].NET应用技巧Asp.NET MVC 4 设置IIS下调试 - chutianshu_1981 - 博客园

mikel阅读(960)

[转载]【.NET应用技巧】Asp.NET MVC 4 设置IIS下调试 – chutianshu_1981 – 博客园.

【环境】 VS 2012  IIS7.5

【问题】

MVC项目在创建时和APS.NET不同,不能够选择服务器类型,不能够直接把项目创建到IIS上。

如果在项目中直接更改属性,更换调试服务器类型,会报错

【解决方法】

1.右键点击项目——【属性】——Web——服务器——使用自定义 web服务器——填入IIS中服务器Url

2.调试菜单——附加到进程

如下图配置,选中“显示所有用户进程”,选择w3wp.exe进程,并在“选择”按钮打开的“选择代码类型中”,选中托管(4.5、4.0版)

然后就ok了

[转载]ASP.NE的缓存技术提高Web站点的性能 - 童天佑 - 博客园

mikel阅读(1138)

[转载]ASP.NE的缓存技术提高Web站点的性能 – 童天佑 – 博客园.

一:我们为什么要使用缓存?

先来理解一下ASP.NET缓存技术的基本原理:把访问频繁的数据以及需要花大量的时间来加载的数据缓存在内存中,那么用户在下次请求同样的数据时,直接将内存中的数据返回给用户,从而大大的提高了应用程序的性能。

 

二:缓存的分类(整页缓存,页面部分缓存,应用程序缓存)

那么什么时候我们要用到整页缓存,什么时候用到部分页面缓存,什么时候我们要用到应用程序缓存呢?

>>整页缓存的情况:

a)不需要平凡更新数据的页面

b)占用大量时间和资源的页面

>>页面部分缓存:

顾名思义,页面部分缓存,则只需要缓存页面中的部分页面,常用方法是“缓存后替换”,就是下缓存整个页面,然后替换页面中不需要缓存的地方。这里要用到一个Substitution控件实现,稍后详细讲解它的应用。

>>应用程序缓存:

用来存储与应用程序相关的对象,主要由Cache类来实现,它给我们提供了一种机制,使我们可以通过编码的方式来灵活控制缓存操作。

 

三:如何使用”整页缓存”?

首现我们需要在页面顶部加上这样一个指令:

<%@ OutputCache Duation=”5″ VaryByParam=”id,name” VaryByControl=”none” Location=”any” VaryByCustom=”browser” %>

以下详细介绍一下各个属性的用途:

  • Duration:必要属性,就是这个页面过多久后缓存不在起到作用,单位是s
  • VaryByParam:以分号分隔的字符串变量/none/* ,none表示不随任何参数彼变化缓存,*表示为任何一个有效的参数缓存一个单独的页面。 注意:如果没有BaryByControl属性,该属性是必要的。
  • VaryByControl:以分号分隔的字符串变量,代表用户控件中声明的服务器控件的ID,注意:如果没有VaryByParam,该属性是必须的。
  • VaryByCustom:自定义输出缓存的要求,它需要在global.asax中定义自定义变动,一般默认值为browser,根据了浏览器决定缓存要求。
  • Location:指定输出缓存项的位置,OutputCacheLocation枚举,默认值为any。

例如:现在我们有一个图书详情页面,这里就拿当当网的图书详情页面来举例,当当网当你选择一本书后,进入到图书详情页,详情页肯定会包含:该图书的点击量,价格,目录信息,作者,出版社等等的一些介绍。

那么我们要缓存这个页面首现需要在页面顶部添加这样一个指令:

1)首现无条件缓存:<%@ OutputCache Duration=”120″ VaryByParam=”none” %>

这个表示什么意思呢?就是这个页面缓存时间为120秒,假如你进到这个详情页,然后卖家在后台修改了图书的出版社,那么你现在无论怎么刷新,你看到的价格始终都是刚进页面时的出版社信息。必须等到120s后,点击刷新才可以看到卖家修改后的出版社信息。

2)根据条件缓存:<%@ OutputCache Duration=”120″ VaryByParam=”bookId,bookName” %>

这个就是说以bookId或者bookName作为参数传递的页面都将被缓存120s,例如:http://web/BookDetails.aspx?bookId ,页面会被缓存120s。

3)根据VaryByControl属性<%@ OutputCache Duration=”120″ VaryByParam=”none” VaryByControl=”ddlCategories” %>

当需要缓存的数据随页面上的某个控件值的改变而改变,根据ASP.NET控件的命名规则,大家都知道ddlCategories控件就是一个图书的分类控件了(DropDawnList)。

现在假如有一个图书分类的页面,那么假如卖家修改了某个分类下的某一本图书的信息,当用户在页面中查看到的还是修改之前的信息,只有在更换ddCategories控件的项后,在更新才会显示修改后的图书信息。

 

三:如何使用”页面部分缓存”?

  <%@ OutputCache Duration=”120″ VaryByParam=”none”  %>

什么情况我们要使用到部分页面缓存呢,还是拿图书详情页来举例子:现在要统计图书时实的点击量。

首现放入一个:<asp:Substitution ID=”Sub1″ runat=”server” MethodName=”GetClickCount” />  控件。

这个控件就是用来显示点击量的,我们可以看到MethodName属性调用了一个GetClickCount()方法。

static string GetClickCount(HttpContext contxt)

{

//根据图书ID查询本图书的点击量

int clickCount = manage.GetClick(bookId);

return clickCount.ToString();

}

假如现在点击量是1000次,那么你刷新后,点击量可能变成了1000+次了,因为此时此刻该图书可能还有其他的用户在点击访问。由此证明该图书的点击量并没有被缓存起来。

这里需要注意的是:Substitution控件的MethodName属性所调用的方法必须满足以下三个条件。

1)必须是静态static方法

2)返回值必须是String类型

3)参数类型必须是HttpContext类型

 

 

四:如何实现”应用程序缓存”?

待续。。。

[转载]jquery数据验证插件(自制,简单,练手) - 青牛客 - 博客园

mikel阅读(1158)

转载jquery数据验证插件(自制,简单,练手) – 青牛客 – 博客园.

一:最近项目中js数据验证比较多,为了统一风格,移植复用,于是顺手封装了JQuery的插件。

 

(function($) {
    var defaults = {
        bugColor: '#FFCCCC', //数据有误的时候文本框颜色
        color: 'white', //数据正确时候文本框颜色
        type: "alert", //数据错误时候提示方式 alert 弹出框方式 text 赋值span html
        msg: "Msg", //数据有误的时候提示内容
        ResOjId: 'no'// 当test方式的时候 被赋值的标签 #id
    };
    function UiProcess(options, rexString, object) {
        var options = $.extend(defaults, options);
        var values = object.val();
        if (rexString.test(values)) {
            object.css("backgroundColor", options.color);
            return true;
        } else {
            object.css("backgroundColor", options.bugColor);
            if (options.type == "alert") {
                alert(options.msg);
            }
            if (options.type == "text") {
                $(options.ResOjId).html(options.msg);
            }
            return false;
        }
    }
    //验证ip是否符合格式
    $.fn.RegIp = function(options) {
        var rexString = /^\d{1,3}\.{1}\d{1,3}\.{1}\d{1,3}/;
        return UiProcess(options, rexString, this)
    }
    //验证座机是否符合格式
    $.fn.RegTelPhone = function(options) {
        var rexString = /^[0-9]+[-]?[0-9]+[-]?[0-9]$/;
        return UiProcess(options, rexString, this)

    }
    //验证手机是否符合格式
    $.fn.RegMombilePhone = function(options) {
        var rexString = /(^189\d{8}$)|(^13\d{9}$)|(^15\d{9}$)/;
        return UiProcess(options, rexString, this)

    }
    //验证中文是否符合格式
    $.fn.RegCHZN = function(options) {
        var rexString = /[\u4e00-\u9fa5]/;
        return UiProcess(options, rexString, this)

    }
    //验证decimal是否符合格式
    $.fn.RegDecimal = function(options) {
        var rexString = /^[0-9]+[.]?[0-9]+$/;
        return UiProcess(options, rexString, this)

    }
    //验证decimal保留一位小数是否符合格式
    $.fn.RegDecimalSign = function(options) {
        var rexString = /^[+-]?[0-9]+[.]?[0-9]+$/;
        return UiProcess(options, rexString, this)

    }
    //验证整数保留一位小数是否符合格式
    $.fn.RegNumber = function(options) {
        var rexString = /^[0-9]+$/;
        return UiProcess(options, rexString, this)

    }
    //验证各位整数保留一位小数是否符合格式
    $.fn.RegNumberSign = function(options) {
        var rexString = /^[+-]?[0-9]+$/;
        return UiProcess(options, rexString, this)

    }
    //验证非空字符
    $.fn.IsEmpty = function(options) {
        var rexString = /(^.+$)|([\u4e00-\u9fa5])/;
        return UiProcess(options, rexString, this)

    }
})(jQuery);

调用:

<script type="text/javascript">
        function submitOk() {
            var interfaceNameInput = $("#<%=interfaceName.ClientID %>");
            var userNameInput = $("#<%=userName.ClientID %>");
            var passWordInput = $("#<%=passWord.ClientID %>");
            var interfaceUrlInput = $("#<%=interfaceUrl.ClientID %>"); ;
            if (!interfaceNameInput.IsEmpty({ "msg": "接口名称格式不正确!" })) { return false }
            if (!userNameInput.IsEmpty({ "msg": "格式不正确!" })) { return false }
            if (!passWordInput.IsEmpty({ "msg": "格式不正确!" })) { return false }
            if (!interfaceUrlInput.IsEmpty({ "msg": "格式不正确!" })) { return false }
        }
    </script>