2013年12月4日 星期三

grep + less with color


grep -R --color=always "search string" * | less -R


若只有--color的話預設為--color=auto,pipe with less使用的話不會有顏色

less -R才會翻譯顏色escape charactor


2013年11月26日 星期二

linux 使用swap memory

最近嘗試在aws ec2 上deploy 一個ror的project

但因為使用的是free trail instace所以memory很小,deploy的時候會噴cannot allocate memory

要切一塊disk空間作為swap memory以解決這個問題


首先先確認是否已經存在swap memory:

sudo swapon -s


如果沒有會顯示如下
Filename    Type  Size Used Priority


在確認沒有swap memory後檢查我們是否有足夠的硬碟空間,我們要做一塊512mb的swap memory所以要至少這麼多

df -h
Filesystem      Size  Used Avail Use% Mounted on
/dev/xvda1      7.9G  3.5G  4.1G  47% /
udev            288M   12K  288M   1% /dev
tmpfs           119M  176K  118M   1% /run
none            5.0M     0  5.0M   0% /run/lock
none            296M     0  296M   0% /run/shm


開始建立swap memory

sudo dd if=/dev/zero of=/swapfile bs=1024 count=512k
sudo mkswap /swapfile
執行完會顯示
Setting up swapspace version 1, size = 262140 KiB
no label, UUID=xxxxxxxxxxxxxxxxxxxxxxxxxxx

接著使用這個空間
sudo swapon /swapfile

不過只是這樣重開機的話會失去效用,所以要把這塊swap memory加入fstab(file system table)中
sudo vim /etc/fstab

加入這行
 /swapfile       none    swap    sw      0       0 

記得把Swappiness設定為0, 這樣可以確保os只有在緊急的時候(例如不swap就噴out of memory)才做swap動作,增加效能

sudo su -
echo 0 > /proc/sys/vm/swappiness
exit



2013年11月11日 星期一

Android: 使用LightingColorFilter 來做使用image background的button的onClick特效

以往我在做用其他image為background的button的onClick特效時,都要準備好兩張image,

一張為按下去前,一張為按下去後,實為麻煩.

後來發現只要使用Android sdk內建的LightingColorFilter就可以用一張圖達到不錯的類似效果:


mTestBtn.setOnTouchListener(new OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { // TODO Auto-generated method stub if(event.getAction() ==MotionEvent.ACTION_DOWN) v.getBackground().setColorFilter(new LightingColorFilter(0xFF999999, 0xFF000000)); else if(event.getAction() ==MotionEvent.ACTION_UP) v.getBackground().clearColorFilter(); return false; } });

Android:setting text size in pixel


textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, valueInPixel);

2013年11月5日 星期二

Android: 當資料庫越來越肥大 之 容易擴充的DB架構

使用這樣的寫法在修改或擴增database的時候可以比較舒服(我覺得啦XD)
不過如果只是要寫一個小小且不太會擴充的db那就先不需要這把牛刀了!

後面有完全沒有整理過(囧)的範例code,可能看code比較好懂.

此架構大致分為五個部分:

DBHelper


  • 這個就是一般在寫db的時候繼承SQLiteOpenHelper而來的class,這邊增加了一些function讓操作更便利一點.擔負起了依照schema來建立table、實際操作db等重責大任.
  • 這個class是獨立於project的,可以直接貼到別的project.

DBSchema


  • 掌握了整個db的架構,建立實體以後會喂给DBHelper的constructor吃.
  • 這個class幾乎也獨立於不同project,除了要在變數定義的時候告知這個project中table的相關資訊.另外加入新的Table時也記得要在這裡增加定義.

DBTable

  • 每一個table有自己專屬的class,所以加入新table的時候基本上不會動到舊的架構.
  • 提供的method儘量不存取到自己以外的table.
  • 提供存取自己table 的method给data provider使用.

Data Provider

  • 扮演db的adapter角色,可以存取多個db並且將資料處理組合後以便使用.
  • app的其他部分需要存取資料理當只會使用到Provider,不會直接使用DBTable.
  • Provider理當是唯一會直接使用到DBTable的class.
  • 可以視需求增加不同的data provider

Global initialization

因為DBHelper是採用singleton,所以用application context來做初始化.
我是另外建立了一個application class來確保db在被任何activity使用前已經初始劃完成.



建立一個新的table步驟:

  1. 先建立一個table class.
  2. 去Schema中加入Table資訊.
  3. 接著就可以在data provider中使用這個table.

剩下的就讓code自己來解說吧XD



2013年10月16日 星期三

Android: 建立一個configure class當作參數以增加constructor的彈性

如果初始化一個class所需要設定的參數太多,會造成這個class的constructor過長,這個情況可以利用自定義一個config class作為參數給constructor吃來解決.


這樣做有幾個特點:

  1. 可以確保在物件建構前設定所有建構時所需要的資訊
  2. 統一參數設定時機
  3. config class中對於optional的參數可以有default值
  4. 設定參數時可以有很大的自由度不用依照constructor規定的參數順序.
  5. 不代表之後不能變動參數,還是可以加入function 如MyClass.setNewHeight()


用法:


//usage //為何要使用Builder與其實作後面說明 MyClassConfig config = MyClassConfig.Builder() .setHeight(100) .setWidth(50) .setXXX(...) .setYYY(...) .build(); MyClass mc = new MyClass(config); //constructor MyClass(MyClassConfig config) { this.height = config.height; this.width = config.width; //other initialization ... }


接下來介紹config class的實作,架構如下:


public class MyClassConfig { //1.config class fields ... //2.config class constructor ... //3.static inner builder class: public static class Builder { // 3.1 builder fields ... // 3.2 builder constructor ... // 3.3 builder setter ... // 3.4 build() function ... } // end of builder }//end of MyClassConfig


1. config class fields

  //首先定義fields,使用final qualifier來確保config建構的時候要完成全部的設定 final int height; final int width; ...

使用final qualifier有兩個用意

  • 確保全部的設定在config建構的時候就會完成
  • MyClass要可以直接存取這些fields所以不能是private但又不希望config建構後fields還會被更動
(ps1. java沒有指定private或public時候的存取權是package)
(ps2. final 變數除了在宣告的時候直接assign值外唯一的設定機會是在constructor中)

藉由Builder的幫助來建立fields為final的config class

2.config class constructor



config class的constructor吃一個Builder物件作為參數

public MyClassConfig(Builder pBuilder) { this.height = pBuilder.height; this.width = pBuilder.width; ... }



3.static inner builder class


這邊加上static是因為static inner class不用reference到outer class 的instance,才可以直接用以下方式建立:

new MyClassConfig.Builder();

最後是builder 的實作


public static class Builder{ //3.1 builder fields private int height; //可以在這邊設定default value,如果build過程中沒有被更改,default value會被assign到最後build出來的config class中 private int width = 100; //3.2 builder constructor //如果建立config的時候需要一些外部資訊可以當作builder constructor的參數傳入 public Builder(){}; //3.3 builder setter //設定config參數的functions, 回傳自己是為了可以達到method chaining的效果 //i.e., builder.setHeight(100).setWidth(100).build(); public Builder setHeight(int h) { this.height = h; return this; } public Builder setWidth(int w) { this.width = w; return this; } //3.4 build function //最後建立出config的function public MyClassConfig build() { return new MyClassConfig(this); } }

大概就是這樣,最後附上一段之前寫的code作為範例

package itri.u9lab.towolf.ratiofixer; public class RatioLayoutConfig { final int virtualWidth; final int virtualHeight; final boolean isFullScreenMode; /* * constructor */ public RatioLayoutConfig(Builder pBuilder) { this.virtualHeight = pBuilder.virtualHeight; this.virtualWidth = pBuilder.virtualWidth; this.isFullScreenMode = pBuilder.isFullScreenMode; } /* * config builder */ public static class Builder { private int virtualWidth = 768; private int virtualHeight =1230; boolean isFullScreenMode = false; public Builder() { } public Builder setVirtualSize(int pWidth,int pHeight) { virtualWidth = pWidth; virtualHeight = pHeight; return this; } public Builder setFullScreen(boolean mode) { isFullScreenMode = mode; return this; } public RatioLayoutConfig build() { return new RatioLayoutConfig(this); } }//end of builder public static RatioLayoutConfig getDefaultConfig() { return new Builder().setFullScreen(false).setVirtualSize(768, 1230).build(); } }

2013年9月10日 星期二

iOS 自定義動態資料TableView (customized tableview) + 附加section header使用

此篇環境為iOS 5以上, 使用storyboard.

分為兩個部分,第一部分為基本的tableview,

第二部分加上section header.


Part 1,基本table view




使用table view 分成兩個步驟

一、設定stroy board


  1. 首先拉一個TableView元件到view controller中
  2. 再拉一個table view cell元件到table view 中
  3. 在table view cell中放好想要的ui配置,如下圖

  接下來幾個關鍵步驟!

  4.在table view cell的attribute inspector中Identifier要設定一個名字,之後才能抓到這個cell layout,如下



  5.在每一個table view cell的元件中的attribute inspector中tag欄位,設定一個號碼,之後才可以抓到這個元件,如下


  6.最後在這個table對應到的view controller上面define一個IBOutlet,並且與table相連

至此story board上的設置已經完成



二、在view controller中加code

1. 在header file (.h)檔案中

採用UITableViewDelegate,UITableViewDataSource兩個protocol:

@interface YourViewController : UIViewController <UITableViewDelegate,UITableViewDataSource>

並且定義上面所提到的IBOutlet:

@property (weak, nonatomic) IBOutlet UITableView *mTableView;


和一個存放資料的Array:

NSMutableArray* tableData;

2.在implementation file(.m)檔案中

implement以下幾個dataSource protocol function

-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return [tableData count]; }
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { static NSString *simpleTableIdentifier = @"announceTableCell"; //使用在story board設定的identifier才會抓到story board中的table view cell layout UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier]; //如果已經存在則重複使用 if (cell == nil) { cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:simpleTableIdentifier]; } YourDataType *data = [tableData objectAtIndex:indexPath.row]; //利用在story board中設定的tag取得table view cell中的特定component UILabel *titleLabel = (UILabel*)[cell viewWithTag:1]; [titleLabel setText:data.title]; UILabel *contentLable = (UILabel*)[cell viewWithTag:2]; [contentLable setText:data.description]; return cell; }

3.在改變tableData的資料後記得要呼叫

[self.mTableView reloadData];
讓table view reload 顯示最新的資料內容, for example:

//假設執行一個向server 進行http request的function,並且傳回一個array [ServerApiCaller callApiWithSuccess:^(NSArray *result) { tableData = result; [self.mTableView reloadData]; } failure:^(NSError *error, id result) { }];


至此已經完成可以依據server回傳資料改變內容的TableView

Part 2, 加上section header



一、在storyboard中另外加入:

1.加入另外一個table view cell並且設置好想要的section header ui,如下顯示日期黑色那塊.


2.和先前一樣在attribute inspector中設定table view cell 的 identifier 和其中元件的tag


二、在implement file(.m)中加入header 相關的delegate method:

1.設定section header的高度:

-(CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section { return 106; }

2.設定section header的內容:

這裡的tabelData變數與part 1中並不相同,
使用section header時的table data有多種實作方式,這邊採用一個section的array,一個array cell包含了一個section object,其中又包含了這個section中的cell的方式


-(UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section { NSString * const headerID = @"announceTableHeader"; //同一般的cell,依據id取得section header的layout UIView * headerView = [tableView dequeueReusableCellWithIdentifier:headerID]; if(headerView == nil) { headerView = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:headerID]; } //設定layout內的ui內容 //注意:這邊的tableData跟YourDataType和Part 1中的並不相同,一個array cell包含了一個section object,其中又包含了這個section中的cell YourDataType * data = [tableData objectAtIndex:section]; UILabel *weekDay = (UILabel*)[headerView viewWithTag:1]; [weekDay setText:[data getWeekDay]]; UILabel *monthDay = (UILabel*)[headerView viewWithTag:2]; [monthDay setText:[data getDay]]; UILabel *year = (UILabel*)[headerView viewWithTag:3]; [year setText:[data getYear]]; //設定上圖中的"th"位置 UILabel *th = (UILabel*)[headerView viewWithTag:4]; CGSize textSize = [[monthDay text] sizeWithFont:[monthDay font]]; CGFloat strikeWidth = textSize.width; [th setFrame:CGRectMake(monthDay.frame.origin.x + strikeWidth, monthDay.frame.origin.x, monthDay.frame.size.width, monthDay.frame.size.height)]; return headerView; }
part 1 中所實作的delegate method也要有所修改:
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { //getListSize拿到這個section下的data cell數量 return [[tableData objectAtIndex:section] getListSize]; }

設定每個cell 內容的方法也要更改成:
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { static NSString *simpleTableIdentifier = @"announceTableCell"; UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier]; if (cell == nil) { cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:simpleTableIdentifier]; } //先從tableData中用indexPath.section拿到section YourDataType *data = [tableData objectAtIndex:indexPath.section]; //再從section中用indexPath.row拿到cell UILabel *titleLabel = (UILabel*)[cell viewWithTag:1]; [titleLabel setText:[data getTitleWithIndex:indexPath.row]]; UILabel *contentLable = (UILabel*)[cell viewWithTag:2]; [contentLable setText:[data getDescriptionWithIndex:indexPath.row]]; return cell; }









2013年8月2日 星期五

$nice man true love

在linux下 $ nice man true love No manual entry for love. 蠻有趣的XD nice: run a program with modified scheduling priority man: format and display the on-line manual pages ture: do nothing, successfully

2013年7月13日 星期六

Android save file to external storage

在External storage下開一個app專屬資料夾並且儲存檔案:

首先建立資料夾

final String writeDir= Environment.getExternalStorageDirectory()+ "/YourAppName/log/"; File dir = new File(writeDir); dir.mkdirs();
case1. 寫入文字檔案
mFileWriter = new FileWriter(writeDir+ "/logFile.txt", true); mFileWriter.write("yayahihihello"); mFileWriter.flush(); mFileWriter.close();

case2. 使用FileOutputStream儲存圖檔


//這個路徑可以被內建的gallery app 找到 final String writePath = Environment.getExternalStorageDirectory()+"/Pictures/YourAPPName"; Bitmap bitmap = (拿到你要存的bitmap); //將Bitmap轉成bytes才能儲存 ByteArrayOutputStream bytes = new ByteArrayOutputStream(); bitmap.compress(Bitmap.CompressFormat.JPEG, 100, bytes); //使用FileOutputStream儲存bytes FileOutputStream fo = new FileOutputStream(f); fo.write(bytes.toByteArray()); bytes.close(); fo.close();

接著最後儲存的檔案編號繼續下去


String fileName,fileNameBase = "CanvasNetPic" ; int counter =1; fileName = "CanvasNetPic0"; File f = new File(savePath+"/"+fileName+".jpg"); while(f.exists()) { fileName = fileNameBase + Integer.toString(counter); f = new File(savePath+"/"+fileName+".jpg"); counter++; }



通知Android media scanner file system被改變過, 這樣新儲存的檔案才會馬上被gallery或其他content reader讀到


sendBroadcast(new Intent(Intent.ACTION_MEDIA_MOUNTED, Uri.parse("file://"+ Environment.getExternalStorageDirectory())));

但這樣子media scanner會去重新掃描整個ExternalStroage, 或是檔案很多或是device比較慢的話要掃很久, 會導致打開gallery以後要放著一段時間新儲存的檔案才會出來

只掃描儲存檔案的資料夾可以大幅減掃掃描時間:

sendBroadcast(new Intent(Intent.ACTION_MEDIA_MOUNTED, Uri.parse("file://"+ "你的儲存路徑")));

2013年7月6日 星期六

Android http post (file) + responsed json handling

以下code包含:建立一個連線, post file or value, 處理回傳的json 首先是http request post的部分: //設定連線timeout HttpParams httpParameters = new BasicHttpParams(); int timeoutConnection = 5000; HttpConnectionParams.setConnectionTimeout(httpParameters, timeoutConnection); //設定等待socket回傳timeout int timeoutSocket = 8000; HttpConnectionParams.setSoTimeout(httpParameters, timeoutSocket); HttpClient httpclient = new DefaultHttpClient(httpParameters); httpclient.getParams().setParameter(CoreProtocolPNames.PROTOCOL_VERSION, HttpVersion.HTTP_1_1); //建立post request HttpPost httppost = new HttpPost("http://xxxxx.php"); //要傳送的檔案 File file = new File(filePath); //android post傳送檔案內建好像只提供自己建立http header然後再手動在content中包入檔案的方式,頗麻煩,這邊使用apache的library來加入檔案. MultipartEntity mpEntity = new MultipartEntity(); ContentBody cbFile = new FileBody(file); mpEntity.addPart("File", cbFile); httppost.setEntity(mpEntity); //執行 HttpResponse response = httpclient.execute(httppost); 如果沒有要傳送檔案的話可以這樣: List nameValuePairs = new ArrayList(2); nameValuePairs.add(new BasicNameValuePair("id", "12345")); nameValuePairs.add(new BasicNameValuePair("stringdata", "AndDev is Cool!")); httppost.setEntity(new UrlEncodedFormEntity(nameValuePairs)); 接著拿到response以後做json處理. BasicResponseHandler handler = new BasicResponseHandler(); String responseString = handler.handleResponse(response); //將回應的string parse成json 物件 JSONObject json = new JSONObject(responseString); //取值範例 if(json.has("Status")) { String status = json.getString("Status"); }

2013年7月1日 星期一

Ios animation sample (CABasicAnimation)

To start animation:

ImageView wave1,wave2 //basic sample CABasicAnimation *wave1ScaleAnimation = [CABasicAnimation animationWithKeyPath:@"transform.scale"]; wave1ScaleAnimation.toValue = [NSNumber numberWithFloat:1.5]; wave1ScaleAnimation.duration = 1; //infinity loop wave1ScaleAnimation.repeatCount = HUGE_VALF; //totally 2 second per cycle wave1ScaleAnimation.autoreverses = YES; wave1ScaleAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]; [wave1.layer addAnimation:wave1ScaleAnimation forKey:@"RecordingAnimation"]; //group animation sample CABasicAnimation *wave2ScaleAnimation = [CABasicAnimation animationWithKeyPath:@"transform.scale"]; wave2ScaleAnimation.toValue = [NSNumber numberWithFloat:2.1]; CABasicAnimation *alphaAnimation = [CABasicAnimation animationWithKeyPath:@"opacity"]; alphaAnimation.toValue = [NSNumber numberWithFloat:0.0]; //begin time and duration relative to group alphaAnimation.beginTime = 0.5; alphaAnimation.duration =0.5; CAAnimationGroup *group = [CAAnimationGroup animation]; //use CACurrentMediaTime() to get absolute begin Time group.beginTime = CACurrentMediaTime()+ 1; group.duration = 1; group.repeatCount = HUGE_VALF; group.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]; group.animations = [NSArray arrayWithObjects:wave2ScaleAnimation, alphaAnimation,nil]; [wave2.layer addAnimation:group forKey:@"groupAnimation"];

To stop animation:

[wave1.layer removeAllAnimations]; [wave2.layer removeAnimationForKey:@"groupAnimation"];

[CABasicAnimation animationWithKeyPath:@"transform.scale"]中的keyPath如下表:


另一種較舊的animation用法:

[UIView animateWithDuration:0.5 delay:1.0 options: UIViewAnimationCurveEaseOut animations:^{ self.basketTop.frame = basketTopFrame; self.basketBottom.frame = basketBottomFrame; } completion:^(BOOL finished){ NSLog(@"Done!"); }]; }

順便附上很方便的alpha transition animation:

[UIView transitionWithView:myImageView duration:0.25f options:UIViewAnimationOptionTransitionCrossDissolve animations:^{ [rotateView setImage:[UIImage imageNamed:@"record_rotate_red.png"]]; } completion:nil];

2013年6月21日 星期五

Android NDK 使用C++與 STL(Standard Template Library)

最近有一份使用STL的c++ code要port到android上面, 最後決定採用NDK而不改寫成java

一點點筆記:

就算code都是用c style寫的, 但只要用了STL之類的c++ library就要視為c++處理, 以下是編譯步驟.

1. 和往常一樣在project之中建立jni資料夾, 關鍵是資料夾中的c++檔案要以.cpp附檔名,

如此一來使用ndk-build script的時候就會自動選用c++的compiler來編譯. 不然build的時候會先遇到找不到header的error. 因為c++ compiler和c compiler 的搜尋header路徑不同.

2. 建立Android.mk在jni資料夾中, 內容大概像這樣

LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := temp_jni LOCAL_SRC_FILES := test.cpp LOCAL_LDLIBS := -llog include $(BUILD_SHARED_LIBRARY)


include $(CLEAR_VARS) -> 開始一段全新乾淨的編譯

LOCAL_MODULE := temp_jni -> 指定編出來的library名稱, 這樣會編出 libtemp_jni.so

LOCAL_SRC_FILES := test.cpp -> 當然就是要編譯的source file了

LOCAL_LDLIBS := -llog -> 在這邊我們多link一個log的library之後debug用,不是必須但是很方便,等等介紹怎麼用.

include $(BUILD_SHARED_LIBRARY) -> 最後呼叫Android負責編譯shared library的script開始編譯

3(Optional). 使用liblog.so的print log debug 功能: (即上述LOCAL_LDLIBS := -llog)

在.cpp source file內加入以下內容


#define LOG_TAG "TEST_JNI" #define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__) #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)


之後只要在c++ Code中使用 LOGE("hello world");就可以像在android java program 中使用Log.e(LOG_TAG,"hello world")一樣印log在logcat中! 同理LOGI,LOGV等.


4.在jni資料夾下建立一個Application.mk檔案, 內容如下面一行

APP_STL := stlport_static

目的是這樣的, 因為Android NDK預設只會link一份最簡陋的STL, 但其實NDK中有還是有放一份完整的STL, 所以要告訴編譯器編譯時去使用這份完整的STL.

附帶一提APP_STL的值有以下四種:
system -->預設,簡陋
stlport_static
stlport_shared
gnustl_static

5. 寫一個使用STL的 .cpp file吧!
像這樣

#include <vector> #include <jni.h> #include <android/log.h> //見第3點 #define LOG_TAG "FEATRUEEXTRACTION" #define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__) #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__) using namespace std; void runTest(const char* in) { vector<const char*> tt; tt.push_back("hello"); tt.push_back("world"); tt.push_back(in); LOGE(tt[2]);//會在logcat中印出從java端呼叫JNITestFunction時所傳入的String } extern "C"{ //注意 void Java_com_example_temp_MainActivity_JNITestFunction( JNIEnv* env, jobject thiz , jstring inString) { //should be released const char *in = (env)->GetStringUTFChars(inString,0); runTest(in); } } //注意


有兩點要特別注意:

一,一定要用extren "C"{}包住jni function,不然runtime會找不到這個jni function.

二,使用C++的時候jni.h的function會被一層wrapper包住,所以呼叫的方法和使用c的時候不一樣,

如上面使用GetStringUTFChars function 在c的版本會是這樣使用:
(*env)->GetStringUTFChars(env,inString,0); 

細節只要去NDK的platforms/android-(隨便一個版本)/arch-arm/usr/included/jni.h中看就瞭解.

6.java的部分大概長這樣
package com.example.temp; import android.os.Bundle; import android.app.Activity; import android.view.Menu; public class MainActivity extends Activity { public static native void JNITestFunction(String inString); static { System.loadLibrary("temp_jni"); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); JNITestFunction("cyy"); } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.activity_main, menu); return true; } }
7.切換目錄到jni/下面使用 <NDKROOT>/ndk-build 指令.

這樣會編出shared library. 這些編出來的library之後會被包進.apk中一併燒進手機中

8. 在Eclipse中像平常一樣執行這個app, 搞定收工!



2013年6月18日 星期二

ios project中使用STL(Standard Template Library)

ios中使用c: 直接用

ios中使用c++,STL:

因為STL是c++的library所以直接編譯含有STL的code會發生找不到header之類的問題.

古早一點版本的x code似乎需要手動去設定編譯使使用c++的compiler.

但現在只要將reference到 c++ code的檔案副檔名從.m改成.mm, 則編譯的時候會自動使用c++ compiler來處理,並且STL header等c++的header也會被加入搜尋路徑, 相當方便!

舉個例子, 我有foo.h foo.cpp main.m,

則直接在main.m中 import foo.h並且把檔名改成main.mm就搞定了!




2013年6月15日 星期六

Android app的status bar高度

status bar就是android手機最上面那一條有顯示電量與時間的bar,

為了寫一個可以在不同size螢幕上面都能按照同比例顯示view的layout需要得到status bar的高度來幫助計算.

一開始google都是找到類似如下的答案

Rect rectgle= new Rect();
Window window= getWindow();
window.getDecorView().getWindowVisibleDisplayFrame(rectgle);
int StatusBarHeight= rectgle.top;
int contentViewTop= 
    window.findViewById(Window.ID_ANDROID_CONTENT).getTop();
int TitleBarHeight= contentViewTop - StatusBarHeight;

   Log.i("*** Jorgesys :: ", "StatusBar Height= " + StatusBarHeight + " , TitleBar Height = " + TitleBarHeight);
( from http://stackoverflow.com/questions/3355367/height-of-statusbar/3356263#3356263)

雖然這樣可以拿到我要的status bar高度, 但是有一個大缺點, 就是這個方法要等到整個畫面被畫出來以後才根據畫出來的結果來計算status bar高度.

偏偏android的life cycle中, 畫面被畫出來是在onCreate(), onResume(), onRestart()等步驟之後,

所以在這些override function內是不能拿到正確的status bar heigth的!!

很不方便.

直到最近偶然發現了更好的方法:

public int getStatusBarHeight() { int result = 0; int resourceId = getResources().getIdentifier("status_bar_height", "dimen", "android"); if (resourceId > 0) { result = getResources().getDimensionPixelSize(resourceId); } return result; }

from http://mrtn.me/blog/2012/03/17/get-the-height-of-the-status-bar-in-android/

真是太猛了這樣一來就可以在onCreate()等處得到status bar的高度,

Comments中還有提到

<sdk-root>/platforms/android-<version>/data/res/values/dimens.xml

內還有一堆參數也都拿的到啊!!!



2013年5月23日 星期四

Android AsyncTask 寫成獨立class 並且不讓程式邏輯跳來跳去

用這樣的寫法可以將開始AsyncTask 和AsyncTask結束後要如何更新ui的code寫在一起.

避免程式破碎.



strategy pattern:

因為java沒有function pointer, 這裡就把strategy pattern當作function pointer來用




首先建立一個interface之後做call back用:


public interface MyCallBack {
public void onTaskComplete(Object p);
}


建立一個class 繼承AsyncTask


public class DownloadTask extends AsyncTask<Void, Void, Void> {

    MyCallBack callBack;
    ReturnObject ret;   //耗時工作的回傳值    
    public DownloadPackageListTask(MyCallBack pCallBack) {
    callBack = pCallBack;
}
    
@Override
protected Void doInBackground(Void... params) {

ret = (網路傳輸之類的function,最後得到回傳值)

return null;
}


@Override
protected void onPostExecute(Void result) {
super.onPostExecute(result);
callBack.onTaskComplete(ret); 
}
}


有了以上一個interface跟一個asynctask的subcalss後,我們就可以在main thread 中這樣使用:


final DownloadTask task = new DownloadTask(
new MyCallBack() { //藉由MyCallBack interface建立一個 anonymous 的strategy class

@Override
public void onTaskComplete(Object p) {
ReturnObject ro = (ReuturnObject)p;

(可以將得到return object後的ui code直接寫在這裡, 這個block內的東西將會在asynctask 的 onPostExecute中執行)

});

task.execute();



如此就可以直接把開始執行耗時工作和執行完耗時工作要做的事情寫在一起了!

2013年5月15日 星期三

Android 恐怖的Out of memory exception 之:死不了的Activity


當初遇到這個問題debug超久直到看完了以下google io影片後才有眉目,

http://www.youtube.com/watch?v=_CruQY55HOk

此篇為此影片的subset, 有閒的推薦直接看影片 (以下圖片來源為影片截圖)


故事是從這段外表看似簡單,leak卻過於常人的code開始的


狀況敘述:

Android 的Garbage collector只有在物件沒有被任何別的物件reference到的時候才回將這個物件回收.

當上圖這個Activity離開的時候理當被Garbege collector回收,但是因為leak物件依然reference這個Activity,所以此Activity不會被回收,

若同時這個Activity reference到一些Bitmap的話, 則這些Bitmap也不會被回收掉!!

導致多start幾次這個activity馬上就會噴out of memory.



狀況詳解:
問題的關鍵在於1.Leaky為一個inner class, 且2.leak為static object.

這兩件事情一同造成了MainActivity不會被garbage collect:

1. java non-static inner class 在實例化的時候固定會有一個reference指向其enclose class(包住這個inner class的class)

這個特性是為了能在inner class中使用其enclose class中的資源,例如
public class MainActivity extends Activity
{
 class Leaky
 {
     void doSomething()
  {
    MainActivity.this.someFunction();
  }
 }
}

這就是為什麼上圖中leaky物件指向MainActivity.

2. java class中的static variable 會在java class loader load class 後儲存在heap中一段稱為Permanent Genration的特如區域中. 而這區域在process結束之前都不會被回收.

因為leak物件為static的關係所以

leak = new Leaky() 這行程式碼建立了上圖中MainActivity class 到leaky的連結.

結果因為leak不會被回收導致被leak reference到的MainActivity就算離開了也還是存在著一條reference而不會被回收.

同樣的情況不只會發生在inner class, anonymous class一樣會產生一條對enclose class的連結,所以也可能會導致一樣的狀況


一般使用static object是不會出問題的, 但是要特別注意這個static object是否為一個inner class或anonymous class.

很多的memory leak都跟static脫不了關西, static與singleton雖然好用,但使用上要特別小心.



2013年5月13日 星期一

Android智障的oom(out of memory)原因: 圖片讀進來超大!? 原來是放錯資料夾

這問題雖然低能, 但我相信一定很多人不知道, 卻又深受其苦.

Android 在load image的時候會自動做scale的動作, 舉個例子

如果我把圖片放在 drawable-ldpi或drawable中, 卻使用被歸類於hdpi的手機來讀這張圖片,

則在圖片decode的時候會順便再多做scale的動作.

像我這種懶人開發的時候通常只會針對一種resolution生一組圖片然後把它隨便找一個資料夾放, 

例如開一個drawable資料夾然後全都放裡面, 跑起來有看到圖就以為沒差. 

然後每次oom都在想android是搞毛啊沒幾張圖就亂噴.

其實是當代的手機幾乎都是hdpi以上,所以如果在drawable-ldpi中放大resolution的圖片,

則在圖片decode的時候會被沒有必要的scale到超級大. 所以memory很容易就爆了.

懶人解法是將圖片全部放到drawable-xdpi下面, 就能保證只會scale down了.



在網路上survey了很久, 有一堆解法都是自己在loading bitmap的時候手動scale, 不然就是自己做memory 控管寫了一堆扣.  結果搞了半天才發現是放對資料夾就沒事了.

話說雖然在android sdk document中似乎有提到會自動scale, 但誰會一個字一個字看完document在開始寫啊= =+

  

以上解法雖然輕鬆愉快, 但依然會有memory的浪費, 因為放進project的圖片不可能完全適合每一款手機的螢幕手機,

android document 中有提到如何在load bitmap的時候針對實際需求去scale


其中的 Loading Large Bitmaps Efficiently 段落就是在說這個.
流程大概是先讀出image的大小 -> 再根據實際需要大小算出比例 -> 在實際decode bitmap的時候做scale.  雖然不難但是有點煩.

我個人只覺得只要能避免掉將high resolution image 放到 較低的資料夾所導致的多餘scale
對基本app來說已經很足夠了 ^.<

2013年5月12日 星期日

Start

本來不覺得有什麼寫blog的需求, 打電動都來不及了.

但最近對自己殘弱的記憶力已經到達忍無可忍的程度了QQ

每次要寫麼function明明不久前才剛用過,卻要整個重新survey一遍,

更恐怖的是花的時間還不下之前, 簡直坑爹

下定決心把每次辛苦compile過的survey結果紀錄下來!