解决 iOS 的 SSH 连接不上的问题

要用 SSH 连接 iPhone 手机,当然要越狱才行。当然越狱不是这篇的重点,就不多说了。
然后手机需要装有 OpenSSH ,通过 Cydia 就可以安装,很简单。
然后就遇到问题了,用的是一个 iPhone 6,系统 10.3.3 。发现即使都安装好了,使用 SSH 还是无法连接。

查资料的过程就不介绍了,解决方案如下:
首先需要在手机上安装一个命令行 App ,自己找 Cydia 的源就好了,只要能用就行。
使用命令行先停掉 sshd 的 plist

然后用如下命令启动 sshd

之后就可以在电脑上使用 ssh 操作我的苹果手机了。
没有在其他设备和系统测试过,不知道能不能解决所有问题,希望对看到这篇博文的你能有一点帮助。

Unity3D Custom Render Texture Shader 编译到低版本 Shader model 环境

写好的 Custom Render Texture 的 shader 在打包的时候报出了一个错误:

查资料发现 asuint 这个函数是在 Shader Model 4 (SM4) 才开始支持。
然后稍微看了一下 cginc 里边的代码,发现只有一个地方用到了这个函数——是把浮点类型的 primitive ID 转成 uint 类型。

这个 primitiveID 是 update zone 的当前正在处理的索引,而我目前并没有使用很多 update zone 来实现效果的需求,所以决定尝试一下,直接将 float 类型强转成 uint 。

操作步骤:

  • 在自己的工作目录下创建一个自己的 CustomRenderTexture 的 cginc ,也就是新建个文本文件,把后缀改成 .cginc。
  • 把 UnityCustomRenderTexture.cginc 中的代码全部复制到自己新建的 cginc 中,并且按照上面代码修改。
  • 别忘了修改一下头部的宏定义名称 #ifndef UNITY_CUSTOM_TEXTURE_INCLUDED 跟默认的做一个区分。
  • 在 shader 代码中引用我们自己的 cginc ,就可以用了。

Unity3D UGUI 基础布局

1. 轴点 (Pivot)
作为控件的“轴”,在控件状态改变时对最终布局产生影响。比如,旋转的时候控件就以轴点为中心旋转,调整尺寸或缩放的时候轴点会固定在原位置。

2. 锚点 (Anchor)
Anchor 看起来的样子是四个相对的三角形。Anchor 决定了当前控件与父控件的位置关系。控件的 Rect Transform 的坐标 Pos X 和 Pox Y 就以 Anchor 为原点。也就是 Pivot 会放在 Anchor + (Pos X, Pox Y) 的位置上。当父控件状态(旋转、缩放、移动等)发生变化时,控件会保持这个关系跟随父控件。
在 Anchor 选择面板,按住 Shift 选择会同时将 Pivot 设置在 Anchor 选择的相同的位置;按住 Alt 选择会将控件位置同时停靠在 Anchor 位置。如果要同时将 Pivot 和位置放在 Anchor 点,就同时按住 Shift 和 Alt。

3. 尺寸自适应
也可以选择自动伸展的 Anchor 比如,全屏或者 X 轴或 Y 轴方向自动伸展。这个时候在 Rect Transform 中的坐标或(和)尺寸设置会变成 Left Right Top Bottom 。这些值是相对 Anchor 自动伸展方向的距离。这个概念是非常有用的,可以实现控件相对父控件同比例缩放。具体效果看我撸的视频会比较清楚一点。

Unity3D 解决默认碰撞检测的缺陷 —— 实现理想的匀速直线运动

首先介绍一下默认碰撞检测存在的问题。先不提【我只想要你丫告诉我碰撞盒撞上了,却非得要我挂个钢体组件,还得接受重力,阻尼,角速度的影响。不是在逗我?】,那么我们手动勾选上钢体的 Is Kinematic 属性,自己来控制。
我们看下边的图:
collider1
当这个圆走到实线圆圈位置的时候,就会触发碰撞检测回调。当然,如果是在虚线圆圈的位置,回调是不会被触发的。那么问题来了,如果一帧的移动距离刚好是从虚线位置移动到实线位置,情况就是这样的,移动前一帧不会触发碰撞回调,而后一帧两个碰撞体已经嵌到一起了。
当然,如果只是小小的插入一下,也并没有什么问题,接下来看下面的图:
collider2
当一帧的移动距离超过碰撞目标的时候,奇迹发生了,这时碰撞检测的回调根本不会触发。也就是说在速度足够快的情况下,你的墙是挡不住你的球的。
为了实现一个在理想条件下的匀速直线运动,我就用 Unity 本身的射线检测功能来解决这个问题。
首先取出自己的碰撞盒
Collider 中有 Raycast 和 Cast 两个方法,参数差不多,具体的区别,还得画两张图:
ray1
图一
ray2
图二
ray3
图三
图一、图二中蓝色的线就是 Raycast 的结果,是从自己的中心射到目标碰撞盒上,如果按照这个射线的距离运动,结果就是图二的样子。
图三种的蓝色线是 Cast 方法的结果,射到目标碰撞盒上的是自己碰撞盒与目标碰撞盒的交点。这个距离就是从当前点移动到发生碰撞的点的距离。
简单一点就用 Cast 方法就够了,Raycast 比 Cast 提供了更多细节操作,比如专门针对某一层的检测等,如果需要做更细节的优化,就得自己用 Raycast 方法来处理。
RaycastHit 中比较重要的属性:
collider —— 目标碰撞盒
distance —— 到碰撞点的距离(上面图3中蓝线的长度)
normal —— 碰撞点的法线
point —— 目标碰撞点
接下来的逻辑就是:
比较本帧移动距离和 distance ,如果本帧移动距离比较小,就直接移动。
当然之后要把总距离减去这步的移动距离。
如果这一帧的距离大于剩余的距离了,就需要处理反射了。
先移动到碰撞点→方向向量反射→继续移动完。
打完收工。
当然这只是简单的描述,实际上还会遇到一些坑,比如当前自己和一个碰撞盒有重叠的时候,取到的第一个 RaycastHit 永远是有接触的这个碰撞盒,此时与方向向量无关,所以需要根据自己的逻辑处理。还有同时碰撞两个或多个碰撞盒等,都要小心处理。

libgit2使用教程(十三)git rebase

rebase 就是将制定目标分支,或者制定 commit 所在的一条路径,直接插到当前分支或目标分支。(好像有点乱,具体的东西自己去查吧)

简单点说,这不是合并。

当然,虽然不是 merge 但是也会有冲突的可能,所以中途有解决冲突的需要,所以 rebase 操作是分阶段进行的,因此逻辑会复杂一些。

首先尝试打开现有未完成的 rebase

如果存在未完成的 rebase,可以选择继续将其完成,或者把它终止掉

在 git_rebase_open 返回 -3 (也就是 GIT_ENOTFOUND ) 表示当前仓库并没有其他未完成的 rebase 可以放心大胆的从头开始搞。

接下来创建一个 rebase

init 的第三个参数是需要被操作的分支,传空表示当前分支;第四个参数和第五个参数这俩二选一,前者表示以一个 commit 为节点把到这个节点为止的一条链合并到目标分支;后者是直接选一个分支的最新一个 commit ,将这个分支的整条链合并到目标分支。

当然,这个时候工作区不会有任何变化。到 .git 文件夹里面会看到多了一个叫做 rebase-merge 的文件夹。如果这个时候程序被终止,这个文件夹会保留,在下一次启动的时候,就可以通过 git_rebase_open 打开这个 rebase 。

接下来就是实际执行 rebase 这个操作

这里可能存在遍历,但是为什么会有多个 operation 我也还没搞太明白,不过为了避免出事,还是循环调用保险一些:

接下来,不要忘记查看是否有冲突,需要将冲突解决才可以做后边的 commit 的操作:

在解决完冲突,并且 add 之后,就可以 commit 了。这里并不需要使用 commit 的 create 接口,rebase 部分提供了 rebase 专用的 commit 接口。

最后,做一下收尾 finish 掉这个 rebase 操作

示例代码:sample13

libgit2使用教程(十二)git tag

tag 的作用是作为重要节点的标记,在需要的时候可以直接切过去。所以在 git 管理系统,比如 github ,直接以带信息描述的 tag 作为 release。并且通常也习惯以版本编号作为 tag 名。

首先来看看仓库里有哪些 tag ,libgit2 提供了两个接口,它们有不同的作用。
1. list

输出的是 tag 的名字,并且没有更多的详细信息。
2. foreach

这个接口是通过回调的方式遍历所有的 tag。回调中的参数 name 返回的是 tag 的完整路径(refs/tags/tagname 这种形式,这样看实际上它也是一种 reference),这一点要特别注意;oid 是这个 tag 的 oid,如果要获取这个 tag 对象的指针,则需要使用 lookup 这个接口去获取——这个接口需要仓库指针作为参数,所以仓库指针需要通过 payload 传入。lookup 的时候有可能会出现找不到的情况,那是因为只有带描述的 tag 才能被找到,也就是说只有名字的轻量级 tag 是找不到的。

然后有必要解释一下什么是轻量级 tag 么?
轻量级 tag 是这个 tag 只有名字,没有其他信息。通过 git tag [tagname] 这个命令来创建;
除了轻量级 tag 之外还有一种就是带附注的 tag,这个 tag 要求创建的时候输入一段描述信息,通过 git tag -a [tagname] -m [message] 这个命令创建。

因此使用 libgit2 创建 tag 也有不同的接口来实现。
1. 轻量级 tag

因为 tag 是一种 reference 所以,它一定是以 commit 为节点的,所以 target 参数要找一个目标 commit。

2. 带附注的 tag

相比 lightweight 版本多了两个参数,一个是提交者的信息,另一个是这个 tag 附带的信息。这个信息在 github 的 release 中会对外显示。

下面介绍一个模糊搜索的功能,对应的是 git tag -l “*.*” 这个命令:

接下来是删除 git tag -d [tagname]

如果 error 返回的是 -3 也就是 GIT_ENOTFOUND 这个宏,表示并没有找到输入的这个 tag。

接下来,tag 作为一个标记,就是为了作为一个目标点可以直接切过去。使用命令行工具的命令是 git checkout [tagname],它实现的是将当前 head 切成一个空分支,这个分支指向的就是目标 tag。
然而,在代码中这个操作并不是通过 checkout 系列的接口实现,我们的目标是把对象换到 head 所以,需要的是设置当前的 head。

这里的重点是第二个参数,需要这个 tag 的 reference 形式的全名,在 foreach 的回调中可以获取到,在文件目录里也可以找得到。

最后,要将 tag 上传到远端仓库,需要在 push 的时候显示的指出。就像命令行:git push origin –tags。在代码中的重点就是 push 接口的 refspecs 参数,由这个参数指定 push 什么东西。

示例代码:sample12

libgit2使用教程(十一)git push

将本地分支上传到远端,如果前面的各个功能都掌握了的话,这个接口可以算是非常简单的了。

需要注意的细节就是 1. 身份验证;2. refspecs

验证证书的回调,是通过 git_push_options 这个结构传递的,与之前一样的 callbacks 参数。详情可以参考前面的

refspecs 是用来指定这个 push 操作的本地分支和远端分支。它的格式是本地分支远端目标分支中间以冒号隔开。

比如我要把本地的 master 推到远端,碰巧远端对应的分支也叫 master,那好,它就是这个样子的:
refs/heads/master:refs/heads/master

当然,我们必须制定远端目标,因为它不一定仅仅是 origin:

那么最终,push 操作的代码就是:

示例代码:sample11

libgit2使用教程(十)git clone

这是一个非常重要的操作。
这是一个非常简单的接口。
这是一个没多少内容又不得不提的主题。

直接上代码

需要注意的就是不要忘记身份验证,然后就开始 clone 操作了。

最后就简单介绍一下 git_clone_options 的主要参数:

checkout_opts
checkout 的配置,如果需要的话

fetch_opts
fetch 的配置,如果需要的话

bare
0 表示标准仓库,1 表示 bare 仓库

local
决定是否拷贝目标数据库而不是 fetch 。(如果目标是在本地,直接拷贝会更快一些)

checkout_branch
切到哪个分支,如果是空使用远端默认的分支

repository_cb
在这个回调里可以自己创建一个 repository

remote_cb
在这个回调里可以自己创建一个 remote

关于 bare 这个参数,不仅仅在 clone 这个操作用到。在 init 或 open 也是同样的意思。
表示只有版本记录,没有实际的工程里的代码和文件那些东西。更具体一点就是除了 .git 这个文件夹以外啥都没有。

示例代码:sample10

libgit2使用教程(九)git fetch & pull

目前 libgit2 对 pull 操作的支持还不是太好,所以目前能找到的资料都指出 pull 操作就是先 fetch 然后再 merge 目标分支。比如要实现 git pull origin master ,就先 fetch 然后将 master 切换到 head 再将 origin master 合并到 head。

那么就先 fetch

git_remote_fetch 这个接口的后三个参数都是可以传 null 的,但是如果报了下面这个错:
authentication required but no callback set
说明远端地址需要验证身份,所以我们要设置 ssh 证书的回调。

这样就完成了 fetch 操作。so easy

prune 也是 fetch 操作的一个重要参数。表示:清理掉远端已经删除的分支对应的本地分支,有点绕,不过应该还好理解吧。具体信息可以查一下下面这个命令。
git fetch -p (或 git fetch –prune)
在代码里实现只需要在 option 参数里做一个设置:

这样就强制使用 prune 参数了。另外几个可选枚举值可以去读注释。

接下来为了实现类似 pull 的效果,就是蛋疼的合并操作了,顺便可以复习一下之前的内容。
首先将本地 master 设为 head

然后去拿 origin master 的 commit

合并

解决冲突

add 和 commit

清空状态

打完收工。

示例代码:sample9

Android 自动化批量打包精粹

在项目即将上线的时候,都会遇到这样的问题:
有好多个有自己 SDK 的商店,而且都要赶同一个档期上线。
个别商店可能会有一些独特的小需求,比如 icon 的角标、开机闪屏 logo 、或者独有的功能接口要调用。
当10个 SDK 接完8个了,第1个接入的有更新了,需要接入最新版本才给上线。最要命的是接口有变动。
双方联调,找问题,各种沟通不畅。
下一个项目这些问题再重演一遍。

所以这个事情如果要做的优雅,高逼格,高效简洁,我们应该做到:
一次接入可以让所有项目通用。
各个部分充分解耦(SDK、项目、服务器),单独接入单独调试单独测试。
渠道商店配置可以由非技术人员(市场、运营)来管理。
当然不可少的打包应该有工具自动化完成。

简简单单几句话来描述这个事情,看起来挺简单,但是仔细想想,都挺空的。所以,还是要细化一下具体的东西。

一、 一次接入所有项目通用
要实现通用,就需要有一个比较稳定的接口将功能性 SDK 封装起来。封装起来的 SDK 我们可以姑且命名为 SDK 插件,各个项目调用统一的接口,而不需要关心 SDK 插件之中的具体的 SDK 。

二、各个部分解耦
实际上稳定统一的接口就是完成了解耦的第一步,我们需要梳理清楚具体部分的划分。渠道商店的 SDK 往往都会有服务器端接口部分,用作用户校验和支付验证。所以大致可以分为三个部分:SDK 插件开发、渠道商店对接服务器、客户端使用的 API。
各部分详细的设计参考下面的示意图:
结构图

开发 SDK 插件的流程:
前提条件:模板工程、可用的 SDK 、测试服务器接口
流程:通过模板创建工程 → 接入 SDK → 测试通过 → 导出 SDK 插件
SDK开发模板
当完成这一部分的时候,SDK 的对接就可以完全不需要核心项目团队的资源来支持了。这样就实现了一定程度的解耦,而核心项目部分,不用关心这些 SDK 功能的具体实现,只要接入一个用于测试各种 SDK 功能的测试 SDK 就可以无忧了。
测试SDK
从上面两个图可以看到,独立出一个 Platform Server 平台服务器来做与 SDK 相关的对接。这样的好处是,服务器端也实现了解耦;一个平台服务器可以服务于多个项目;并且不需要项目团队拿出资源来维护这个部分。而区分不同项目不同商店渠道只要以自定义标准参数的形式,制定出一套自己的标准就可以通用于所有接入的项目了。

对于测试 SDK 的设计,有必要叨唠两句。这个东西的目的是把通过 SDK 获取的数据直接给客户端,所以有些东西可以更直接一些。比如,需要登录获取一个 user id ,这种需求直接做一个输入框或者下拉列表,直接让开发人员输入或者选择 user id,就可以了。没有必要还让测试人员走一个完整的登录流程是不是?并且这样可以随意调整需要登录的账号,方便测试。支付同理,客户端需要处理在成功时会怎样,失败时会怎样,那么直接用两个按钮,返回给客户端结果就可以了。真的没有必要还调个支付宝啥的真去走个第三方支付流程。

三、 由非技术人员管理
因为实际上对于渠道商店出包的需求都是由非技术人员(运营、市场等)发起的,所以最好由非技术人员负责管理渠道商店的参数、数据、出包等。这样不仅可以解放出一小部分技术人员的生产力,最关键的是减少了沟通造成的损耗,当运营取得渠道相关参数之后,直接在工具中填写并提交然后工具自动读取后将需求的包打出来,这样这个流程一个人就可以完成了。
这个工作流的设计的关键当然是非技术人员以什么形式来提交给工具这些参数?最好的方案,当然是有一个非常友好的界面,可以让用户根据提示正确的填写各种参数以及提交资源等。但是如果在开发资源不足的情况下,退而求其次可以考虑使用 excel 表格,一般运营人员经过简单培训也是完全可以 hold 住的,只要将标准制定的全面且完善。

四、 自动化打包
首先只打一包的话使用 ant 一个命令就可以实现,并且同时完成包括 编译 打包 混淆 签名 等完整的流程。然而,最重要的是我们要根据之前填好的数据自动打出所有我们需要的包。
要实现这个需求,单靠 ant 脚本就不太现实了,虽然理论上也是可以实现的。这里还是建议使用强逻辑型的语言实现这个程序。在最后打包的环节再调用 ant 脚本完成打包的最后一步。
这个程序大框架上看是一个很简单的流水线,可以看下面的图。然后有很多的细节需要处理,这其中的任何小细节被忽略都有可能无法良好的工作。
流水线

编译 C++ 我的习惯是只编译一次,所有包使用同一个 so 这样可以最大程度的保证编译速度。需要传给 C++ 部分的一些参数可以通过 JNI 传递,运行效率是会有一些小影响,几乎可以忽略不计,可以让打包部分的复杂度降低不少。不过确实有一些特殊情况需要在 so 文件中打入一些数据,那就根据自己的情况再具体解决了。

我习惯在打每个包之前都删掉生成文件夹 bin 和 gen ,因为曾经有发生过不少次因为之前的生成文件的存在导致后来的改动并没有被编译进生成文件中。并且不要忘记,如果项目有引入 Library 的话,最好递归的去清理一些。

在对工程做任何改动之前都不要忘记做现场的保护。我的做法是,如果是对一个已经存在的文件做修改,我会把那个文件做备份和操作记录。再这个包打完或者出问题被中断后可以把现场恢复到最初的状态;如果是加入一个新的文件也要记录下来,在打完这个包将其删除。这样做可以最大程度的保障不会让当前操作污染到以后的事情。

可能有人注意到了,流水线这张图并没有提到 SDK Plugin 。实际上我赞成把 SDK Plugin 看做一个实体,只是为了描述更方便,理解更直观一些,具体存在形式最好是根据自己系统的情况量身定制。它可以只是散乱的一堆代码和一堆资源,也可以是其他合适的形式甚至是代码片段或者 patches ,如何使用需要根据自己的情况具体问题具体分析,而这个流水线图展示的是最纯粹的重要工作节点。

引入 Library 只需要修改 project.properties 中的 android.library.reference.* 参数,当然不要忘了在后边添加新的之前要先解析一下旧的,取到一个当前数字再添加新的数字。然而为什么是这个文件呢?它的秘密都在 build.xml 这个默认的 ant 脚本文件里,这个文件的注释读一读可以获得好多重要的信息。

默认的 ant 构建系统是需要自己生成 local.properties 这个文件,并且设定 sdk.dir 来显示指出 Android SDK 的路径。我喜欢把 build.xml 做个小改动,让这个变量直接访问环境变量:

不管是选择使用传统的 local.properties 还是修改 build.xml ,都必须要保证所有引入的 library 都有一份。我选择修改 build.xml 这个做法就是为了在不同环境中不用再多修改一个文件。

每一个渠道商店的包都必然会有一些常量参数,而使用这些参数的大多是相关的 SDK 。所以我的方案是生成一个常量类写到代码文件中替换掉之前埋在工程中用于测试的那个类。而这里最好将生成后的类做个 log,以便在出包后可以再检查一下数据是否有误,毕竟打错参数这种事情有时候并不容易被测试发现。

有许多 SDK 都会有要求在 AndroidManifest.xml 加一些东西,比如,声明 Activity、 Receiver 、权限等等,也有包括包名、版本号这些需要修改的东西。所以读写这个 xml 也是这个工具必不可少的一部分。那么数据是从哪里来的呢?首先,参数类的数据,肯定是从配置信息里获取的(包名、版本号、其他参数等);然后,与 SDK 有关的,肯定是不能让非技术人员来配置的,这部分就要作为 SDK Plugin 的一部分,可以在 SDK Plugin 中放一个专属这个 SDK 的 xml 配置,在这一步的时候从里边读取与工程中的 AndroidManifest.xml 合并。(有人选择使用替换特殊标示的方式来操作这个文件,这样的话就需要提前在这个文件里写很多特殊标识符,其后果是这个工程可能就不能直接编译运行调试了。想运行一下还需要先替换标识符感觉就有点累了,所以我认为还是读写 xml 是最靠谱的方案)

写 AndroidManifest.xml 的时候经常会遇到命名空间不见了的情况,表现为原文件的 android:xxxx 变成了 ns0:xxxx 。原因就是原来的 xml 命名空间被丢掉了,修改这个文件一定不要忘记手动设置一下 manifest 的命名空间:
xmlns:android=”http://schemas.android.com/apk/res/android”

签名的配置修改 ant.properties 这个文件,key.store 这个变量就是签名文件的路径,不过签名文件是需要提前生成好的,这个常打包的同学应该并不陌生,这里就不展开了。

要强调的一点是 .properties 文件写中文的话是会出错的,这个文件的编码是 ascii 所以如果传入的路径或文件名有包含非 ascii 字符的话,是需要转换成 \u4e00 这种形式的编码的。