分類
Java

List 速度比較紀錄

以下是參考 think in java 實作 ArrayList v.s LinkedList 速度比較的紀錄。
size 指的是 element 的數量,每一個比較項目下方是速度單位,越少代表越快,element 的型態是 Integer。

----------------------------------------------   LinkedList   ----------------------------------------------
    size             add             get             set     iter.add(x)        add(x,y)          remove
      10             164              27              26              18             369             445
     100              12              39              41              15             285              20
    1000              15             281             283               9              57              18
   10000              14            3947            3825               8              14              15
-----------------------------------------------   ArrayList   -----------------------------------------------
    size             add             get             set     iter.add(x)        add(x,y)          remove
      10             176              13              14              28             440             165
     100               6              11              12              21             398              23
    1000               7              11              12              66             400              80
   10000               9              12              14             576            1864             689

大部分的其他比較並沒有考慮到 size 的因素,基本上都是說

若常用 add, get, set 就選擇 ArrayList , 若常用 iter.add, add(x,y), remove 就選擇 LinkedList。

上面的比較表格把 size 加入考慮的因素,e.g. 若 size 為 10000,且常用 add 和 remove,那麼建議選擇 LinkedList。
若 size 為 10,也是常用 add 和 remove , 那麼選擇 ArrayList 會比較好。
 

分類
Ubuntu tools

Ubuntu 使用 pppoe 設定 hinet 固定 IP

由於更換新的固定ip, 這邊紀錄在 ubuntu12.04 使用 pppoe 設定 hinet 固定ip
過程相當簡單並沒有什麼太大的問題。

1.安裝 pppoeconfig

sudo apt-get install pppoeconf

2.照著畫面操作(基本上都是選Yes)

Note: 注意在 “輸入使用者名稱” 畫面
固定ip為 xxxxxxxx@ip.hinet.net
非固定ip為 xxxxxxxx@hinet.net

3.完成之後重新啟動電腦,/etc/network/interfaces 的內容應該會類似 e.g.

auto lo
iface lo inet loopback
auto dsl-provider
iface dsl-provider inet ppp
pre-up /sbin/ifconfig eth1 up # line maintained by pppoeconf
provider dsl-provider
auto eth1
iface eth1 inet manual

4.使用 ifconfig 應該可以看到多出 ppp0 裝置 e.g.

ppp0      Link encap:Point-to-Point Protocol
          inet addr:xxx.xxx.xxx.xxx  P-t-P:168.95.98.254  Mask:255.255.255.255
          UP POINTOPOINT RUNNING NOARP MULTICAST  MTU:1492  Metric:1
          RX packets:27060 errors:0 dropped:0 overruns:0 frame:0
          TX packets:21000 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:3
          RX bytes:24596503 (24.5 MB)  TX bytes:3591481 (3.5 MB)

 
 

分類
wordpress

WordPress 更換 IP address 步驟

因為升級中華電信100M並申請新的固定ip,因此想更改wordpress的舊ip讓它使用新的ip。

以下紀錄設定成功的步驟。

 

1.開啟終端機

ctrl + alt + t

 

2.使用 root 權限開啟mysql

mysql -u root -p

 

3.選擇 wordpress 使用的資料庫

use wordpress;

Note:如果你是照著官方教學安裝,那麼資料庫名稱就是 wordpress, 如果不是的話可以到 /var/www/wp-config.php 查看
 

4.更換新的ip取代舊的ip

首先 wordpress 的資料庫會有一堆資料表,可以使用以下指令顯示所有的資料表。

show tables;

就會列出wordpress資料庫的所有資料表

+-------------------------------+
| Tables_in_wordpress           |
+-------------------------------+
| wp_commentmeta                |
| wp_comments                   |
| wp_crumina_slider             |
| wp_layerslider                |
| wp_links                      |
| wp_options                    |
| wp_postmeta                   |
| wp_posts                      |
| wp_revslider_css              |
| wp_revslider_layer_animations |
| wp_revslider_settings         |
| wp_revslider_sliders          |
| wp_revslider_slides           |
| wp_revslider_static_slides    |
| wp_term_relationships         |
| wp_term_taxonomy              |
| wp_terms                      |
| wp_usermeta                   |
| wp_users                      |
| wp_wfBadLeechers              |
| wp_wfBlocks                   |
| wp_wfBlocksAdv                |
| wp_wfConfig                   |
| wp_wfCrawlers                 |
| wp_wfFileMods                 |
| wp_wfHits                     |
| wp_wfHoover                   |
| wp_wfIssues                   |
| wp_wfLeechers                 |
| wp_wfLockedOut                |
| wp_wfLocs                     |
| wp_wfLogins                   |
| wp_wfNet404s                  |
| wp_wfReverseCache             |
| wp_wfScanners                 |
| wp_wfStatus                   |
| wp_wfThrottleLog              |
| wp_wfVulnScanners             |
+-------------------------------+
38 rows in set (0.00 sec)

我們的目標是 wp_options 資料表,使用以下指令來查看 wp_options資料表內容。

describe wp_options;

會顯示該資料表所有欄位

+--------------+---------------------+------+-----+---------+----------------+
| Field        | Type                | Null | Key | Default | Extra          |
+--------------+---------------------+------+-----+---------+----------------+
| option_id    | bigint(20) unsigned | NO   | PRI | NULL    | auto_increment |
| option_name  | varchar(64)         | NO   | UNI |         |                |
| option_value | longtext            | NO   |     | NULL    |                |
| autoload     | varchar(20)         | NO   |     | yes     |                |
+--------------+---------------------+------+-----+---------+----------------+
4 rows in set (0.00 sec)

可以看到該資料表有4個欄位。我們的目標是把 option_value 欄位的值以新的ip來取代舊ip
新ip(http://59.127.53.122/wordpress) 取代 舊ip(http://122.146.238.121/wordpress)
首先查詢 option_valule 為 舊ip 有哪些

Select * from wp_options where option_value = 'http://122.146.238.121/wordpress';

查詢結果如下

+-----------+-------------+----------------------------------+----------+
| option_id | option_name | option_value                     | autoload |
+-----------+-------------+----------------------------------+----------+
|         1 | siteurl     | http://122.146.238.121/wordpress |   yes    |
|         2 | home        | http://122.146.238.121/wordpress |   yes    |
+-----------+-------------+----------------------------------+----------+
2 rows in set (0.00 sec)

符合查詢結果有2筆。首先修改第1筆 option_name 為 siteurl,而 option_value 的值為 http://122.146.238.121/wordpress(舊ip)
使用以下指令來更換新 ip

update wp_options set option_value='http://59.127.53.122/wordpress' where option_name = 'siteurl';

接著修改第2筆 option_name 為 home,而 option_value的 值為 http://122.146.238.121/wordpress(舊ip)
使用以下指令來更換新 ip

update wp_options set option_value='http://59.127.53.122/wordpress' where option_name = 'home';

更換完成後輸入 exit 離開 mysql。
整個工作完成啦!!!wordpress 就可以使用新的ip了。
 

分類
Design Pattern

組合模式(Component Pattern)應用

組合模式定義:

將物件組合為樹狀結構,以表達 ”部份-整體” 的層次。組合模式讓使用者對單一物件或組合物件的存取具有統一性。
 

需求:

必須提供一個機制讓資料可以轉換成 xml ,xml格式並不複雜,但可能會出現多層嵌套的情況。e.g.
Example 1

        <result>
            <code>999</code>
            <format>
                <name>John</name>
                <id>0123345</id>
                <gender>Male</gender>
                <mobileNumber>0988767654</mobileNumber>
                <age>20</model>
            </format>
            <format>
                <name>Mary</name>
                <id>543210</id>
                <gender>Female</gender>
                <mobileNumber>0911721431</mobileNumber>
                <age>18</model>
            </format>
        </result>

Example 2

        <queryResult>success</queryResult>

Example 3

        <device>
            <id>000001</id>
            <id>000002</id>
        </device>

上面3種格式雖然看起來不同,但基本的xml組成格式是相同的。可以把1個 xml element 當作1個資料結構的單體,如下方的code

<code>999</code>

 
因此以上3種 xml 格式組成的 xml element 可以視為2種種類
第1種為 Leaf,代表本身沒有包含 child (xml element),如下面的code

<code>999</code>

第2種為 Composite,代表本身有包含其他 child (xml element),如下面的format。
format 包含了5個child(name, id, gender, mobileNumber, age),而這5個child 因為沒有再包含其他的child,因此他們都是Leaf。

            <format>
                <name>John</name>
                <id>0123345</id>
                <gender>Male</gender>
                <mobileNumber>0988767654</mobileNumber>
                <age>20</age>
            </format>

 
 

對應的資料結構

public class XmlInfo
{
    private String mTag;
    private String mValue;
    private List<XmlInfo> mChildren = null;
    public XmlInfo(String tag, String value) {
        mTag = tag;
        mValue = value;
    }
    public void addChild(XmlInfo child)
    {
        if (mChildren == null) {
            mChildren = new ArrayList<XmlInfo>();
        }
        mChildren.add(child);
    }
    public String getTag()
    {
        return mTag;
    }
    public String getValue()
    {
        return mValue;
    }
    public List<XmlInfo> getChildren()
    {
        return mChildren;
    }
}

重點就在XmlInfo,其中 mTag 對應 xml element 的 tag 內容,mValue 對應 xml element 的 value內容。
當該 xml element 是 Composite 時,mValue就填 null , 為 Leaf,mValue 就填相應數值。e.g.
1.Leaf 寫法:
queryResult 為 Leaf 。

<queryResult>success</queryResult>

建立對應資料寫法為

XmlInfo queryResult = new XmlInfo("queryResult", "success");

2.Composite 寫法:
format 為 Composite。

            <format>
                <name>John</name>
                <id>0123345</id>
                <gender>Male</gender>
                <mobileNumber>0988767654</mobileNumber>
                <age>20</age>
            </format>

建立對應資料寫法為

XmlInfo format = new XmlInfo("format",null);
format.addChild(new XmlInfo("name", "John"));
format.addChild(new XmlInfo("id", "012345"));
format.addChild(new XmlInfo("gender", "Male"));
format.addChild(new XmlInfo("mobileNumber", "0988767643"));
format.addChild(new XmlInfo("age","20"));

另外需要注意的是 XmlInfo 不完全是 Composite Pattern 的應用。
原始的 Composite Pattern 會有個父類別(Component),並讓其子類別代表不同的種類如,Leaf , Component。
所以起碼會有3個 class 一起運作。因為目前的需求不需要用到其他的類別,單一類別(XmlInfo)即可符合需求。


 
 

模擬建立資料結構的操作

Example 1

private XmlInfo makeExample1()
    {
        XmlInfo result = new XmlInfo("result" , null);
        XmlInfo resultCode = new XmlInfo("code", "999");
        result.addChild(resultCode);
        XmlInfo format1 = new XmlInfo("format", null);
        format1.addChild(new XmlInfo("name", "John"));
        format1.addChild(new XmlInfo("id", "012345"));
        format1.addChild(new XmlInfo("gender", "Male"));
        format1.addChild(new XmlInfo("mobileNumber", "0988767654"));
        format1.addChild(new XmlInfo("age", "20"));
        XmlInfo format2 = new XmlInfo("format", null);
        format2.addChild(new XmlInfo("name", "Mary"));
        format2.addChild(new XmlInfo("id", "543210"));
        format2.addChild(new XmlInfo("gender", "Female"));
        format2.addChild(new XmlInfo("mobileNumber", "0911721431"));
        format2.addChild(new XmlInfo("age", "18"));
        result.addChild(format1);
        result.addChild(format2);
        return result;
    }

Example 2

private XmlInfo makeExample2()
{
        XmlInfo queryResult = new XmlInfo("queryResult", "success");
        return queryResult;
}

Example 3

    private XmlInfo makeExample3()
    {
        XmlInfo device = new XmlInfo("device", null);
        XmlInfo id = new XmlInfo("id", "000001");
        XmlInfo id2 = new XmlInfo("id", "000002");
        device.addChild(id);
        device.addChild(id2);
        return device;
    }

 
 

轉換資料為 xml

private String parseXmlData(XmlInfo xmlData)
    {
        result.append( "<"+xmlData.getTag()+">");
        if(xmlData.getChildren() != null){
            for (int i = 0; i < xmlData.getChildren().size(); ++i) {
                parseXmlData(xmlData.getChildren().get(i));
            }
            result.append( "<"+"/"+xmlData.getTag()+">");
        }else{
            result.append(xmlData.getValue());
            result.append( "<"+"/"+xmlData.getTag()+">");
        }
        return result.toString();
    }

 
 

Test Case

Example 1

@Test
    public void testExample1()
    {
        String target = "<result>"
                + "<code>999</code>"
                + "<format>"
                + "<name>John</name>"
                + "<id>012345</id>"
                + "<gender>Male</gender>"
                + "<mobileNumber>0988767654</mobileNumber>"
                + "<age>20</age>"
                + "</format>"
                + "<format>"
                + "<name>Mary</name>"
                + "<id>543210</id>"
                + "<gender>Female</gender>"
                + "<mobileNumber>0911721431</mobileNumber>"
                + "<age>18</age>"
                + "</format>"
                + "</result>";
        assertEquals(target, parseXmlData(makeExample1()));
    }

Example 2

@Test
    public void testExample2()
    {
        String target = "<queryResult>success</queryResult>";
        assertEquals(target, parseXmlData(makeExample2()));
    }

Example 3

@Test
    public void testExample3()
    {
        String target = "<device><id>000001</id><id>000002</id></device>";
        assertEquals(target, parseXmlData(makeExample3()));
    }

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

分類
agile development

敏捷軟體開發宣言與原則

敏捷軟體開發為輕量級,具有回饋,可以針對需求變化而快速反應修改的一種開發方法。
其核心思想及價值觀都可由下列敏捷開發宣言和原則所包括。

敏捷軟體開發宣言

1.個人互動勝於流程工具
必須具有良好的合作,溝通互動。
合適的工具(編譯器,整合環境,源碼控制系統)相當重要,可以從小工具開始著手。
2.可用軟體勝於詳盡文件
撰寫並維護設計原理以及系統架構的文件,但文件必須簡短扼要,最多不要超過10~20頁。
透過程式碼以及團隊將資訊傳遞給開發成員。
3.客戶合作勝於合約談判
因應快速,輕量化的開發方式以及需求的變動,客戶需給予頻繁的回饋。
4.回應變化勝於墨守計畫
對未來2周作詳盡的計畫,對未來3個月作非常粗略的規劃,在之後有個模糊的概念即可。

 

敏捷軟體開發原則(由敏捷開發宣言所引出,與一般重量型開發方法不同之處)

1.早期且頻繁的交付。
2.交付的週期以短期為主,範圍從數週到數個月。
3.歡迎需求改變。
4.可以修改其他因素(環境,流程)以符合團隊。
5.客戶需有大量回饋。
6.最佳的溝通方式為面對面。
7.測量進度的方式為可用的軟體。
8.保持簡單(只做目前需要的基礎設施)。
9.保持乾淨(時時重構,絕不弄亂程式碼)。
10.穩定的開發步調。
11.共同分攤責任(每個成員都可以開發專案的任何部份)。
12.持續修改行為。

 
 

分類
Uncategorized

Refactoring for LogoFlow.java

LogoFlow 是極簡黑記帳本(SimpleBlackNote app)的起始 Activity,當初寫的時候並沒有 refactoring 以及 readable code 的概念。
剛好可以拿來練習重構。
以下為原始程式碼,可以看到其中充滿許多 bad smell。e.g. large class(過大的類別) ,duplicate code(重複的程式碼),long method(過長的函式) ,
commit(過多的註釋),大型的if-else條件式,不適當的封裝等等 。

package com.foxx.main;
import java.util.Random;
import android.animation.Animator;
import android.animation.Animator.AnimatorListener;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.MotionEvent;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.view.animation.ScaleAnimation;
import android.widget.ImageView;
import android.widget.LinearLayout;
import com.google.ads.AdRequest;
import com.google.ads.AdSize;
import com.google.ads.AdView;
import com.tlyerfoxx.sbcnote.R;
@SuppressLint("NewApi")
public class LogoFlow extends Activity implements Runnable{
	private ImageView mLogo;
	private Handler mHandler;
	private float mLogoActIndex;
	private boolean mIsSleeping;
	private ValueAnimator mScaleX;
	private ValueAnimator mScaleY;
	private ValueAnimator mRotationY;
	private ValueAnimator mRotationX;
	private AnimatorSet mScalexy;
	public void onCreate(Bundle savedInstanceState){
		super.onCreate(savedInstanceState);
		setContentView(R.layout.logoflow);
//		showAdmob();
		mLogo = (ImageView)findViewById(R.id.imageView1);
//		useThreadFadeIn();
		Random r = new Random();
		useAnimfadein2(r.nextInt(4));
	}
	public void run() {
		// TODO Auto-generated method stub
		while(!mIsSleeping){
			try {
				Thread.sleep(30);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			Message m = new Message();
            mHandler.sendMessage(m);
		}
	}
	private void useThreadFadeIn(){
		mLogo.setAlpha(0.0f);
		mHandler = new Handler(){
			public void handleMessage(Message msg) {
				Log.v("Trace Log", "revice maessage");
				if(mLogo.getAlpha()<1.0f){
					mLogoActIndex+=0.01f;
					mLogo.setAlpha(mLogoActIndex);
					Log.v("Trace Log", "test2");
				}
				else if(mLogo.getAlpha()>=1.0f){
					LogoFlow.this.finish(); // close activity
					Log.v("Trace Log", "test3");
					mIsSleeping = true;// stop Thread
					Intent goAct = new Intent();// new a Intent
					goAct.setClass(LogoFlow.this, Table_Flow.class); // set another activity
					startActivity(goAct); // start another Activity
					System.exit(0);// stop program
				}
				Log.v("Trace Log", ""+mLogo.getAlpha());
                super.handleMessage(msg);
            }
		};
		Thread th = new Thread(this);
		th.start();
	}
	void useAnimfadein(){
		ValueAnimator fadein = ObjectAnimator.ofFloat(mLogo, "alpha", 0f, 1f);
        fadein.setDuration(2000);
        ValueAnimator standby = ObjectAnimator.ofFloat(mLogo, "alpha", 1f, 1f);
        standby.setDuration(1000);
        ValueAnimator fadeout = ObjectAnimator.ofFloat(mLogo, "alpha", 1f, 0f);
        fadeout.setDuration(2000);
        AnimatorSet bouncer = new AnimatorSet();
        bouncer.playSequentially(fadein,standby,fadeout);
        bouncer.start();
        bouncer.addListener(new AnimatorListener() {
			@Override
			public void onAnimationStart(Animator animation) {
				// TODO Auto-generated method stub
			}
			@Override
			public void onAnimationRepeat(Animator animation) {
				// TODO Auto-generated method stub
			}
			@Override
			public void onAnimationEnd(Animator animation) {
				// TODO Auto-generated method stub
				LogoFlow.this.finish(); // close activity
				mIsSleeping = true;// stop Thread
				Intent goAct = new Intent();// new a Intent
				goAct.setClass(LogoFlow.this, CopyRightFlow.class); // set another activity
				startActivity(goAct); // start another Activity
				System.exit(0);// stop program
			}
			@Override
			public void onAnimationCancel(Animator animation) {
				// TODO Auto-generated method stub
			}
		});
	}
	void useAnimfadein2(int type){
		if(type==0){
			mScaleX = ObjectAnimator.ofFloat(mLogo, "scaleX", 0.1f, 1f);
			mScaleY = ObjectAnimator.ofFloat(mLogo, "scaleY", 0.1f, 1f);
			mScaleX.setDuration(2500);
			mScaleY.setDuration(1250);
			mRotationY = ObjectAnimator.ofFloat(mLogo, "rotationY", 0f, 360f);
			mRotationX = ObjectAnimator.ofFloat(mLogo, "rotationX", 0f, 360f);
			mRotationY.setDuration(2500);
	        mRotationX.setDuration(1250);
	        mScalexy = new AnimatorSet();
			mScalexy.playTogether(mScaleX,mScaleY,mRotationY,mRotationX);
		}
		else if(type==1){
			mScaleX = ObjectAnimator.ofFloat(mLogo, "scaleX", 2.0f, 1f);
			mScaleY = ObjectAnimator.ofFloat(mLogo, "scaleY", 2.0f, 1f);
			mScaleX.setDuration(2500);
			mScaleY.setDuration(1250);
			mRotationY = ObjectAnimator.ofFloat(mLogo, "rotationY", 0f, 360f);
			mRotationX = ObjectAnimator.ofFloat(mLogo, "rotationX", 0f, 360f);
			mRotationY.setDuration(2500);
	        mRotationX.setDuration(1250);
	        mScalexy = new AnimatorSet();
			mScalexy.playTogether(mScaleX,mScaleY,mRotationY,mRotationX);
		}
		else if(type==2){
			mScaleX = ObjectAnimator.ofFloat(mLogo, "scaleX", 1f, 1f);
			mScaleY = ObjectAnimator.ofFloat(mLogo, "scaleY", 1f, 1f);
			mScaleX.setDuration(2500);
			mScaleY.setDuration(1250);
			mRotationY = ObjectAnimator.ofFloat(mLogo, "rotationY", 0f, 720f);
			mRotationX = ObjectAnimator.ofFloat(mLogo, "rotationX", 0f, 0f);
			mRotationY.setDuration(2500);
	        mRotationX.setDuration(1250);
	        mScalexy = new AnimatorSet();
			mScalexy.playTogether(mScaleX,mScaleY,mRotationY,mRotationX);
		}
		else if(type==3){
			mScaleX = ObjectAnimator.ofFloat(mLogo, "scaleX", 1f, 1f);
			mScaleY = ObjectAnimator.ofFloat(mLogo, "scaleY", 1f, 1f);
			mScaleX.setDuration(2500);
			mScaleY.setDuration(1250);
			mRotationY = ObjectAnimator.ofFloat(mLogo, "rotationY", 0f, 0f);
			mRotationX = ObjectAnimator.ofFloat(mLogo, "rotationX", 0f, 720f);
			mRotationY.setDuration(2500);
	        mRotationX.setDuration(1250);
	        mScalexy = new AnimatorSet();
			mScalexy.playTogether(mScaleX,mScaleY,mRotationY,mRotationX);
		}
        ValueAnimator standby = ObjectAnimator.ofFloat(mLogo, "alpha", 1f, 1f);
        standby.setDuration(1000);
        ValueAnimator fadeout = ObjectAnimator.ofFloat(mLogo, "alpha", 1f, 0f);
        fadeout.setDuration(1000);
        AnimatorSet bouncer = new AnimatorSet();
        bouncer.playSequentially(mScalexy,standby,fadeout);
        bouncer.start();
        bouncer.addListener(new AnimatorListener() {
			@Override
			public void onAnimationStart(Animator animation) {
				// TODO Auto-generated method stub
			}
			@Override
			public void onAnimationRepeat(Animator animation) {
				// TODO Auto-generated method stub
			}
			@Override
			public void onAnimationEnd(Animator animation) {
				// TODO Auto-generated method stub
				LogoFlow.this.finish(); // close activity
				mIsSleeping = true;// stop Thread
				Intent goAct = new Intent();// new a Intent
				goAct.setClass(LogoFlow.this, Table_Flow.class); // set another activity CopyRightFlow.class
				startActivity(goAct); // start another Activity
				overridePendingTransition(R.anim.zoom_enter, R.anim.zoom_exit);//�ϥιL��,��1�ӰѼƬO�U1�ӳ������i�J,��2�ӰѼƬO�������������}
				System.exit(0);// stop program
			}
			@Override
			public void onAnimationCancel(Animator animation) {
				// TODO Auto-generated method stub
			}
		});
	}
	void showAdmob(){
		 AdView adView;
//   	  Create the adView
    	adView = new AdView(this, AdSize.BANNER, "a150189a3abeb1a");
    	LinearLayout layout = (LinearLayout)findViewById(R.id.AdLayout);
    	layout.addView(adView);
    	adView.loadAd(new AdRequest());
	}
	public boolean onTouchEvent(MotionEvent event){
//		FP.p("in onTouchEvent");
		LogoFlow.this.finish(); // close activity
		mIsSleeping = true;// stop Thread
		Intent goAct = new Intent();// new a Intent
		goAct.setClass(LogoFlow.this, Table_Flow.class); // set another activity CopyRightFlow.class
		startActivity(goAct); // start another Activity
		overridePendingTransition(R.anim.zoom_enter, R.anim.zoom_exit);//�ϥιL��,��1�ӰѼƬO�U1�ӳ������i�J,��2�ӰѼƬO�������������}
		System.exit(0);// stop program
		switch(event.getAction()){
		case MotionEvent.ACTION_DOWN:
//			dx = (int)event.getX();
			break;
		case MotionEvent.ACTION_UP:
			break;
		}
			return true;
	}
}

以下將一步一步進行重構。
首先從適當的封裝開始,觀察類別屬性以及函式是否為useless,或是可以縮小存取權限以避免讓外界知道不必要的細節。
可以發現第77行的useThreadFadeIn,第119行的useAnimfadein,第282的showAdmob 這3個函式都沒有使用,沒有使用函式就是多餘的函式。
直接刪除吧。
刪完沒有使用的函式之後,logoActIndex 變數也沒有使用,另外刪掉多餘的 import 以及註解。
最後為類別屬性加上適當的重新命名之後。整個LogoFlow類別會從324行縮短到 175行。e.g.

package com.foxx.main;
import java.util.Random;
import android.animation.Animator;
import android.animation.Animator.AnimatorListener;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.MotionEvent;
import android.widget.ImageView;
import com.tlyerfoxx.sbcnote.R;
@SuppressLint("NewApi")
public class LogoFlow extends Activity implements Runnable
{
    private ImageView mLogo;
    private Handler mHandler;
    private boolean mIsSleeping;
    private ValueAnimator mScaleX;
    private ValueAnimator mScaleY;
    private ValueAnimator mRotationY;
    private ValueAnimator mRotationX;
    private AnimatorSet mScalexy;
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.logoflow);
        mLogo = (ImageView) findViewById(R.id.imageView1);
        Random r = new Random();
        useAnimfadein2(r.nextInt(4));
    }
    public void run()
    {
        while (!mIsSleeping) {
            try {
                Thread.sleep(30);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            Message m = new Message();
            mHandler.sendMessage(m);
        }
    }
    void useAnimfadein2(int type)
    {
        if (type == 0) {
            mScaleX = ObjectAnimator.ofFloat(mLogo, "scaleX", 0.1f, 1f);
            mScaleY = ObjectAnimator.ofFloat(mLogo, "scaleY", 0.1f, 1f);
            mScaleX.setDuration(2500);
            mScaleY.setDuration(1250);
            mRotationY = ObjectAnimator.ofFloat(mLogo, "rotationY", 0f, 360f);
            mRotationX = ObjectAnimator.ofFloat(mLogo, "rotationX", 0f, 360f);
            mRotationY.setDuration(2500);
            mRotationX.setDuration(1250);
            mScalexy = new AnimatorSet();
            mScalexy.playTogether(mScaleX, mScaleY, mRotationY, mRotationX);
        } else if (type == 1) {
            mScaleX = ObjectAnimator.ofFloat(mLogo, "scaleX", 2.0f, 1f);
            mScaleY = ObjectAnimator.ofFloat(mLogo, "scaleY", 2.0f, 1f);
            mScaleX.setDuration(2500);
            mScaleY.setDuration(1250);
            mRotationY = ObjectAnimator.ofFloat(mLogo, "rotationY", 0f, 360f);
            mRotationX = ObjectAnimator.ofFloat(mLogo, "rotationX", 0f, 360f);
            mRotationY.setDuration(2500);
            mRotationX.setDuration(1250);
            mScalexy = new AnimatorSet();
            mScalexy.playTogether(mScaleX, mScaleY, mRotationY, mRotationX);
        } else if (type == 2) {
            mScaleX = ObjectAnimator.ofFloat(mLogo, "scaleX", 1f, 1f);
            mScaleY = ObjectAnimator.ofFloat(mLogo, "scaleY", 1f, 1f);
            mScaleX.setDuration(2500);
            mScaleY.setDuration(1250);
            mRotationY = ObjectAnimator.ofFloat(mLogo, "rotationY", 0f, 720f);
            mRotationX = ObjectAnimator.ofFloat(mLogo, "rotationX", 0f, 0f);
            mRotationY.setDuration(2500);
            mRotationX.setDuration(1250);
            mScalexy = new AnimatorSet();
            mScalexy.playTogether(mScaleX, mScaleY, mRotationY, mRotationX);
        } else if (type == 3) {
            mScaleX = ObjectAnimator.ofFloat(mLogo, "scaleX", 1f, 1f);
            mScaleY = ObjectAnimator.ofFloat(mLogo, "scaleY", 1f, 1f);
            mScaleX.setDuration(2500);
            mScaleY.setDuration(1250);
            mRotationY = ObjectAnimator.ofFloat(mLogo, "rotationY", 0f, 0f);
            mRotationX = ObjectAnimator.ofFloat(mLogo, "rotationX", 0f, 720f);
            mRotationY.setDuration(2500);
            mRotationX.setDuration(1250);
            mScalexy = new AnimatorSet();
            mScalexy.playTogether(mScaleX, mScaleY, mRotationY, mRotationX);
        }
        ValueAnimator standby = ObjectAnimator.ofFloat(mLogo, "alpha", 1f, 1f);
        standby.setDuration(1000);
        ValueAnimator fadeout = ObjectAnimator.ofFloat(mLogo, "alpha", 1f, 0f);
        fadeout.setDuration(1000);
        AnimatorSet bouncer = new AnimatorSet();
        bouncer.playSequentially(mScalexy, standby, fadeout);
        bouncer.start();
        bouncer.addListener(new AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animation)
            {
            }
            @Override
            public void onAnimationRepeat(Animator animation)
            {
            }
            @Override
            public void onAnimationEnd(Animator animation)
            {
                LogoFlow.this.finish();
                mIsSleeping = true;// stop Thread
                Intent goAct = new Intent();
                goAct.setClass(LogoFlow.this, Table_Flow.class);
                startActivity(goAct);
                overridePendingTransition(R.anim.zoom_enter, R.anim.zoom_exit);
                System.exit(0);// stop program
            }
            @Override
            public void onAnimationCancel(Animator animation)
            {
            }
        });
    }
    public boolean onTouchEvent(MotionEvent event)
    {
        LogoFlow.this.finish();
        mIsSleeping = true;// stop Thread
        Intent goAct = new Intent();
        goAct.setClass(LogoFlow.this, Table_Flow.class);
        startActivity(goAct);
        overridePendingTransition(R.anim.zoom_enter, R.anim.zoom_exit);
        System.exit(0);// stop program
        return true;
    }
}

接著我們將目標轉向第61行的 useAnimfadein2 函式,因為它實在太長了,過長的函式通常不只做一件事,包含太多職責,讓函式的意圖不明。
先從名稱開始,useAnimfadein2大概可以猜出跟動畫淡入有關,而2是個數字命名法,是一個不好的命名習慣,因為你沒辦法從2得到訊息。
基本上也沒有人知道誰使用動畫淡入在誰身上,因此我們只能從函式內容看起。
從第64 到 125行應該是屬於動畫的參數設定,從參數上可以看出是mLogo使用動畫。
而第127行開始執行動畫,第129到159則是設定動畫的監聽器。
因此我們可以把 useAnimfadein2 更名為 runLogoAnimations 並拆分為3個函式 , setAnimationParameters , createAnimationListener。e.g.

    private void runLogoAnimations(int animationType)
    {
        AnimatorSet animation = new AnimatorSet();
        animation.playSequentially(setAnimationParameters(animationType));
        animation.start();
        animation.addListener(createAnimationListener());
    }
    private List<Animator> setAnimationParameters(int type)
    {
        List<Animator> animators = new ArrayList<Animator>();
        if (type == 0) {
            mScaleX = ObjectAnimator.ofFloat(mLogo, "scaleX", 0.1f, 1f);
            mScaleY = ObjectAnimator.ofFloat(mLogo, "scaleY", 0.1f, 1f);
            mScaleX.setDuration(2500);
            mScaleY.setDuration(1250);
            mRotationY = ObjectAnimator.ofFloat(mLogo, "rotationY", 0f, 360f);
            mRotationX = ObjectAnimator.ofFloat(mLogo, "rotationX", 0f, 360f);
            mRotationY.setDuration(2500);
            mRotationX.setDuration(1250);
            mScalexy = new AnimatorSet();
            mScalexy.playTogether(mScaleX, mScaleY, mRotationY, mRotationX);
        } else if (type == 1) {
            mScaleX = ObjectAnimator.ofFloat(mLogo, "scaleX", 2.0f, 1f);
            mScaleY = ObjectAnimator.ofFloat(mLogo, "scaleY", 2.0f, 1f);
            mScaleX.setDuration(2500);
            mScaleY.setDuration(1250);
            mRotationY = ObjectAnimator.ofFloat(mLogo, "rotationY", 0f, 360f);
            mRotationX = ObjectAnimator.ofFloat(mLogo, "rotationX", 0f, 360f);
            mRotationY.setDuration(2500);
            mRotationX.setDuration(1250);
            mScalexy = new AnimatorSet();
            mScalexy.playTogether(mScaleX, mScaleY, mRotationY, mRotationX);
        } else if (type == 2) {
            mScaleX = ObjectAnimator.ofFloat(mLogo, "scaleX", 1f, 1f);
            mScaleY = ObjectAnimator.ofFloat(mLogo, "scaleY", 1f, 1f);
            mScaleX.setDuration(2500);
            mScaleY.setDuration(1250);
            mRotationY = ObjectAnimator.ofFloat(mLogo, "rotationY", 0f, 720f);
            mRotationX = ObjectAnimator.ofFloat(mLogo, "rotationX", 0f, 0f);
            mRotationY.setDuration(2500);
            mRotationX.setDuration(1250);
            mScalexy = new AnimatorSet();
            mScalexy.playTogether(mScaleX, mScaleY, mRotationY, mRotationX);
        } else if (type == 3) {
            mScaleX = ObjectAnimator.ofFloat(mLogo, "scaleX", 1f, 1f);
            mScaleY = ObjectAnimator.ofFloat(mLogo, "scaleY", 1f, 1f);
            mScaleX.setDuration(2500);
            mScaleY.setDuration(1250);
            mRotationY = ObjectAnimator.ofFloat(mLogo, "rotationY", 0f, 0f);
            mRotationX = ObjectAnimator.ofFloat(mLogo, "rotationX", 0f, 720f);
            mRotationY.setDuration(2500);
            mRotationX.setDuration(1250);
            mScalexy = new AnimatorSet();
            mScalexy.playTogether(mScaleX, mScaleY, mRotationY, mRotationX);
        }
        ValueAnimator standby = ObjectAnimator.ofFloat(mLogo, "alpha", 1f, 1f);
        standby.setDuration(1000);
        ValueAnimator fadeout = ObjectAnimator.ofFloat(mLogo, "alpha", 1f, 0f);
        fadeout.setDuration(1000);
        animators.add(mScalexy);
        animators.add(standby.setDuration(1000));
        animators.add(fadeout);
        return animators;
    }
    private AnimatorListener createAnimationListener()
    {
        return new AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animation)
            {
            }
            @Override
            public void onAnimationRepeat(Animator animation)
            {
            }
            @Override
            public void onAnimationEnd(Animator animation)
            {
                LogoFlow.this.finish();
                mIsSleeping = true;// stop Thread
                Intent goAct = new Intent();
                goAct.setClass(LogoFlow.this, Table_Flow.class);
                startActivity(goAct);
                overridePendingTransition(R.anim.zoom_enter, R.anim.zoom_exit);
                System.exit(0);// stop program
            }
            @Override
            public void onAnimationCancel(Animator animation)
            {
            }
        };
    }

接著我們再從 setAnimationParameters 來看看,是否有可以改進的地方。
首先 setAnimationParameters 有個旗標參數,函式會根據旗標參數執行不同的行為。這也代表該方法做了不只一件事。
觀察 mSaleX , mScaleY,mRotationX , mRotationY , mScaleXY , 可以看到這四個類別屬性僅僅使用在setAnimationParameters函式內。
而不需要在類別的函式之間使用。因此可以將他們改為暫時變數,為了縮短生命週期。

    private List<Animator> setAnimationParameters(int type)
    {
        ValueAnimator scaleX;
        ValueAnimator scaleY;
        ValueAnimator rotationY;
        ValueAnimator rotationX;
        AnimatorSet scaleXY = null;
        if (type == 0) {
            scaleX = ObjectAnimator.ofFloat(mLogo, "scaleX", 0.1f, 1f);
            scaleY = ObjectAnimator.ofFloat(mLogo, "scaleY", 0.1f, 1f);
            scaleX.setDuration(2500);
            scaleY.setDuration(1250);
            rotationY = ObjectAnimator.ofFloat(mLogo, "rotationY", 0f, 360f);
            rotationX = ObjectAnimator.ofFloat(mLogo, "rotationX", 0f, 360f);
            rotationY.setDuration(2500);
            rotationX.setDuration(1250);
            scaleXY = new AnimatorSet();
            scaleXY.playTogether(scaleX, scaleY, rotationY, rotationX);
        } else if (type == 1) {
            scaleX = ObjectAnimator.ofFloat(mLogo, "scaleX", 2.0f, 1f);
            scaleY = ObjectAnimator.ofFloat(mLogo, "scaleY", 2.0f, 1f);
            scaleX.setDuration(2500);
            scaleY.setDuration(1250);
            rotationY = ObjectAnimator.ofFloat(mLogo, "rotationY", 0f, 360f);
            rotationX = ObjectAnimator.ofFloat(mLogo, "rotationX", 0f, 360f);
            rotationY.setDuration(2500);
            rotationX.setDuration(1250);
            scaleXY = new AnimatorSet();
            scaleXY.playTogether(scaleX, scaleY, rotationY, rotationX);
        } else if (type == 2) {
            scaleX = ObjectAnimator.ofFloat(mLogo, "scaleX", 1f, 1f);
            scaleY = ObjectAnimator.ofFloat(mLogo, "scaleY", 1f, 1f);
            scaleX.setDuration(2500);
            scaleY.setDuration(1250);
            rotationY = ObjectAnimator.ofFloat(mLogo, "rotationY", 0f, 720f);
            rotationX = ObjectAnimator.ofFloat(mLogo, "rotationX", 0f, 0f);
            rotationY.setDuration(2500);
            rotationX.setDuration(1250);
            scaleXY = new AnimatorSet();
            scaleXY.playTogether(scaleX, scaleY, rotationY, rotationX);
        } else if (type == 3) {
            scaleX = ObjectAnimator.ofFloat(mLogo, "scaleX", 1f, 1f);
            scaleY = ObjectAnimator.ofFloat(mLogo, "scaleY", 1f, 1f);
            scaleX.setDuration(2500);
            scaleY.setDuration(1250);
            rotationY = ObjectAnimator.ofFloat(mLogo, "rotationY", 0f, 0f);
            rotationX = ObjectAnimator.ofFloat(mLogo, "rotationX", 0f, 720f);
            rotationY.setDuration(2500);
            rotationX.setDuration(1250);
            scaleXY = new AnimatorSet();
            scaleXY.playTogether(scaleX, scaleY, rotationY, rotationX);
        }
        ValueAnimator standby = ObjectAnimator.ofFloat(mLogo, "alpha", 1f, 1f);
        standby.setDuration(1000);
        ValueAnimator fadeout = ObjectAnimator.ofFloat(mLogo, "alpha", 1f, 0f);
        fadeout.setDuration(1000);
        List<Animator> animators = new ArrayList<Animator>();
        animators.add(scaleXY);
        animators.add(standby.setDuration(1000));
        animators.add(fadeout);
        return animators;
    }

觀察其中的條件式(第9~61),可以發現每個條件式的執行區塊,都做了2件事,首先是設定XY的縮放動畫,再來設定旋轉動畫。
但不管是設定縮放還是設定旋轉,都是使用類似的參數。為了減少重複的程式碼,我們可以另外建立通用的函式 setAnimation。
並把 if-else 條件式內容提取出來以增加可讀性。e.g.

    private ValueAnimator setAnimation(float start, float end, String propertyName, int durationMS)
    {
        ValueAnimator animation;
        animation = ObjectAnimator.ofFloat(mLogo, propertyName, start, end);
        animation.setDuration(durationMS);
        return animation;
    }
    private AnimatorSet zoomInAndRotation360()
    {
        AnimatorSet animations;
        ValueAnimator scaleX = setAnimation(0.1f, 1f, "scaleX", 2500);
        ValueAnimator scaleY = setAnimation(0.1f, 1f, "scaleY", 1250);
        ValueAnimator rotationY = setAnimation(0f, 360f, "rotationY", 2500);
        ValueAnimator rotationX = setAnimation(0f, 360f, "rotationX", 1250);
        animations = new AnimatorSet();
        animations.playTogether(scaleX, scaleY, rotationY, rotationX);
        return animations;
    }
    private AnimatorSet zoomOutAndRotation360()
    {
        AnimatorSet animations;
        ValueAnimator scaleX = setAnimation(2.0f, 1f, "scaleX", 2500);
        ValueAnimator scaleY = setAnimation(2.0f, 1f, "scaleY", 1250);
        ValueAnimator rotationY = setAnimation(0f, 360f, "rotationY", 2500);
        ValueAnimator rotationX = setAnimation(0f, 360f, "rotationX", 1250);
        animations = new AnimatorSet();
        animations.playTogether(scaleX, scaleY, rotationY, rotationX);
        return animations;
    }
    private AnimatorSet rotationYAxis720()
    {
        AnimatorSet animations;
        ValueAnimator rotationY = setAnimation(0f, 720f, "rotationY", 2500);
        animations = new AnimatorSet();
        animations.playTogether(rotationY);
        return animations;
    }
    private AnimatorSet rotationXAxis720()
    {
        AnimatorSet animations;
        ValueAnimator rotationX = setAnimation(0f, 720f, "rotationX", 2500);
        animations = new AnimatorSet();
        animations.playTogether(rotationX);
        return animations;
    }
    private List<Animator> setAnimationParameters(int type)
    {
        AnimatorSet animations = null;
        if (type == 0) {
            animations = zoomInAndRotation360();
        } else if (type == 1) {
            animations = zoomOutAndRotation360();
        } else if (type == 2) {
            animations = rotationYAxis720();
        } else if (type == 3) {
            animations = rotationXAxis720();
        }
        ValueAnimator alphaNormal = setAnimation(1f, 1f, "alpha", 1000);
        ValueAnimator fadeOut = setAnimation(1f, 0f, "alpha", 1000);
        List<Animator> animators = new ArrayList<Animator>();
        animators.add(animations);
        animators.add(alphaNormal);
        animators.add(fadeOut);
        return animators;
    }

OK~ 原先的setAnimationParameters,被我們拆分為一小段一小段的函式,小段的函式除了可以增加重用的機會以外,而明確的函式名稱完整表達了其意圖。
而且經過重構可以了解正確的程式碼意圖。為了提昇可讀性,第52~64的 if-else 決定引入列舉(AnimationType)來取代原本的int。e.g.

    ...
    private enum AnimationType {
        ZOOMIN_AND_ROTATION360, ZOOMOUT_AND_ROTATION360, ROTATION_Y_AXIS720, ROTATION_X_AXIS720
    }
    private List<Animator> setAnimationParameters(int type)
    {
        AnimationType animationType = AnimationType.values()[type];
        AnimatorSet animations = null;
        if (animationType == AnimationType.ZOOMIN_AND_ROTATION360) {
            animations = zoomInAndRotation360();
        } else if (animationType == AnimationType.ZOOMOUT_AND_ROTATION360) {
            animations = zoomOutAndRotation360();
        } else if (animationType == AnimationType.ROTATION_Y_AXIS720) {
            animations = rotationYAxis720();
        } else if (animationType == AnimationType.ROTATION_X_AXIS720) {
            animations = rotationXAxis720();
        }
        ValueAnimator alphaNormal = setAnimation(1f, 1f, "alpha", 1000);
        ValueAnimator fadeOut = setAnimation(1f, 0f, "alpha", 1000);
        List<Animator> animators = new ArrayList<Animator>();
        animators.add(animations);
        animators.add(alphaNormal);
        animators.add(fadeOut);
        return animators;
    }
...

現在我們再來看看 mHandler 的用途,看起來它在 run 函式中呼叫了 sendMessage。讓 Logo 發生圖形的變化。
事實上讓 LogoFlow 繼承 Runnable interface 並不是一個好的作法。
簡單的說為了避免 ANR,一般的耗時任務都應該放到Worker Thread 而不可在 UI Thread 執行(詳細內容參考android worker thread)。
因此另外將圖形處理獨立到另一個 LogoAnimTask 來處理。e.g.

package com.foxx.main;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import android.animation.Animator;
import android.animation.Animator.AnimatorListener;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.annotation.SuppressLint;
import android.os.AsyncTask;
import com.foxx.main.LogoFlow.AnimationType;
@SuppressLint("NewApi")
public class LogoAnimTask extends AsyncTask<Void, Void, Void>
{
    private ILogoFlow mLogoFlow;
    public LogoAnimTask(ILogoFlow logoFlow){
        mLogoFlow = logoFlow;
    }
    @Override
    protected void onPreExecute()
    {
        Random randomAnimationType = new Random();
        runLogoAnimations(randomAnimationType.nextInt(4));
    }
    @Override
    protected Void doInBackground(Void... params)
    {
        return null;
    }
    private void runLogoAnimations(int animationType)
    {
        AnimatorSet animation = new AnimatorSet();
        animation.playSequentially(setAnimationParameters(animationType));
        animation.start();
        animation.addListener(createAnimationListener());
    }
    private ValueAnimator setAnimation(float start, float end, String propertyName, int durationMS)
    {
        ValueAnimator animation;
        animation = ObjectAnimator.ofFloat(mLogoFlow.getLogo(), propertyName, start, end);
        animation.setDuration(durationMS);
        return animation;
    }
    private AnimatorSet zoomInAndRotation360()
    {
        AnimatorSet animations;
        ValueAnimator scaleX = setAnimation(0.1f, 1f, "scaleX", 2500);
        ValueAnimator scaleY = setAnimation(0.1f, 1f, "scaleY", 1250);
        ValueAnimator rotationY = setAnimation(0f, 360f, "rotationY", 2500);
        ValueAnimator rotationX = setAnimation(0f, 360f, "rotationX", 1250);
        animations = new AnimatorSet();
        animations.playTogether(scaleX, scaleY, rotationY, rotationX);
        return animations;
    }
    private AnimatorSet zoomOutAndRotation360()
    {
        AnimatorSet animations;
        ValueAnimator scaleX = setAnimation(2.0f, 1f, "scaleX", 2500);
        ValueAnimator scaleY = setAnimation(2.0f, 1f, "scaleY", 1250);
        ValueAnimator rotationY = setAnimation(0f, 360f, "rotationY", 2500);
        ValueAnimator rotationX = setAnimation(0f, 360f, "rotationX", 1250);
        animations = new AnimatorSet();
        animations.playTogether(scaleX, scaleY, rotationY, rotationX);
        return animations;
    }
    private AnimatorSet rotationYAxis720()
    {
        AnimatorSet animations;
        ValueAnimator rotationY = setAnimation(0f, 720f, "rotationY", 2500);
        animations = new AnimatorSet();
        animations.playTogether(rotationY);
        return animations;
    }
    private AnimatorSet rotationXAxis720()
    {
        AnimatorSet animations;
        ValueAnimator rotationX = setAnimation(0f, 720f, "rotationX", 2500);
        animations = new AnimatorSet();
        animations.playTogether(rotationX);
        return animations;
    }
    private List<Animator> setAnimationParameters(int type)
    {
        AnimationType animationType = AnimationType.values()[type];
        AnimatorSet animations = null;
        if (animationType == AnimationType.ZOOMIN_AND_ROTATION360) {
            animations = zoomInAndRotation360();
        } else if (animationType == AnimationType.ZOOMOUT_AND_ROTATION360) {
            animations = zoomOutAndRotation360();
        } else if (animationType == AnimationType.ROTATION_Y_AXIS720) {
            animations = rotationYAxis720();
        } else if (animationType == AnimationType.ROTATION_X_AXIS720) {
            animations = rotationXAxis720();
        }
        ValueAnimator alphaNormal = setAnimation(1f, 1f, "alpha", 1000);
        ValueAnimator fadeOut = setAnimation(1f, 0f, "alpha", 1000);
        List<Animator> animators = new ArrayList<Animator>();
        animators.add(animations);
        animators.add(alphaNormal);
        animators.add(fadeOut);
        return animators;
    }
    private AnimatorListener createAnimationListener()
    {
        return new AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animation)
            {
            }
            @Override
            public void onAnimationRepeat(Animator animation)
            {
            }
            @Override
            public void onAnimationEnd(Animator animation)
            {
                mLogoFlow.finishLogo();
            }
            @Override
            public void onAnimationCancel(Animator animation)
            {
            }
        };
    }
}

另外建立ILogoFlow interface 並讓外界透過 interface 來交互,目的是讓模組透過抽象來相依。

public interface ILogoFlow
{
    public View getLogo();
    public void finishLogoFlow();
}

而原本的 LogoFlow 只剩下輕量化的內容。e.g.

package com.foxx.main;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.MotionEvent;
import android.view.View;
import android.widget.ImageView;
import com.tlyerfoxx.sbcnote.R;
@SuppressLint("NewApi")
public class LogoFlow extends Activity implements ILogoFlow
{
    private ImageView mLogo;
    private LogoAnimTask mLogoAnimationtask;
    public enum AnimationType {
        ZOOMIN_AND_ROTATION360, ZOOMOUT_AND_ROTATION360, ROTATION_Y_AXIS720, ROTATION_X_AXIS720
    }
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.logoflow);
        mLogo = (ImageView) findViewById(R.id.imageView1);
        mLogoAnimationtask = new LogoAnimTask(this);
        mLogoAnimationtask.execute();
    }
    public boolean onTouchEvent(MotionEvent event)
    {
        finishLogoFlow();
        return true;
    }
    @Override
    public View getLogo()
    {
        return mLogo;
    }
    @Override
    public void finishLogoFlow()
    {
        LogoFlow.this.finish();
        Intent goAct = new Intent();
        goAct.setClass(LogoFlow.this, Table_Flow.class);
        startActivity(goAct);
        overridePendingTransition(R.anim.zoom_enter, R.anim.zoom_exit);
        System.exit(0);
    }
}

最後重新命名 LogoFlow 為 LogoActivity,ILogoFlow 為 ILogo。
 
 
 
 
 
 
 
 
 
 
 
 
 

分類
Java

Java的參數傳遞(pass by value)

基本概念卻讓很多人搞不清(包括我)。
首先在 java,函式的參數傳遞確實為 pass by value。
pass by value 代表當呼叫函式傳遞參數的時候,會複製參數的副本並傳入函式中,代表在函式內的參數和在函式外的變數是不同的。
所以在函式內部改變其值都不影響函式外部原先的變數。
基本型別(boolean , char , byte  , short , int , long , float , double)的情況很好理解。

    @Test
    public void testInt()
    {
        int intNumber = 0;
        System.out.println("before increase: intNumber:"+intNumber);
        increase(intNumber);
        System.out.println("after increase: intNumber:"+intNumber);
    }
    private void increase(int number)
    {
        number++;
        System.out.println("in increase intNumber:"+number);
    }

output:

before increase: intNumber:0
in increase intNumber:1
after increase: intNumber:0

但如果是非基本型別,如物件或是陣列就要小心,因為這時候複製的副本是連結到物件的參考,而不是物件本身的副本。
也就是說傳入函式內的參考和和函式外部的參考連結到的是同一個物件,而因為連結的是同一個物件,所以在函式內改變了參考也會影響原先的物件。
情況就會完全不同。
為了方便示範,先建立一個Account 類別。

public class Account {
    private int mAge;
    private String mName;
    public Account(int age, String name){
        mAge = age;
        mName = name;
    }
    public void setAge(int age)
    {
        mAge = age;
    }
    public void setName(String name)
    {
        mName = name;
    }
    public int getAge(){
        return mAge;
    }
    public String getName()
    {
        return mName;
    }
}

測試如下

@Test
    public void testAccount()
    {
        Account foxx = new Account(18, "foxx");
        System.out.println("before foxx.getAge():"+foxx.getAge()+" foxx.getName()"+foxx.getName());
        changeAccount(foxx);
        System.out.println("after foxx.getAge():"+foxx.getAge()+" foxx.getName():"+foxx.getName());
    }
    private void changeAccount(Account account)
    {
        account.setAge(81);
        account.setName("new foxx");
        System.out.println("in changeAccount account.getAge():"+account.getAge()+" account.getName():"+account.getName());
    }

output:

before foxx.getAge():18 foxx.getName()foxx
in changeAccount account.getAge():81 account.getName():new foxx
after foxx.getAge():81 foxx.getName():new foxx

從輸出可以看到在函式中藉由參考修改了不同的數值也會影響原先外部的物件。 
因此要小心在函式中的參考有什麼變化。修改 changeAccount 如下

    private void changeAccount(Account account)
    {
        account = new Account(0, "zero");
        System.out.println("in changeAccount account.getAge():"+account.getAge()+" account.getName():"+account.getName());
    }

output:

before foxx.getAge():18 foxx.getName()foxx
in changeAccount account.getAge():0 account.getName():zero
after foxx.getAge():18 foxx.getName():foxx

因為在 changeAccount 的第3行我們讓傳進來的參考連結一個新的物件,因此在第3行之後的修改完全不會影響原先的物件。
 
 
補上 Array 以及 String 的測試

    @Test
    public void testArray()
    {
        int[] intArray = new int[]{0,0,0};
        System.out.println("before intArray[0]:"+intArray[0]+" intArray[1]:"+intArray[1]+" intArray[2]:"+intArray[2]);
        changeArray(intArray);
        System.out.println("after intArray[0]:"+intArray[0]+" intArray[1]:"+intArray[1]+" intArray[2]:"+intArray[2]);
    }
    private void changeArray(int[] intArray)
    {
        intArray[0] = 100;
        intArray[1] = 101;
        intArray[2] = 102;
        System.out.println("in changeArray intArray[0]:"+intArray[0]+" intArray[1]:"+intArray[1]+" intArray[2]:"+intArray[2]);
    }

output:

before intArray[0]:0 intArray[1]:0 intArray[2]:0
in changeArray intArray[0]:100 intArray[1]:101 intArray[2]:102
after intArray[0]:100 intArray[1]:101 intArray[2]:102

 可以看到Array也是傳入參考的副本,所以在函式內的修改會影響原值。
 
String 的測試

    @Test
    public void testString()
    {
        String name = "foxx";
        System.out.println("before name:"+name);
        changeName(name);
        System.out.println("after name:"+name);
    }
    private void changeName(String name)
    {
        name = "xxof";
        System.out.println("in changeName name:"+name);
    }

output:

before name:foxx
in changeName name:xxof
after name:foxx

String 的結果就不太一樣了,它雖然不是基本型別,但測試結果卻和基本型別一樣。 
在函式內的修改不會影響原值。
 
再補上 Integer 的測試

    @Test
    public void testInteger()
    {
        Integer integer = new Integer(10);
        System.out.println("before integer:"+integer);
        changeInteger(integer);
        System.out.println("after integer:"+integer);
    }
    private void changeInteger(Integer integer)
    {
        integer = Integer.MAX_VALUE;
        System.out.println("in change integer:"+integer);
    }

output:

before integer:10
in change integer:2147483647
after integer:10

看起來 Integer 也是和基本型別相同,在函式中的改變不會影響外部。
 
 
 
 
 
 

分類
Uncategorized

分析 hello-jni 範例

這裡以hello-Jni為範例,紀錄如何呼叫原生方法,宣告原生方法,載入原生模組,在c/c++實作原生方法。
以下為 HelloJni.java

package com.example.hellojni;
import android.app.Activity;
import android.widget.TextView;
import android.os.Bundle;
public class HelloJni extends Activity
{
    /* this is used to load the 'hello-jni' library on application
     * startup. The library has already been unpacked into
     * /data/data/com.example.hellojni/lib/libhello-jni.so at
     * installation time by the package manager.
     */
    static {
        System.loadLibrary("hello-jni");
    }
    /* A native method that is implemented by the
     * 'hello-jni' native library, which is packaged
     * with this application.
     */
    public native String  stringFromJNI();
    /* This is another native method declaration that is *not*
     * implemented by 'hello-jni'. This is simply to show that
     * you can declare as many native methods in your Java code
     * as you want, their implementation is searched in the
     * currently loaded native libraries only the first time
     * you call them.
     *
     * Trying to call this function will result in a
     * java.lang.UnsatisfiedLinkError exception !
     */
    public native String  unimplementedStringFromJNI();
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        /* Create a TextView and set its content.
         * the text is retrieved by calling a native
         * function.
         */
        TextView  tv = new TextView(this);
        tv.setText( stringFromJNI() );
        setContentView(tv);
    }
}

第16~18行為載入原生模組的動作,注意使用 System.loadLibrary(“hello-jni”)指的是載入 java.library.path變數所包含路徑的 hello-jni,不必包含擴展名稱(lib)(.so),
而”hello-jni”必須與 Android.mk 裡的 LOCAL_MODULE 變數值(hello-jni)相同,另外要注意是原生模組的名稱要避免重複。
第24行宣告原生方法 stringFromJni()
第36行宣告原生方法unimplementedStringFromJni()
 
接著來看看 hello-jni.c 的內容。

/*
 * Copyright (C) 2009 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */
#include <string.h>
#include <jni.h>
/* This is a trivial JNI example where we use a native method
 * to return a new VM String. See the corresponding Java source
 * file located at:
 *
 *   apps/samples/hello-jni/project/src/com/example/hellojni/HelloJni.java
 */
jstring Java_com_example_hellojni_HelloJni_stringFromJNI( JNIEnv* env,
                                                  jobject thiz )
{
    return (*env)->NewStringUTF(env, "Hello from JNI !");
}

第26~30行即為原生方法stringFromJNI()的實作,注意

Java_com_example_hellojni_HelloJni_stringFromJNI

除了開頭的 Java_ 以外,原生方法的名稱必須完全對應於java層的位置以及名稱,e.g.
com_example_hellojni :路徑
HelloJni:類別名稱
stringFromJNI:方法名稱


 
而回傳值 jstring 對應於 java層的 String
參數部份java層不帶參數,而對應的原生方法必須帶入 JNIEnv* env , jobject thiz 2個參數。
JNIEnv* env 必須為原生方法的第一個參數,透過 env 可以使用虛擬機的許多方法(ref:http://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/functions.html)。
注意幾點:
1.在 C 中,使用env的方式必須為 (*env)->method,
而 C++ 則是 env->method。
 
2.靜態方法和實體方法,在java層中如果是實體方法,JNI層第2個參數為 jobject thiz,
在java層中如果是靜態方法,JNI層第2個參數則為 jclass clazz。
 
 

分類
Android

使用 gcc 編譯 .o 以及 .so 檔

首先確定已經存在 .c 檔(這裡以 hello-jni 為範例,在預設的情況下,已經存在/project_root_path/jni/hello-jni.c。

Step 1.打開Terminal並移動到專案根目錄的 jni 資料夾(/project_root_path/jni/)。

 

Step 2.編譯 .o 檔

輸入

gcc -Wall -fPIC -c hello-jni.c -I /usr/lib/jvm/java-7-oracle/include/linux/ -I /usr/lib/jvm/java-7-oracle/include/

Note:
-Wall:產生所有警告訊息。
-fPIC:表示編譯後為位置獨立的代碼。
-c:產生.o檔案。
hello-jni.c:代表要編譯的檔案,若有多個檔案必須使用空格分開,也可以指定路徑。
-I /path_of_directory:指定額外.h檔的搜索路徑(/path_of_directory),這是用在編譯的檔案(.c)中出現
#include “file” 的時候 , gcc/g++會在目前目錄尋找.h檔,如果沒有找到,會回到預設的.h目錄尋找,如果使用-I指定目錄,gcc 會從指定的目錄尋找,然後順著一般的順序。
因為hello-jni.c 有 #include <jni.h>,jni.h的位置即為 /usr/lib/jvm/java-7-oracle/include/
編譯完成後會產生 hello-jni.o 檔案
 

Step 3.編譯 .so 檔

輸入

gcc -Wall -rdynamic -shared -o libhello-jni.so hello-jni.o

完成後會產生 libhello-jni.so 檔
 

以上 Step 2 和 Step 3 可以使用一句指令完成,省去了產生.o檔的步驟,e.g.

gcc hello-jni.c -fPIC -shared -I /usr/lib/jvm/java-7-oracle/include/linux/ -I /usr/lib/jvm/java-7-oracle/include/ -lm -o libhello-jni.so

 
 

分類
Android

Android.mk 和 Application.mk

Android.mk 為 android ndk project 必備的描述檔,為 GNU Makefile 格式。

它的位置必須在jni目錄中,主要是說明如何build source 和 shared libraries。
在 /ndk的根目錄/build/core/ 底下還有許多mk檔可以參考。
以下為 HelloJni(/android-ndk/samples/HelloJni) 的 Android.mk

# Copyright (C) 2009 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE    := hello-jni
LOCAL_SRC_FILES := hello-jni.c
include $(BUILD_SHARED_LIBRARY)

首先#開頭的都為註解。
第15行規定為每個 Android.mk 起始行,不可替換成別的內容,其中 my-dir 為指出目前的路徑,也就是jni目錄的路徑。
第17行 CLEAR_VARS 會連結到 /ndk的根目錄/build/core/clear-vars.mk。
目的是清除所有除了LOCAL_PATH以外的 LOCAL_XXX 的變數,e.g. LOCAL_MODULE , LOCAL_SRC_FILES 等等。
Android.mk 在每一次的執行過程中可能會建立多個module,因為以LOCAL_ 開頭的都是全域變數,為了避免module之間的衝突,必須清除掉。
第19行為命名 module 的名稱,系統自動會加上前綴lib 以及後綴.so,最後會產生一個共享庫文件.so ,並名為 libhello-jni.so
第20行為用來建立這個module的來源檔,若有多個檔案,必須以空格分開。
第22行 BUILD_SHARED_LIBRARY 會連結到 /ndk的根目錄/build/core/build-shared-library.mk。
目的是將來源檔案轉換為共享庫文件的動作,其完整動作會描述在build-shared-library.mk 。
 

如何藉由 Android.mk 編譯 c/c++檔案:

1.開啟 Terminal,並移動到專案根目錄中。
2.呼叫 ndk-build,Terminal 輸入 ndk-build 絕對路徑(/path_of_android_ndk/ndk-build)。
3.編譯成功輸出

Android NDK: WARNING: APP_PLATFORM android-8 is larger than android:minSdkVersion 3 in ./AndroidManifest.xml
[arm64-v8a] Gdbserver      : [aarch64-linux-android-4.9] libs/arm64-v8a/gdbserver
[arm64-v8a] Gdbsetup       : libs/arm64-v8a/gdb.setup
[x86_64] Gdbserver      : [x86_64-4.9] libs/x86_64/gdbserver
[x86_64] Gdbsetup       : libs/x86_64/gdb.setup
[mips64] Gdbserver      : [mips64el-linux-android-4.9] libs/mips64/gdbserver
[mips64] Gdbsetup       : libs/mips64/gdb.setup
[armeabi-v7a] Gdbserver      : [arm-linux-androideabi-4.8] libs/armeabi-v7a/gdbserver
[armeabi-v7a] Gdbsetup       : libs/armeabi-v7a/gdb.setup
[armeabi] Gdbserver      : [arm-linux-androideabi-4.8] libs/armeabi/gdbserver
[armeabi] Gdbsetup       : libs/armeabi/gdb.setup
[x86] Gdbserver      : [x86-4.8] libs/x86/gdbserver
[x86] Gdbsetup       : libs/x86/gdb.setup
[mips] Gdbserver      : [mipsel-linux-android-4.8] libs/mips/gdbserver
[mips] Gdbsetup       : libs/mips/gdb.setup
[arm64-v8a] Install        : libhello-jni.so => libs/arm64-v8a/libhello-jni.so
[x86_64] Install        : libhello-jni.so => libs/x86_64/libhello-jni.so
[mips64] Install        : libhello-jni.so => libs/mips64/libhello-jni.so
[armeabi-v7a] Install        : libhello-jni.so => libs/armeabi-v7a/libhello-jni.so
[armeabi] Install        : libhello-jni.so => libs/armeabi/libhello-jni.so
[x86] Install        : libhello-jni.so => libs/x86/libhello-jni.so
[mips] Install        : libhello-jni.so => libs/mips/libhello-jni.so
➜

編譯成功後在專案根目錄會多出 obj , libs 資料夾,.so會產生在libs資料夾內。
 
進階用法:
1.建立多個共享庫文件。

LOCAL_PATH := $(call my-dir)
#第1個模組
include $(CLEAR_VARS)
LOCAL_MODULE    := hello-jni
LOCAL_SRC_FILES := hello-jni.c
include $(BUILD_SHARED_LIBRARY)
#第2個模組
include $(CLEAR_VARS)
LOCAL_MODULE    := module2
LOCAL_SRC_FILES := hello-jni.c
include $(BUILD_SHARED_LIBRARY)

 
2. 建立靜態函式庫。
可將第3方的模組轉換為靜態函式庫,並加到原始函式庫上

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE    := third_party_lib
LOCAL_SRC_FILES := third.c
include $(BUILD_STATIC_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE    := module
LOCAL_SRC_FILES := module.c
LOCAL_STATIC_LIBRARIES := third_party_lib
include $(BUILD_SHARED_LIBRARY)

 
3.多個模組使用同一個靜態函式庫

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE    := third_party_lib
LOCAL_SRC_FILES := third.c
include $(BUILD_STATIC_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE    := module1
LOCAL_SRC_FILES := module.c
LOCAL_STATIC_LIBRARIES := third_party_lib
include $(BUILD_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE    := module2
LOCAL_SRC_FILES := module2.c
LOCAL_STATIC_LIBRARIES := third_party_lib
include $(BUILD_SHARED_LIBRARY)

參考官網:https://developer.android.com/ndk/guides/android_mk.html#over

Application.mk 為描述應用程序需要哪些模組,也定義模組間的通用變數,必須位於 jni 目錄。

以下為 HelloJni 的 Application.mk

APP_ABI := all

APP_ABI 表示平台,而 all 代表為所有支援的平台建立二建制文件。
參考官網:https://developer.android.com/ndk/guides/application_mk.html