Như mọi người biết thì apple giới hạn dung lượng tối đa có thể download game/app từ apple store bằng Cellular (3G/4G/LTE) là 100MB. Nếu Unity để phát triển game thì dung lượng của game quả là 1 vấn đề không hề nhỏ, nhất là với game social. Dung lượng quá lớn vượt quá 100MB, user sẽ phải dùng đến wifi để có thể download được game của bạn. Điều đó sẽ làm giảm lượng download game của bạn.
Có nhiều cách để có thể giảm dung lượng của game. Nhưng với những game social, resource (ảnh, audio) rất lớn, việc tối ưu hoá file dưới 100MB là điều bất khả thi. Unity có hỗ trợ Assetbundle để giải quyết vấn đề này. Developer có thể đưa 1 phần resource lên server và user có thể tải phần đó sau khi mở game.
Quay trở lại vấn đề về dung lượng file. Vậy cho bao nhiều resource lên server để download sau khi user mở game là đủ? Nếu lạm dụng Assetbundle nhiều quá sẽ làm cho lượng resource phải tải về từ server của mình là khá lớn. Server sẽ chịu tải nhiều. Thường thì chúng ta sẽ cố gắng để cân bằng 2 điều đó là dung lượng file dưới 100MB và lượng resource download bằng Assetbundle không quá lớn.
Sau khi build làm thế nào để tính được dung lượng file ipa?
Mình giới thiệu 1 cách hiện giờ mình vẫn đang áp dụng cho game của mình.
Đầu tiên bạn build file ipa (release version). Ví dụ là `mygame.ipa`
Dùng Terminal gõ lệnh: ls -la {đường đẫn đến file}
vd: ls -la mygame.ipa
Kết quả sẽ hiện:
-rw-r--r--+ 1 guest-user staff 62113983 10 29 11:24 mygame.ipa
Tiếp tục bạn gõ: unzip {đường đẫn đến file}
vd: unzip mygame.ipa
Lúc này file ipa sẽ được unzip.
Unzip xong bạn sẽ thấy file {app-bundle}.app trong thư mục Payload vừa được unzip
Tiếp theo gõ : otool -l Payload/{app-bundle}.app/{app-bundle} | grep cryptsize
vd: otool -l Payload/my-game.app/my-game | grep cryptsize
Kết quả:
cryptsize 29884416
cryptsize 34668544
Tính tổng:
62113983 + 29884416 + 34668544 = 126,666,943 bytes
File ipa có size ~ 100MB sau khi up lên apple store theo tính toán như ở trên có kết quả là 125,687,674 bytes
Dựa vào so sánh giữa 2 kết quả này ta thấy file mygame.ipa dung lượng đã vượt qua 100MB
126,666,943 - 125,687,674 = 979,269 ~ 0.93MB
Chúng ta sẽ phải đưa bớt resource thành Assetbundle hoặc tối ưu lại chúng.
Mong là bài viết này hữu ích cho các bạn.
Kid Blog
Thursday, October 29, 2015
Sunday, May 10, 2015
[Tản Mạn] Thủ tục bảo lãnh vợ/chồng sang Nhật - kĩ sư 5/2015
Các tài liệu cần nộp khi làm đơn bảo lãnh người thân (vợ) sang Nhật của
kĩ sư: 1. Chuẩn bị từ Việt Nam: ①. Giấy kết hôn dịch tiếng Nhật và công chứng……..................…………..1 bản. ②. Hộ chiếu của vợ photocopy (không cần công chứng)…................…….1 bản. ③. Ảnh hồ sơ 3cmx4cm của vợ nền xanh hoặc trắng, không đổ bóng……1 tấm. Bản công chứng và ảnh làm trong vòng 6 tháng đến thời điểm nộp đơn. 2. Chuẩn bị tại Nhật: ④. Giấy chứng nhận là nhân viên chính của công ty (在職証明書). Xin tại công ty………………………………………....……………..1 bản. ⑤. Bảng lương trong 1 năm.
Xin tại công ty có dấu công ty là tốt nhất……..…....……………..1 bản.
⑥. Giấy chứng nhận đóng thuế có 2 loại:
- 納税証明 (1 の年度,税目につき1 件 300 円)................................................1 bản. - 課税証明 (1 件 300 円)………………………...........................……………....1 bản. Xin tại 市役所.
Trong trường hợp bạn nào mới sang Nhật làm việc thì chưa có giấy chứng nhận thuế thì phải ra ngân hàng xin lịch sử giao dịch.
納税証明: năm đầu chưa phải đóng nên chưa có.
⑦. Giấy chứng nhận đăng kí nguyên bản của thẻ ngoại kiều, 登録原票記載事項証明書 とうろくげんぴょうきさいじこうしょうめいしょ
giờ có 1 tên khác
(1件 300 円)……….......................................................………… 1 bản.
Xin tại 市役所 しやくしょ . ⑧. Hộ chiếu của mình photocopy (không cần công chứng)……..1 bản. ⑨. Thẻ ngoại kiều photocopy (không cần công chứng)……....... .1 bản. ⑩. Phong bì loại thường (書留の封筒) có dán sẵn tem (tem 392 yen). và ghi rõ địa chỉ nhận thư của mình. Mua tại コンビニ (Family mart..) ....1 bản. Nên mua loại phong bì to để không bị nhàu giấy. ⑩. Đơn xin cấp giấy chứng minh thừa nhận tư cách định cư. 在留資格認定証明書交付申請書 Là mẫu dưới đây đã được download tại trang chủ của bộ tư pháp Nhật http://www.immi-moj.go.jp/english/tetuduki/kanri/shyorui/01-format.html. (mục thứ 11 nhé)
Tư cách của người viết đơn là vợ, người được ủy quyền viết đơn là mình có đóng
dấu của mình. Và ảnh 3x4 đã chuẩn bị của vợ “③” được dán vào đây. Mẫu đơn này được viết trước ở nhà.
Và thư mời download tại đây http://www.moj.go.jp/content/000007382.pdf
Các giấy tờ chuẩn bị tại Nhật làm trong vòng 3 tháng đến thời điềm nộp đơn. Giấy ⑥, ⑦. xin tại 市役所
chỉ mất 30 phút. 市役所
làm việc từ 8h30~ 17h15 thứ 2 đến thứ 6. ※ Khi đầy đủ các mẫu ① ~ ⑩ thì tất cả được nộp tại cục quản lý xuất nhập cảnh,入管(東京入国管理局__
địa chỉ:
Tokyo:
東京都港区港南5-5-30).
Yokohama:
神奈川県横浜市
金沢区鳥浜町10−7
東京入国管理局横浜支局
làm việc ừ 9h00 ~12h00 & 13h00 ~ 16h00 thứ 2 đến thứ 6. Sau khi nộp nhận
lại biên lai đã thu là xong.
Thời gian xét đơn trong vòng 3 tháng (có thể nhanh hơn, như mình là 2 tuần).
Ai ở Yokohama thì không nên làm ở cục xuất nhập cảnh ở trên tokyo vì sẽ đông và đợi lâu
Nếu không có vấn đề gì, sẽ được nhận giấy OK gửi về theo bưu điện. Chỉ cần gửi Certificate of Eligibility để vợ ở Việt Nam nộp để xin visa (mình gửi dịch vụ EMS của bưu điện, mất 3-4 ngày đến nhà, mất 900 yen)
* Ở nhà xin visa thì cần 5 loại giấy tờ
- Certificate of Eligibility (bạn gửi từ Nhật về) : bản chính và bản photo không cần công chứng
- Hộ chiếu của vợ
- Đăng kí kết hôn: bản chính + photo
- Đơn xin visa down trên trang chủ của đại sứ quán Nhật tại VN.
- Ảnh 3x4 đằng sau ghi tên
|
Tuesday, January 27, 2015
[NOTE] How to Deal With an iPhone Crash Report
Kudos to the app review team at Apple, they found a crasher in the update to Emergency List that I recently submitted. They even took the time to step through and document what they did exactly to reproduce the error.
From their email:
1. Launch app connected to a network
2. Tap Emergency List
3. Tap the Add button
4. Tap Add from Address Book
5. App crashes
Please refer to the attached crash logs.
They even sent two crash logs to help find the problem.
Reading the Crash Logs
The crash logs by themselves are very difficult to read. Below is a segment:
Thread 0 Crashed:
0 libSystem.B.dylib 0x3293f98c 0x328c1000 + 518540
1 libSystem.B.dylib 0x3293f97c 0x328c1000 + 518524
2 libSystem.B.dylib 0x3293f96e 0x328c1000 + 518510
3 libSystem.B.dylib 0x3295461a 0x328c1000 + 603674
4 libstdc++.6.dylib 0x30a143b0 0x309cf000 + 283568
5 libobjc.A.dylib 0x3347a858 0x33475000 + 22616
6 libstdc++.6.dylib 0x30a12776 0x309cf000 + 276342
7 libstdc++.6.dylib 0x30a127ca 0x309cf000 + 276426
8 libstdc++.6.dylib 0x30a12896 0x309cf000 + 276630
9 libobjc.A.dylib 0x33479714 0x33475000 + 18196
10 CoreFoundation 0x3355ab86 0x33534000 + 158598
11 CoreFoundation 0x3355ab24 0x33534000 + 158500
12 Foundation 0x326cf9ae 0x3267e000 + 334254
13 Foundation 0x32685760 0x3267e000 + 30560
14 EmergencyNumbers 0x0000641c 0x1000 + 21532
15 EmergencyNumbers 0x000060d2 0x1000 + 20690
16 UIKit 0x31c63aa4 0x31c17000 + 314020
17 UIKit 0x31c7aec0 0x31c17000 + 409280
18 UIKit 0x31c7ace0 0x31c17000 + 408800
19 UIKit 0x31c7abde 0x31c17000 + 408542
20 UIKit 0x31c7a784 0x31c17000 + 407428
21 UIKit 0x31c7a59c 0x31c17000 + 406940
22 UIKit 0x31c7a4e0 0x31c17000 + 406752
23 UIKit 0x31c65df4 0x31c17000 + 323060
24 UIKit 0x31c4a574 0x31c17000 + 210292
25 QuartzCore 0x30039004 0x30030000 + 36868
26 QuartzCore 0x30038e1c 0x30030000 + 36380
27 QuartzCore 0x3003893c 0x30030000 + 35132
28 QuartzCore 0x300386a2 0x30030000 + 34466
29 QuartzCore 0x3003e02a 0x30030000 + 57386
30 CoreFoundation 0x33543b50 0x33534000 + 64336
31 CoreFoundation 0x3358aa32 0x33534000 + 354866
32 CoreFoundation 0x3358a356 0x33534000 + 353110
33 GraphicsServices 0x3352bb2c 0x33528000 + 15148
34 GraphicsServices 0x3352bbd8 0x33528000 + 15320
35 UIKit 0x31c19768 0x31c17000 + 10088
36 UIKit 0x31c1846c 0x31c17000 + 5228
37 EmergencyNumbers 0x000020b4 0x1000 + 4276
38 EmergencyNumbers 0x00002070 0x1000 + 4208
All I can read from this is that lines 14 and 15 above in the stack trace is where the crash happened in MY code. But what does 0x0000641c mean?
Fortunately, I had the dSYM file for the release that they reviewed handy. I ran the following command against it:
dwarfdump --lookup 0x0000641c -arch armv6 EmergencyNumbers.app.dSYM
This command would tell me at what line the app failed. And here it is:
Line table file: 'AddressBookLookupTableViewController.m' line 60,
column 0 with start address 0x000000000000641a
Determining the cause of the Crash
Going to that file and line in the source code reveals:
1 2 3 |
|
So the crash is happening in the
NSString stringWithString
. If cfName
is equal to nil, this line of code will crash.Understanding the Error
But this error makes no sense to me. In line 59,
cfName
is populated with the composite name from a valid address book record (person is not nil). According to Apple’s reference documentation, ABRecordCopyCompositeName returns:For group records: The value of the group name property
(kABGroupNameProperty).
For person records: The concatenated value of these properties:
Prefix, Suffix, Organization, First name, and Last name.
I just assumed that all contact records in the address book would actually have a name of some kind. And I was wrong! It turns out that it is possible to create address book entries with none of the above fields filled in. And my code should support that.
The Fix
Easy. If
cfName
is nil
, use a default name called “No Name”, just like the address book on Mac OS X. I noticed that the iPhone address book works differently in that it has a cascade of which fields it shows before coming up with “No Name”, but that is not necessary for my app.1 2 3 4 5 6 7 8 9 10 11 |
|
Lessons Learned
- Always save the dSYM file from each release so that you can drill on crash reports.
- Always save a snapshot of each release version in source code control so you can pull out the right code base to match the crash report.
- Watch out for assumptions when writing code. I assumed all address book contacts would have names. I was wrong!
Once again, thanks Apple Review People for catching this.
(source) http://noverse.com/blog/2010/03/how-to-deal-with-an-iphone-crash-report/
Wednesday, October 29, 2014
The iOS Data Storage Guidelines indicate that only content that the user creates using your app, e.g., documents, new files, edits, etc., should be backed up by iCloud.
My app is rejected by apple.
In particular, we found that on launch and/or content download, your app stores XXX MB. To check how much data your app is storing:
- Install and launch your app
- Go to Settings > iCloud > Storage & Backup > Manage Storage
- If necessary, tap "Show all apps"
- Check your app's storage
The iOS Data Storage Guidelines indicate that only content that the user creates using your app, e.g., documents, new files, edits, etc., should be backed up by iCloud.
Solution:
Excluding a File from Backups on iOS 5.1
Setting the Extended Attribute on iOS 5.0.1
Source: https://developer.apple.com/library/ios/qa/qa1719/_index.html
In particular, we found that on launch and/or content download, your app stores XXX MB. To check how much data your app is storing:
- Install and launch your app
- Go to Settings > iCloud > Storage & Backup > Manage Storage
- If necessary, tap "Show all apps"
- Check your app's storage
The iOS Data Storage Guidelines indicate that only content that the user creates using your app, e.g., documents, new files, edits, etc., should be backed up by iCloud.
Solution:
You should prevent files from being backed up to iCloud and iTunes!!
iOS 5.1 and later
Starting in iOS 5.1, apps can use either NSURLIsExcludedFromBackupKey or kCFURLIsExcludedFromBackupKey file properties to exclude files from backups. Either of these APIs is preferred over the older, deprecated approach of directly setting an extended attribute. All apps running on iOS 5.1 should use these APIs to exclude files from backups.
- (BOOL)addSkipBackupAttributeToItemAtURL:(NSURL *)URL |
{ |
assert([[NSFileManager defaultManager] fileExistsAtPath: [URL path]]); |
NSError *error = nil; |
BOOL success = [URL setResourceValue: [NSNumber numberWithBool: YES] |
forKey: NSURLIsExcludedFromBackupKey error: &error]; |
if(!success){ |
NSLog(@"Error excluding %@ from backup %@", [URL lastPathComponent], error); |
} |
return success; |
} |
iOS 5.0.1
If your app must support iOS 5.0.1, you can use the following method to set the "do not back up" extended attribute. Whenever you create a file or folder that should not be backed up, write the data to the file and then call this method, passing in a URL to the file.
#import <sys/xattr.h> |
- (BOOL)addSkipBackupAttributeToItemAtURL:(NSURL *)URL |
{ |
assert([[NSFileManager defaultManager] fileExistsAtPath: [URL path]]); |
const char* filePath = [[URL path] fileSystemRepresentation]; |
const char* attrName = "com.apple.MobileBackup"; |
u_int8_t attrValue = 1; |
int result = setxattr(filePath, attrName, &attrValue, sizeof(attrValue), 0, 0); |
return result == 0; |
} |
iOS 5.0
It is not possible to exclude data from backups on iOS 5.0. If your app must support iOS 5.0, then you will need to store your app data in
Caches
to avoid that data being backed up. iOS will delete your files from theCaches
directory when necessary, so your app will need to degrade gracefully if it's data files are deleted.Source: https://developer.apple.com/library/ios/qa/qa1719/_index.html
Thursday, October 16, 2014
[Unity] Use of undeclared identifier 'GL_RGBA8_OES' in xcode 6
I'm having a similar issue with my Unity project not compiling with the Xcode 6 and the iOS 8 SDK. Xcode 6 gives me five errors, all in the GlesHelper.mm file (which, if I'm not mistaken, is part of Unity). All five are semantic issues:
- Line 63: Use of undeclared identifier 'GL_RGBA8_OES'
- Line 123: Use of undeclared identifier 'glRenderbufferStorageMultisampleAPPLE'; did you mean 'glRenderbufferStorageMultisample'?
- Line 133: Use of undeclared identifier 'GL_DEPTH_COMPONENT24_OES'
- Line 135: Use of undeclared identifier 'GL_DEPTH24_STENCIL8_OES'
- Line 143: Use of undeclared identifier 'glRenderbufferStorageMultisampleAPPLE'; did you mean 'glRenderbufferStorageMultisample'?
Answer:
Please add
#include <OpenGLES/ES2/glext.h>
Saturday, August 23, 2014
[English] Useful Macros
1. [NSString stringWithFormat:]
#define STRFORMAT(f, ...) [NSString stringWithFormat:f, __VA_ARGS__]
NSString* message = [NSString stringWithFormat:@"HELLO %3.1d", 333.333];
-> NSString* message = STRFORMAT(@"HELLO %3.1d", 333.333);
2. NSLocalizedString
#define LSTR(s) NSLocalizedString((s), nil)
NSString* message = NSLocalizedString(@"KEY", nil);
-> NSString* message = LSTR(@"KEY");
3. Class Method
#define ME [self class]
[ExampleClass classMethod];
-> [ME classMethod];
4. Block's __weak self
#define PREPARE_SELF __weak typeof(self) SELF = self
__weak MyClass* SELF = self;
[self doSomethingWithBlock:^{
[SELF showAlert];
}];
->
PREPARE_SELF;
[self doSomethingWithBlock:^{
[SELF showAlert];
}];
5. Hex Color
#define HEXCOLOR(c) [UIColor colorWithRed:((c>>16)&0xFF)/255.0 green:((c>>8)&0xFF)/255.0 blue:(c&0xFF)/255.0 alpha:1.0]
HEXCOLOR(0x34ff78);
6. 4 inch Size Check
#define STRFORMAT(f, ...) [NSString stringWithFormat:f, __VA_ARGS__]
NSString* message = [NSString stringWithFormat:@"HELLO %3.1d", 333.333];
-> NSString* message = STRFORMAT(@"HELLO %3.1d", 333.333);
2. NSLocalizedString
#define LSTR(s) NSLocalizedString((s), nil)
NSString* message = NSLocalizedString(@"KEY", nil);
-> NSString* message = LSTR(@"KEY");
3. Class Method
#define ME [self class]
[ExampleClass classMethod];
-> [ME classMethod];
4. Block's __weak self
#define PREPARE_SELF __weak typeof(self) SELF = self
__weak MyClass* SELF = self;
[self doSomethingWithBlock:^{
[SELF showAlert];
}];
->
PREPARE_SELF;
[self doSomethingWithBlock:^{
[SELF showAlert];
}];
5. Hex Color
#define HEXCOLOR(c) [UIColor colorWithRed:((c>>16)&0xFF)/255.0 green:((c>>8)&0xFF)/255.0 blue:(c&0xFF)/255.0 alpha:1.0]
HEXCOLOR(0x34ff78);
6. 4 inch Size Check
#define IS_IPHONE4INCH (([[UIScreen mainScreen] bounds].size.height-568)?NO:YES)
if (IS_IPHONE4INCH) {
.....
}
7. iOS Version Major
#define IOS_VERSION_MAJOR [[[[[UIDevice currentDevice]systemVersion] componentsSeparatedByString:@"."] objectAtIndex:0] intValue]
if (IOS_VERSION_MAJOR < 7) {
....
}
8. Document Path
#define DOCUMENT_PATH [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0]
9. NSLog
In release version you want to remove all NSLog, but when debug code's log is very usefully. This macro disable NSLog in release version and enable in debug.
#ifdef DEBUG
# define LOG(...) NSLog(__VA_ARGS__)
# define LOG_CURRENT_METHOD NSLog(@"%@/%@", NSStringFromClass([self class]), NSStringFromSelector(_cmd))
#else
# define LOG(...) ;
# define LOG_CURRENT_METHOD ;
#endif
NSLog(@"Test");
-> LOG(@"Test");
10. Async
#define TTAsync(...) dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ##__VA_ARGS__)
11. Async Main Queue
#define TTAsyncMain(...) dispatch_async(dispatch_get_main_queue(), ##__VA_ARGS__)
12. Dispatch Once
#define TTOnce(...) { \
static dispatch_once_t __wb_oncePred; \
dispatch_once(&__wb_oncePred, ##__VA_ARGS__); \
}
Monday, August 18, 2014
[English] The Push-Notification on iOS8
In iOS8 maybe "registerForRemoteNotificationTypes:" is deprecated. So if you wanna get notification list in iOS 8. You should follow this way.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
//-- Set Notification
if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 8.0)
{
[[UIApplication sharedApplication] registerUserNotificationSettings:[UIUserNotificationSettings settingsForTypes:(UIUserNotificationTypeSound | UIUserNotificationTypeAlert | UIUserNotificationTypeBadge) categories:nil]];
[[UIApplication sharedApplication] registerForRemoteNotifications];
}
else
{
[[UIApplication sharedApplication] registerForRemoteNotificationTypes:
(UIUserNotificationTypeBadge | UIUserNotificationTypeSound | UIUserNotificationTypeAlert)];
}
//--- your custom code
return YES;
}
Subscribe to:
Posts (Atom)