Sketch 导出时保留透明像素

有些图标我们希望它的边缘要有一定的透明像素。

比如有一组图标作为 TableView 列表左侧的图标,如果这些图标的宽度不相同的话,会造成文字的左侧起点不能对齐。所以这一组图标的宽度必须都相同。

假设统一宽度为 20px,但是有的图标的实际尺寸可能不足 20px,所以一般我们会将这些图标的左右两边,填充透明像素,来使它达到宽度 20px。但是 Sketch 在导出 slice 时总是自作聪明地把透明像素裁切掉。

解决方法:

默认情况下,我们对一个 Symbol 点击 Make Exportable 后,会变成这个样子,Symbol 标志的右下角加了一个切片标志。这时候我们是不能改变 Export 的各种属性的。

在右侧 Export 区域,点击小刀图标:

就会变成这样的两个层:

然后我们就可以点击上面那个切片层,设置所有的 Export 属性,确保 Trim Transparent Pixels 未选中即可,并且调节 Size 和 Position,让切片左右两侧有透明区域。

注意的是,这时切片层和 Symbol 已经没有任何联系了,而是一个普普通通的切片,所以可以任意调节位置、大小等。

Local notification changes after iOS 8

大多数 iOS 开发者应该知道,在 iOS 8 的时候推送通知的开发有了些变化。主要体现在一些命名的变化(Remote 改为 User),另外相对之前只需要一句代码就能解决,现在需要多一些步骤。但总的来说改变并不大,实现原理并未改变。

如 iOS 7:

iOS 7
1
2
3
4
[[UIApplication sharedApplication] registerForRemoteNotificationTypes:(
UIRemoteNotificationTypeAlert |
UIRemoteNotificationTypeBadge |
UIRemoteNotificationTypeSound )];

iOS 8:

iOS 8
1
2
3
4
UIUserNotificationType notificationTypes = UIUserNotificationTypeBadge | UIUserNotificationTypeSound | UIUserNotificationTypeAlert;
UIUserNotificationSettings *notificationSettings = [UIUserNotificationSettings settingsForTypes:notificationTypes categories:nil];
[[UIApplication sharedApplication] registerUserNotificationSettings:notificationSettings];
[[UIApplication sharedApplication] registerForRemoteNotifications];

虽然很多人早就已经知道并在实践中作出了改变,但可能依然有些人并不知道 iOS 做出这种改变的原因。

原因是要对本地通知进行规范,并与远程推送通知进行统一。

之前对于本地通知,是无需取得用户授权的,只有远程推送类型的通知需要申请用户授权。上面的那些代码也只是针对远程通知来说的(所以它们之前的命名中都带有”Remote”字眼),本地通知根本无须注册,直接发送通知就好了。但其实这并不合理。

进行改变之后,本地通知跟远程推送通知的概念进行了统一,它们唯一的不同只是消息来源的不同。所以本地通知也需要进行授权。

在代码上,它们的注册也只有一句代码的差距:如果只需要本地通知,则不书写最后的一句:

1
[[UIApplication sharedApplication] registerForRemoteNotifications];

即可。

iCloud development

iCloud 开发的几种模式:

  1. Key-value Storage,在 iCloud 上保存一些简单数据,类似 NSUserDefault 的 iCloud 端实现。每用户最多 1MB 存储。
  2. iCloud Document,在 iCloud Drive 上以文件方式保存的数据。
  3. CloudKit,后端云存储。与上面两个不同,上面是用户自己的 iCloud 存储,只有用户自己可以读取自己的数据。而 CloudKit 是开发者的 iCloud,是与用户无关的,所有用户都可访问。类似 LeanCloud、Bmob 之类的服务。目前可以认为对开发者是免费的(因为免费额度绝对足够大),除了 iOS 外还提供了 JS 的访问方式。

具体可参考 About Incorporating iCloud into Your App

得到某月有几天

获取某月有几天,经典算法是按月份和是否闰年进行判断。

对于 iOS,可以直接用 NSCalendar:

1
2
3
NSDate *today = [NSDate date];
NSCalendar *c = [NSCalendar currentCalendar];
NSRange days = [c rangeOfUnit:NSDayCalendarUnit inUnit:NSMonthCalendarUnit forDate:today];

对于 js,也有一个巧妙的方法。js 里 Date 的构造方法 new Date("yyyy/MM/dd") ,当你传入的是 “yyyy/MM/0”,也就是日为 0 的话,得到的会是 yyyy/MM 月的上一个月的最后一天,这样就能通过它来得到上个月有几天了。

1
2
3
4
5
function getDaysInMonth(year,month){
month = parseInt(month,10) + 1;
var tempDate = new Date(year + "/" + month + "/0");
return tempDate.getDay();
}

国内 Android 应用市场对个人开发者并不友好

根据以往的经验,把一个 Android 应用在国内的应用市场上架简直容易得很--审核?他们真的会审核吗?看一眼就会点击”审核通过“按钮吧!只是国内的应用市场太多,对各种资料的要求不尽相同,所以会很繁琐。但通过审核从来都不会是什么问题。

所以我从来都不担心 Android 应用的上架。

但这次还真就通不过了,原因是--这次是个人开发者身份。国内这些 Android 应用市场对个人开发者并不友好。

在申请个人开发者账号时,就遇到了一些麻烦,有个市场要求必填固定电话,还有一个要求提供网络文化经营许可证,这直接就是把个人开发者拒之门外的意思。

提交应用后,被应用宝拒绝,理由:该应用内容包含支付功能,暂不支持个人开发商提交,请修改企业资质或删除支付相关功能后重新提交。

被 360 开放平台拒绝,理由:亲爱的开发者您好,应用名称(内容)体现为公司性质,名称:XX集团,请注册为企业开发者,并提供著作权或该公司授权证明后提交。

应用宝不允许个人开发者的应用带有支付功能,不知道这是出于什么考虑。但无法理解的是,提交的这个应用里并没有在线支付功能。360 提出的理由,这个应用的内容的确有公司性质,但是,好吧我也没什么好说的。

再次尝试依然得到了同样的结果,可能与相关的管理政策有关吧。还是不得不说,苹果对个人开发者非常友好,这可能也是 App Store 如此有生命力,诞生了这么多优秀作品的原因之一。

最后决定放弃在市场上线,直接发布 apk。

Android 代码混淆

Android Studio 工程中,默认就创建了 proguard-rules.pro 文件,可以在其中编写 Proguard 的混淆规则。

但是,默认是不打开混淆的,需要在 build.gradle 文件中,找到这一段:

build.gradle
1
2
3
4
5
6
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}

minifyEnabled 从 false 改为 true,即可开启在 release 打包时进行混淆。

为什么混淆开关的名字是 minify enabled ?所谓的”混淆“,其实也就是用无意义的符号(如 a,b,c)去代替代码中有意义的符号(如 title, imagePath, url 等),那么代码即使被反编译后,依然很难直接看懂其含义。从另一个角度看,其实也是让代码精简化的过程。(另外,混淆代码的英文是 Shrink Code)

打开混淆后,在 proguard-rules.pro 文件中编辑混淆规则即可。这个文件其实就是之前用 Eclipse 时的 proguard.cfg,混淆规则的写法是一样的,需要在其中写明哪些类不要进行混淆。

具体可参考 Android 文档 Shrink Your Code and Resources

需要注意的是,不要对第三方库进行混淆,否则调用第三方库的地方,将找不到对应方法,应该不难理解。LeanCloud 给出了一个混淆规则,可以参考:

Add adsense in hexo blog

首先,找到现在正在使用的主题所在目录,如:themes/light/。如果不确定,可查看 _config.yml 中 theme 的值。

在主题目录的 layout/_widget 下,放着所有右边栏的小工具。在这个目录下新建文件 adsense.ejs,并在其中填入:

adsense.ejs
1
2
3
<div class="widget tag">
把从 AdSense 里获得的代码全部复制到这里
</div>

最后,在主题根目录的 _config.yml 里,在 widgets 里面加入一项 - adsense 即可,如:

_config.yml
1
2
3
4
5
widgets:
- search
- recent_posts
- adsense
- tagcloud

这样就可以在右侧边栏里显示 Adsense 广告了。

原理:

Blank activity and Empty activity in Android Studio

Android Studio 新建 Activity 的模板里,有一个 Blank Activity,还有一个 Empty Activity。这两个是不同的。

Blank Activity,会为你新建这样一个 Activity:含有导航栏,导航栏上有返回按钮,并设置了导航栏标题,右下角有一个浮动按钮。

可以发现工程中新建了一个 NewActivity.java 的类,同时新建了两个布局文件,一个是 activity_new.xml,另一个是 content_new.xml。activity_new.xml 中是一个 CoordinatorLayout,并且 include 了 content_new.xml。这个 Activity 显示的内容实际在 content_new.xml 中,它是一个 RelativeLayout。

Empty Activity,会为你新建这样一个 Activity:含有导航栏,没有返回按钮,导航栏标题没有设置,其他都是空白。

工程中新建了 NewActivity.java 和 activity_new.xml,里面是一个 RelativeLayout。

要想实现 Blank Activity 的效果,并希望不要有多余的东西,在一个 xml 布局中实现,可以按照以下框架(下面以 EditPasswordActivity 为例):

Android layout 背景图片不显示的奇怪问题

遇到一个奇怪问题。给一个 Activity 设置了全屏的背景图片,方法是给这个 Activity 最父层的 layout 设置 background 属性为这个图片的 drawable。运行后发现图片没有显示,背景全部是白色。

但是在 Android Studio 的 xml 布局渲染调试器里,是正常显示的,在模拟器(API23)里运行,也是正常显示的。

更奇怪的是,把 background 属性换成一个颜色值,可以正常显示。换成另外的一张图,也可以正常显示。

http://www.tc5u.com/android/1371608.htm 问题中,有人提到:

如果楼主出现的是有的图片可以,有些图片不可以,请检查:
1、图片格式必须为png和jpg,也就是文件名称扩展名必须为.png或者.jpg
2、图片的分辨率宽度和高度都要控制在1000像素以内;
3、用Photoshop打开不能显示的图片,看是否为RGB格式,如果是CYMK格式的图片是不能显示的;
4、图片文件名称必须全部小写,不能有大写的文件名称。

发现是由于图片尺寸过大(宽、高超过了1000像素),缩小尺寸后就可以正常显示出来了。

还没调查清楚这是不是 Android 的约束,如果真的有这样的约束,那实在理解不能--现在的手机 1080p 的屏幕满大街都是,还动不动就上 2K,1000px 实在是不够啊。

Android application display name

Android 应用的显示名称,在 AndroidManifest.xml 中进行修改。

AndroidManifest.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity
android:name=".LoginActivity"
android:label="@string/title_activity_login">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
...
</activity>
</application>

我们会很显然的认为,修改最外层的 android:label 即可,也就是修改 @string/app_name 的值。但经过实践会发现并不是这样的,Launcher 里显示的竟然是 @string/title_activity_login,也就是 Login Activity 的标题。

实际上,在 Launcher 中显示的应用名称,是被标记为首页面的 Activity 的标题。也就是被标记为:

AndroidManifest.xml
1
2
3
4
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>

的 Activity。

那 application 标签的 android:label 属性是用来做什么的?它的确如它的名字一样,代表了应用的名称。在应用程序管理器中看到的名称,就是这个名称。但是这个名称并不是 Launcher 用的名称,所以用户在手机的桌面(应用程序列表)中,看到的不是这个名称。

这着实让人感到不可理解。自认为还是 iOS 的处理方式更科学--在 info.plist 文件中有一个属性 Bundle display name,在这里面存储了应用的显示名称,这是一个应用全局的设置。

那如果首页面 Activity 的标题,跟应用的显示名称不能保持一致,怎么办?那就在 AndroidManifest.xml 里,把首页面 Activity 的 label 设置成应用名称,然后在首页面 Activity 的 onCreate 里,用 setTitle 方法再把标题改掉就好了,如:

1
2
3
4
5
6
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setTitle("登录");
setContentView(R.layout.activity_login);
...
}