置顶随笔 #
2012年4月20日 #
我们项目组打算从svn向git迁移,前几天我搭建了git环境,把代码从svn转移过来,然后所有成员都通过server上的git账号来做pull和push,一切都安置妥当,没有问题。但是后来其它项目组也打算使用这个git server,那么问题来了,之前那种授权的方式肯定是不够的,因为只要能连上server,那么他对这个server上所有的repository都有完全的读写权限,这显然是不可接受的。 所以打算使用Gitolite这个组件来做权限控制,搜索了下,找到的文章貌似都是老版本的,所以有了写这篇文章的想法。 Gitolite其实也是一个git repository,首先在server上安装好后,在client上把server上的repository clone下来,在本地做一些更改,再push回server,server端的hooks会根据push上来的配置来更新权限。 接下来,介绍下安装和配置步骤
准备工作
如果你之前是用git账号来做权限控制的话,记得把/etc/passwd里git用户的shell换回/bin/bash,然后把~git/.ssh/authorized_key里不再需要的key移除。 用ssh-kengen生成一对key,比如your-name和your-name.pub(下文均以此为例) 拷贝私钥到本用户的.ssh文件夹中
mv your-name ~/.ssh/
拷贝公钥到git server上
scp you-name.pub git@your.server.name.or.ip.address:~
为了以后方便,这里可以做一个server别名,指定连接所需的用户名,server的地址、端口以及私钥
vim ~/.ssh/config
输入以下内容
host githost user git hostname your.server.name.or.ip.address port 22 identityfile ~/.ssh/your-name
安装Gitolite
登录git server
ssh git@your.server.name.or.ip.address
下载最新的Gitolite
git clone git://github.com/sitaramc/gitolite
安装,这里说明下,安装方式有3种,区别在与指定生成gitolite可执行文件的路径,这里采用Gitolite作者推荐的第二种,也就是把文件生成到$HOME/bin中,这样可以在接下来的bash中直接执行gitolite命令而不用指定路径(如果你的~/bin目录不存在记得先mkdir ~/bin)
gitolite/install -ln
设置,由于是第一次运行这个命令,所以这里指定的key是拥有Gitolite管理员权限的
gitolite setup -pk your-name.pub
此命令会在你的~/repositories/目录生成两个repository:gitolite-admin.git和testing.git
配置权限
退到你的workstation上
exit
clone刚才生成的gitolite-admin.git
git clone githost:gitolite-admin
注意这里用的是刚才准备好的server别名来连接的,其中最重要的区别是使用your-name.pub这个key,并且没有采用绝对路径来指定想要clone的repository,而是直接使用名称,并且这个名称也没有包括.git这个后缀。这一点很重要,因为这是用Gitolite的机制来clone,如果你跳过它直接使用git来,那么它的一些功能就无法实现了。以后clone, push其它需要受Gitolite权限控制的repository都必须这样做。 clone完后会有个新的目录gitolite-admin,里面有两个文件夹conf和keydir,第一个目录中包含的是配置文件,里面就是记录权限配置的地方,第二个目录中则包含所有用户的pub key。 现在我们打开配置文件,按照我们的权限配置需要进行设置
vim gitolite-admin/conf/gitolite.conf
我期望的配置如下,你也可以根据你的需要做更改
@repos_a @proj1 @proj2 @repos_b @proj3 @proj4 @proj5 @team_a @user1 @user2 @team_b @user3 @user4 repo gitolite-admin RW+ = your-name repo @repos_a RW+ = @team_a R = @all repo @repos_b RW+ = @team_b
这个配置很简单,首先定义了两个repository group,再又定义了两个user group,group的好处就是以后添加repository和user的时候,不需要再单独配置,只需加入到对应的group中即可。
- 添加全新的repository,在上面提到的gitolite.conf文件中配置好对应的名称和权限,再push到server即可,server会自动帮你创建一个empty的bare repository。
- 如果你已经有一个repository,想把它加进来的话,那就把它拷贝到git server上的
~/repositories文件夹里,记得文件夹名要以.git结尾,并且这个repository一定要是bare的,(你可以通过拷贝repository里的.git文件夹,然后运行git config --bool core.bare true,也可以运行git clone --bare your-repository来得到bare repository)。这种方式还有一个额外的操作就是在server上运行一次gitolite setup。 - 移除repository,在配置文件中移除对应的repo,然后push,接着再删除server上对应的文件夹即可。
- 添加user,把pub key拷贝到
keydir文件夹里 - 删除user,一样,移除
keydir里对应的pub key
注意,上面说的操作,都必须在clone的gitolite-admin里做更改,然后push,千万别在server上自己来,那样是没用的,因为这些权限配置、repository管理都有一些额外的操作,gitolite-admin会帮你搞定一切。 把你的更改push回server上,试试clone,pull,push,看看权限是否正确。比如
git clone githost:proj3
更多的内容,可以参考官方文档 本文由Roy最初发表于:http://blog.chengbo.net/2012/04/20/use-gitolite-to-manage-git-repositories.html,你可以在保持文章完整和保留本声明的情况下转帖、分发和印刷等。
自从@TimothyYe童鞋介绍偶加入HDC后,为了快点完成新人作业,也为了满足我不折腾不舒服斯基的欲望,哥配了一台凌动小PC,Intel ATOM D525NW的主板,集成了CPU和显卡,加上小机箱总共700元。到手后,插上我Thinkpad拆下来的2根1G内存和另外一台闲置N久的台式机160G SATA硬盘,装了个Debian,就可以用了。 装上transmission后,全速挂BT无压力,比那些直接路由或者1、200的NAS给力多了。哥还继续在上面折腾了Git,emule,动态DNS等,远远没有把2G内存榨干,当然这里都说远了,继续回来。 我家使用的是电信ADSL,一个宽带无线路由,手机,notebook,iPad,PC,xbox等(哥真败家。。。),路由拨号连入Internet,然后通过WIFI把其它设备连入LAN,共享外网连接。这个配置在我挂PT之前一直工作正常,可是当我把BT开着,就算上传和下载速度都为0,LAN中的其它设备访问Internet的速度一样很慢,很慢。 分析了下,估计是我那100元的低端无线路由无法支撑如此多的连接会话造成的,简单直接的办法就是升级路由,于是开始上网查高端路由,不查不知道,一查才知道这东西好贵,便宜的怕是不能解决问题,贵的哥又承受不了。纠结了N久,最后发现,何不把我那台Debian来做路由呢? 马上在淘宝买了根20元的USB有线网卡,收到后插入Debian上,这东西不像Windows和MAC可以自动识别出来然后提示你安装驱动,要自己去折腾,而且附送的光盘上也没有Linux的驱动,看来,要找Google了。
安装配置USB网卡
在Debian上运行
chengbo@chengbo-server:~$ lsusb
Bus 005 Device 002: ID 0fe6:9700 Kontron (Industrial Computer Source / ICS Advent) DM9601 Fast Ethernet Adapter 从上得知,USB网卡是DM9601这个型号的
chengbo@chengbo-server:~$ /sbin/ifconfig
只有lo和eth0,新加的网卡没有识别 以DM9601为关键字,Google,找到了这里,下载解压好linux的驱动后,编译出错,好像是头文件不对,哥不会写驱动啊,只有继续Google,发现DM9601的驱动已经集成在新版的内核里了,但是我试了下,用不了,不知道什么原因。继续Google,又找到了这里,根据上面的说明,下载了qf9700的驱动,编译,成功,生成文件qf9700.ko。 安装刚才编译好的驱动
root@chengbo-server:~$ cp qf9700.ko /lib/modules/`uname -r`/kernel/drivers/net/usb root@chengbo-server:~$ cd /lib/modules/`uname -r`/kernel/drivers/net/usb root@chengbo-server: usb$ modprobe usbnet root@chengbo-server: usb$ insmod qf9700.ko root@chengbo-server: usb$ ifconfig eth1 up
配置好驱动后再运行
root@chengbo-server:~$ ifconfig
多了一个eth1了,到现在新网卡已经可以使用了。
安装配置ADSL拨号相关软件
把原先连接无线宽带路由和猫的网线拔掉,接在猫和Debian的USB网卡上 安装拨号软件
root@chengbo-server:~$ apt-get install pppoe pppoeconf
成功后运行
root@chengbo-server:~$ pppoeconf
会弹出一个窗口,自动检测连接ADSL的网卡,然后照提示输入你的用户名,密码,其它的可以用默认值。 运行
root@chengbo-server:~$ pon dsl-provider
就可以拨号了,过后再ifconfig一下,会发现多了个ppp0。poff命令可以关闭ppp0连接。另外,我发现多次拨号可以有多个ppp,比如ppp1,而且IP还不一样,不知道这样是否可以让带宽double,待测试。此时Debian可以上网了,但是LAN中的其它设备还不行。
设置LAN路由
设置Debian连接LAN的网卡配置
root@chengbo-server:~$ vim /etc/network/interfaces
allow-hotplug eth0 iface eth0 inet static address 192.168.3.3 netmask 255.255.255.0 network 192.168.3.0 broadcast 192.168.3.255 gateway 192.168.3.3
由于所有的无线设备都是有无线宽带路由来DHCP的,所以登录无线宽带路由,把配置改下:
range 192.168.3.100-192.168.3.200 netmask 255.255.255.0 gateway 192.168.3.3 dns 8.8.8.8 or 4.4.4.4
DNS没有用电信分配给我的,是因为不想看电信强行插入的广告 设置NAT
root@chengbo-server:~$ echo "1" > /proc/sys/net/ipv4/ip_forward root@chengbo-server:~$ /sbin/iptables -t nat -A POSTROUTING -o ppp0 -s 192.168.3.0/24 -j MASQUERADE
至此LAN中的其它设备也可以上网了,如果还不行的话,检查下你的配置是否正确,然后再看看路由表,把错误的路由改掉。 最后,我家网络的结构如下图
// TODO: 此文还需添加Debian重启后自动配置USB网卡,拨号,NAT的内容 本文由Roy最初发表于:http://blog.chengbo.net/2012/04/18/use-debian-server-as-a-router.html,你可以在保持文章完整和保留本声明的情况下转帖、分发和印刷等。
2012年4月10日 #
原文:http://android-developers.blogspot.com/2012/04/faster-emulator-with-better-hardware.html [This post is by Xavier Ducrohet and Reto Meier of the Android engineering team. — Tim Bray.]Android模拟器是开发和测试Android程序的一个关键工具。目前Android设备的功能以及多样性发展的日益迅猛,模拟器要跟上步伐十分的困难。 今天我们激动的宣布,模拟器做了一些十分显著的改进,包括激动人心的性能提升,众多硬件特性,尤其是传感器和多点触控的支持。
加入GPU支持
今天我们发布的系统镜像内置了GPU支持(Android 4.0.3 r2)。Android日益依赖GPU来提高性能,所以新模拟器的差异十分显著。在下面的视频中(需翻墙才能看)模拟器依旧翻译着ARM指令(运行模拟器的宿主系统一般是x86平台,此镜像是ARM平台的,所以中间存在着ARM->x86指令的转换。之前没有GPU支持,运行如此高分辨率的模拟器并不流畅);GPU可以让性能提升不少。 一个额外的红利,我们现在已经支持OpenGL ES 2.0了,所以你的OpenGL游戏可以在模拟器中运行了。 请注意的是,目前有很多种的GPU,我们并没有用所有GPU对这个beta版的模拟器做测试,所以,如果你有什么意见或者遇到了什么问题,请联系我们。
模拟更多的硬件特性
移动设备的硬件特性是一个开发平台的重要组成部分,所以我们也高兴的宣布,除了去年加入的摄像头支持,现在还可以使用一个绑定的Android设备来支持传感器和多点触控了。 我们还在为模拟器支持更多的硬件特性,包括蓝牙和NFC(近距离无线通信),而努力。
优化的CPU性能
我们同样也优化了Android模拟器的CPU性能。硬件浮点运算操作在冰淇淋三明治版本(Android 4.0)之上的版本中有效,它让模拟CPU操作大概快了2倍。 上一周,我们发布了包括x86系统镜像和主机驱动程序的Android开发工具r17版本,可以让模拟器使用x86指令执行,大大提高了执行效率。 下面这个视频显示了两个CPU相关的程序,一个有虚化(virtualization),一个没有,在两个运行着相同系统镜像的模拟器上运行的情况。(同样需要翻墙)
构建一个现代化的模拟器
因为Android平台允许应用程序和系统组件之间的深层相互操作,所以我们必须提供一个有着完整系统镜像的模拟器。我们的模拟器虚拟了一个完整的设备:硬件,内核,底层系统库和应用框架。 当然,系统一般被虚拟为ARM CPU的设备;之前,我们用软件来虚拟这些指令,它一直工作的不错,直到包括更多动画和更加复杂的Android 3.0的到来。 我们缺少的是Android x86的支持,和上周r17版本的SDK工具中的GPU支持。把模拟器的OpenGL ES 2.0指令传给宿主操作系统,转换成标准的OpenGL 2.0,然后用宿主GPU来执行,这样模拟器就支持GPU了。
总结
Android生态系统有着很多的来自不同厂商的设备。开发人员需要一个不用买设备就可以测试程序的途径。同时一个快速的,更真实的模拟器有着极大的帮助。 我们希望这些新的优化能够让模拟器在你的开发和测试过程中更加有用,同时期盼能进一步的为你优化它。 本文由Roy最初发表于:http://blog.chengbo.net/2012/04/10/faster-emulator-with-better-hardware.html,你可以在保持文章完整和保留本声明的情况下转帖、分发和印刷等。
2012年3月9日 #
今天在做项目的时候,遇到一个问题,记录下来。
当我们给ListView添加一个HeaderView后(代码如下),发现onItemClick方法里的position参数的值不是我们所期望的,比如点击ListView的第一行,我们期望的position是0,可是却是1,也就是说,它是从Header开始计数的。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.home);
mAdapter = new MyAdapter(this);
mListView = (ListView) findViewById(R.id.list);
mListView.addHeaderView(getLayoutInflater().inflate(R.layout.list_header));
mListView.setAdapter(mAdapter);
mListView.setOnClickListener(this);
}
@Override
public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
doSomething(mAdapter.getItem(position));
}
Google了下,发现有个老外issue过一个bug,和我遇到的问题一样,不过这个bug被RomainGuy reject掉了,理由是,你用错了,请用getAdapter。回答的太简洁了,完全没法理解,只好又去仔细研究ListView的代码,终于领会他的意思了。把其中addHeaderView和setAdapter方法贴下来
/**
* Add a fixed view to appear at the top of the list. If addHeaderView is
* called more than once, the views will appear in the order they were
* added. Views added using this call can take focus if they want.
* <p>
* NOTE: Call this before calling setAdapter. This is so ListView can wrap
* the supplied cursor with one that that will also account for header
* views.
*
* @param v The view to add.
* @param data Data to associate with this view
* @param isSelectable whether the item is selectable
*/
public void addHeaderView(View v, Object data, boolean isSelectable) {
if (mAdapter != null) {
throw new IllegalStateException(
"Cannot add header view to list -- setAdapter has already been called.");
}
FixedViewInfo info = new FixedViewInfo();
info.view = v;
info.data = data;
info.isSelectable = isSelectable;
mHeaderViewInfos.add(info);
}
/**
* Sets the data behind this ListView.
*
* The adapter passed to this method may be wrapped by a {@link WrapperListAdapter},
* depending on the ListView features currently in use. For instance, adding
* headers and/or footers will cause the adapter to be wrapped.
*
* @param adapter The ListAdapter which is responsible for maintaining the
* data backing this list and for producing a view to represent an
* item in that data set.
*
* @see #getAdapter()
*/
@Override
public void setAdapter(ListAdapter adapter) {
if (null != mAdapter) {
mAdapter.unregisterDataSetObserver(mDataSetObserver);
}
resetList();
mRecycler.clear();
if (mHeaderViewInfos.size() > 0|| mFooterViewInfos.size() > 0) {
mAdapter = new HeaderViewListAdapter(mHeaderViewInfos, mFooterViewInfos, adapter);
} else {
mAdapter = adapter;
}
//其它的一些代码这里省略之...
}
从代码和注释里都可以很清楚的得知,addHeaderView一定要在setAdapter之前调用,如果不是,addHeaderView会抛出一个异常。Android为什么要这样做?因为,在setAdapter的时候,会针对我遇到的这种情况(也就是说,position不正确)做些特殊的处理。setAdapter在内部判断了当前ListView是否有Header或者Footer,如果没有,就直接使用参数传进来的adapter;如果有,则用一个decorated的HeaderViewListAdapter来替换参数。这个HeaderViewListAdapter的使命,就是排除Header和Footer,让position(当然也包括getItem, getItemId等)正确返回。
分析到这里,解决方案就出来了:不要直接使用我们声明的adapter,而是用ListView里的那个decorated adapter。获取它的方法就是调用getAdapter。当然,如果ListView没有Header和Footer,直接使用声明的adapter也没有问题,不过为了方便、避免以后出错,还是统一使用decorated adapter比较好。
把onItemClick改成下面这样,就可以了
@Override
public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
doSomething(parent.getAdapter().getItem(position));
}
本文由Roy最初发表于:http://blog.chengbo.net/2012/03/09/onitemclick-return-wrong-position-when-listview-has-headerview.html,你可以在保持文章完整和保留本声明的情况下转帖、分发和印刷等。
2011年11月13日 #
发现上一篇blog居然是07年的时候。。。太懒了,不过准备再写点东西了,主要是记录下自己在工作中的一些经验以及心得,也希望对其他人能有点帮助。
最近工作是Android相关的,这玩意儿的中文资料太少而且质量超级低,自己磕磕碰碰研究了几个月,好歹入了点门,觉得还是记录分享下,也算是对中国的Android发展做出点贡献。。。(太看得起自己了吧?)
2007年5月2日 #
public class CompressedViewStatePage : System.Web.UI.Page

{
const string CompressedViewStateKey = "__COMPRESSEDVIEWSTATE";
protected override void SavePageStateToPersistenceMedium(object state)
{
LosFormatter formatter = new LosFormatter();
StringWriter writer = new StringWriter();
formatter.Serialize(writer, state);
string viewState = writer.ToString();
byte[] data = Convert.FromBase64String(viewState);
byte[] compressedData = CompressHelper.Compress(data);
string str = Convert.ToBase64String(compressedData);
ClientScript.RegisterHiddenField(CompressedViewStateKey, str);
}
protected override object LoadPageStateFromPersistenceMedium()
{
string viewState = Request.Form[CompressedViewStateKey];
byte[] data = Convert.FromBase64String(viewState);
byte[] uncompressedData = CompressHelper.Decompress(data);
string str = Convert.ToBase64String(uncompressedData);
LosFormatter formatter = new LosFormatter();
return formatter.Deserialize(str);
}
}这个方法重写了 Page.SavePageStateToPersistenceMedium 和 Page.LoadPageStateFromPersistenceMedium 方法。代码注册了一个 __COMPRESSEDVIEWSTATE 的隐藏字段,把压缩的 ViewState 放在其中,不再使用原先的 __VIEWSTATE 字段。
我要做的一个页面的情况是,顶部有很多选择查询参数的控件,用户首先输入参数,再点击搜索按钮后系统会把搜索出来的记录集显示在 UpdatePanel 里的 GridView 上;若不选择参数, GridView 上会显示数据库中所有的记录集。使用上面的代码在未启用局部刷新时没有问题,但是启用的话,假如用户第一次选择了一些参数,搜索,GridView 会绑定显示搜索出来的记录集,但是这时点击 GridView 的分页按钮, GridView 重新显示的却是所有记录集,也就是说, ViewState 丢失了。
禁用 Ajax 或不用上面的方法压缩 ViewState ,都可以恢复正常,但如果我两个都想要呢?好在这世上有 Google 这东西,我搜索到了下面的解决方案:
public class CompressedViewStatePage : System.Web.UI.Page

{
protected override void SavePageStateToPersistenceMedium(object state)
{
Pair pair;
PageStatePersister persister = this.PageStatePersister;
object viewState;
if (state is Pair)
{
pair = (Pair)state;
persister.ControlState = pair.First;
viewState = pair.Second;
}
else
{
viewState = state;
}
LosFormatter formatter = new LosFormatter();
StringWriter writer = new StringWriter();
formatter.Serialize(writer, viewState);
string viewStateStr = writer.ToString();
byte[] data = Convert.FromBase64String(viewStateStr);
byte[] compressedData = CompressHelper.Compress(data);
string str = Convert.ToBase64String(compressedData);
persister.ViewState = str;
persister.Save();
}
protected override object LoadPageStateFromPersistenceMedium()
{
PageStatePersister persister = this.PageStatePersister;
persister.Load();
string viewState = persister.ViewState.ToString();
byte[] data = Convert.FromBase64String(viewState);
byte[] uncompressedData = CompressHelper.Decompress(data);
string str = Convert.ToBase64String(uncompressedData);
LosFormatter formatter = new LosFormatter();
return new Pair(persister.ControlState, formatter.Deserialize(str));
}
}如果用 Reflector 看看 System.Web.UI.Page 类的 SavePageStateToPersistenceMedium 和 LoadPageStateFromPersistenceMedium 方法,你会发现上面的代码和微软的实现差不多,都是使用 Persister.Save 和 Persister.Load 来保存和获取 ViewState (只是上面的代码加上了压缩和解压的逻辑),这里的 Persister 是默认的 HiddenFieldPageStatePersister ,所以页面还是使用 __VIEWSTATE 字段来保存 ViewState。
改成第二段代码来压缩 ViewState 就正常了。至于第一段代码会在 Update Panel 中出问题,我是这样猜的:
启用 UpdatePanel 的局部刷新并不是真正的局部刷新,只不过微软做了点手脚,用 XMLHttpRequest 对象去向服务器提交请求,而服务器毫不知情,还是会生成一个完整的页面生成周期,把生成的 HTML 完整的返回,这时 ScriptManager 把不在 UpdatePanel 里的内容统统去掉,只接收 UpdatePanel 里面的内容,然后在客户端刷新一下,造成局部刷新的“假像”。问题就出在用 XMLHttpRequest 对象去请求服务器的时候,ScriptManager 不知道我们把 ViewState 放在 __COMPRESSEDVIEWSTATE 字段中,而用的是 __VIEWSTATE 字段里的内容,所以服务器会认为用户没有输入查询参数,返回了数据库中的所有记录……
问题好像就是这样产生的,不过我还有点不清楚,Persister.ControlState 和 Persister.ViewState 各是什么意思, MSDN 上也没说太明白,哪位大虾解释一下?
参考
2007年3月31日 #
2007年2月27日 #
2007年1月25日 #
2006年12月29日 #