2015年10月13日 星期二

[PHP] 換行造成的災難

關於 ?> end tag,官網有這麼一段話,建議不要寫:

The closing tag of a PHP block at the end of a file is optional, and in some cases omitting it is helpful when using include or require, so unwanted whitespace will not occur at the end of files, and you will still be able to add headers to the response later. It is also handy if you use output buffering, and would not like to see added unwanted whitespace at the end of the parts generated by the included files.

最近遇到一件慘案。

伺服器A要呼叫伺服器B的一隻 API,因為早期開發比較沒有固定做法,所以把整個資料進行加密回傳給伺服器A。 然後有一天突然說解密錯誤。

測試的時候可以看到,伺服器A拿到的資料會換行,但是伺服器B傳出的時候並沒有換行。示意如下:

// 伺服器A
[Tue Oct 13 11:27:39 2015] [error] [client 127.0.0.1] 
4fCfBODGX4qDIcLjcc0NqbY-

// 伺服器B
[Tue Oct 13 11:27:39 2015] [error] [client 127.0.0.1] 4fCfBODGX4qDIcLjcc0NqbY-

  1. // handler.php
  2. <?php
  3. require_once 'XYZ.php';
  4. // ...
  5.  
  6. header('Content-Type: application/octet-stream');
  7. header('HTTP/1.0 200');
  8.  
  9. echo encrypt_data($output);
  10. ?>
檢查伺服器B,看起來沒什麼改動。找了很久之後才發現,有人在某隻依賴的 PHP 檔案的 end tag 後面加了一行:
  1. // ABC.php
  2. <?php
  3. // ...
  4. ?>
  5.  

一個小疏忽會累死別人。

2015年8月13日 星期四

[Android] TextView 取代沒有資料的 ListView

有時候會發生沒有資料可以塞入 ListView ,此時需要一個 TextView 頂替:

<ListView
    android:id="@+id/listView"
    android:layout_width="match_parent"
    android:layout_height="0dp"
    android:layout_weight="1"/>

<TextView
    android:id="@+id/textView"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center_vertical|center_horizontal"
    android:text="no data"/>

接下來就是把 TextView 裝進 ListView:

ListView listView = (ListView) findViewById(R.id.listView);
listView.setEmptyView(findViewById(R.id.textView));
listView.setAdapter(adapter);



另外關於 layout_weight 的用法可以參考: Android 对Layout_weight属性完全解析以及使用ListView来实现表格

Android Studio 1.3
    compileSdkVersion 22
    buildToolsVersion "23.0.0 rc3"

2015年8月6日 星期四

[Android] EditText 做下拉選單

public class SpinnerView extends EditText
{
    private static class OnClickPopupSpinnerListener implements OnClickListener
    {
        private final SpinnerView spinnerView;
        private final List items;

        public OnClickPopupSpinnerListener(SpinnerView spinnerView, List items) {
            this.spinnerView = spinnerView;
            this.items = new ArrayList<>(items);
        }

        @Override
        public void onClick(View v) {
            final ListPopupWindow lpw = new ListPopupWindow(spinnerView.getContext());
            lpw.setOnItemClickListener(new AdapterView.OnItemClickListener() {
                @Override
                public void onItemClick(AdapterView parent, View view, int position, long id) {
                    String item = items.get(position);
                    spinnerView.setText(item);
                    lpw.dismiss();
                }
            });

            lpw.setAdapter(new ArrayAdapter(spinnerView.getContext(), android.R.layout.simple_list_item_1, items));
            lpw.setAnchorView(v);
            lpw.setModal(true);
            lpw.show();
        }
    }

    public SpinnerView(Context context) {
        super(context);
    }

    public SpinnerView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public SpinnerView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    public SpinnerView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

    public void setItems(List items) {
        setOnClickListener(new OnClickPopupSpinnerListener(this, items));
    }
}

在 layout.xml 就當作一般 EditText 使用。 在 activity 有設定 list 才會變成下拉選單。

@Override
protected void onCreate(Bundle savedInstanceState) {
    etSize = (SpinnerView) findViewById(R.id.editSize);
    etSize.setItems(Lists.newArrayList(new String[]{"small", "medium", "large"}));
    ...
}

參考資料:http://www.informit.com/articles/article.aspx?p=2078060&seqNum=4

Android Studio 1.3
    compileSdkVersion 22
    buildToolsVersion "23.0.0 rc3"

[Android] 點擊按鍵時關閉鍵盤

如果關注 EditText,點擊按鍵時鍵盤沒有自動關閉, 可以在 onClick 方法或是 Listener:

public void onClickButton(View view) {
    InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
    imm.hideSoftInputFromWindow(findViewById(R.id.button).getWindowToken(), 0);
}

findViewById(R.id.button).setOnClickListener(new OnClickListener() {
    @Override
    public void onClick(View v) {
        InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
        imm.hideSoftInputFromWindow(findViewById(R.id.button).getWindowToken(), 0);
    }
};


根據 API : public boolean hideSoftInputFromWindow (IBinder windowToken, int flags), flags 僅建議使用 0 或 HIDE_IMPLICIT_ONLY。

flags : Provides additional operating flags. Currently may be 0 or have the HIDE_IMPLICIT_ONLY bit set.

參考資料:

Android Studio 1.3
    compileSdkVersion 22
    buildToolsVersion "23.0.0 rc3"

2015年8月5日 星期三

[Android] ActionBar 顯示返回鍵 homeAsUp

先在 AndroidManifest.xml 設定 activity 關係:

<application ...>
    <activity android:name=".MainActivity" ...>
    </activity>
    <activity android:name=".SecondActivity"
              android:parentActivityName=".MainActivity" ...>
    </activity>
</application>
在 Activity 設定:
@Override
protected void onCreate(Bundle savedInstanceState) {
    ActionBar actionBar = getSupportActionBar();
    actionBar.setDisplayHomeAsUpEnabled(true);
}



網路上的方法不需要添加:

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    switch (item.getItemId()) {
    case R.id.home:
        finish();
        return true;
    }
    return super.onOptionsItemSelected(item);
}
經過測試,id 既不是 R.id.home,也不是 R.id.homeAsUp, 應該是透過 AndroidManifest.xml 綁定 parent id。



前面返回時,是重建 Intent 轉回去呼叫 MainActivity#onCreate(),所以資料會都丟掉。 避免這種情況可以複寫這個方法:

@Override
public Intent getSupportParentActivityIntent() {
    finish();
    return null;
}

參考資料:

Android Studio 1.3
    compileSdkVersion 22
    buildToolsVersion "23.0.0 rc3"

    com.android.support:appcompat-v7:22.2.+

2015年8月4日 星期二

[Android] AsyncTask 使用 Toast

在 #doInBackground() 抓檔時,會想要即時用 Toast 顯示訊息。 但是會造成錯誤:

java.lang.RuntimeException: An error occured while executing doInBackground()
...

可行的方法是在 #postExecute() 才處理。

private Exception exception;

@Override
protected Result doInBackground(Params... requests) {
   try {
   } catch (Exception e) {
      // Toast.makeText(context, "error occurs", Toast.LENGTH_LONG).show();
      exception = e;
   }
}

@Override
protected void onPostExecute(List googleImages) {
   if (exception != null) {
      Toast.makeText(context, "error occurs", Toast.LENGTH_LONG).show();
   }
}

參考資料:StackOverflow: java.lang.RuntimeException: An error occured while executing doInBackground()

Android Studio 1.3
    compileSdkVersion 22
    buildToolsVersion "23.0.0 rc3"

2015年8月3日 星期一

[Android] ActionBar 顯示 app icon

API 21 之後預設移除 app icon。

android.support.v7.widget.Toolbar

The use of application icon plus title as a standard layout is discouraged on API 21 devices and newer.

android.support.v7.app.ActionBar

When using the Material themes (default in API 21 or newer) the navigation button (formerly "Home") takes over the space previously occupied by the application icon.

設定 app icon :

protected void onCreate(Bundle savedInstanceState) {
   ActionBar actionBar = getSupportActionBar();
   actionBar.setLogo(R.drawable.ic_laucher);
   actionBar.setDisplayUseLogoEnabled(true);
   actionBar.setDisplayShowHomeEnabled(true);
}

⚠ icon 需要在 drawable 目錄,不能使用 mipmap。
⚠ 3 個方法都要寫。

參考資料:StackOverflow: The application icon does not show on action bar

Android Studio 1.3
    compileSdkVersion 22
    buildToolsVersion "23.0.0 rc3"

    com.android.support:appcompat-v7:22.2.+

2015年7月28日 星期二

[Android] ScrollView 嵌套 ListView

預設 ListView 只會顯示一個子項,在 ScrollView 添加屬性即可:

android:fillViewport="true"

參考資料:http://www.oschina.net/question/163910_27133



⚠ 實際測試只有 ListView 也可以捲動。

此外,ScrollView 搭配 SwipeRefreshLayout 會發生任何時候下拉都會更新。 但是拿掉 ScrollView ,就只有在最頂端的時候下拉才會更新。

<android.support.v4.widget.SwipeRefreshLayout ...>
   <ScrollView ...>
      <ListView ...>
      </ListView>
   </ScrollView>
</android.support.v4.widget.SwipeRefreshLayout>

Android Studio 1.2.1.1
    compileSdkVersion 22
    buildToolsVersion "23.0.0 rc3"

    com.android.support:support-v4:22.2.+

2015年7月24日 星期五

[Android Studio] 使用 Lombok

Lombok 是在 POJO 寫些注解,然後在編譯時期幫忙產生冗余代碼。

在 build.gradle 設置:

dependencies {
    ...
    compile 'org.projectlombok:lombok:1.16.+'
}

在 Android Studio 編譯時,會出現:

    error: package javax.annotation does not exist
查過網路資料,需要先安裝 Lombok Plugin,再在 Project 根目錄建立 lombok.config,並且設定:
lombok.addGeneratedAnnotation = false

參考資料:https://projectlombok.org/setup/android
參考資料:StackOverflow: How to set up compile library in android studio. LOMBOK

Android Studio 1.2.1.1
    compileSdkVersion 22
    buildToolsVersion "23.0.0 rc3"

[Android] ActiveAndroid 入門

ActiveAndroid 是輕量型 ORM 可以存取 SQLite。 只要幾個步驟就可以簡單使用。

在 build.gradle 設置:

repositories {
    mavenCentral()
    maven { url "https://oss.sonatype.org/content/repositories/snapshots/" }
}

dependencies {
    ...
    compile 'com.michaelpardo:activeandroid:3.1.0-SNAPSHOT'
}

在 AndroidManifest.xml 指定啓動點:

<manifest ...>
    <application android:name="com.activeandroid.app.Application" ...>
    </application>
</manifest>

定義 POJO:

@Table(name = "USER")
public class User extends Model
{
    @Column(name = "NAME")
    private String name;

    public User() {}

    public User(String name)
    {
        this.name = name;
    }

    public String getName()
    {
        return name;
    }

    public void setName(String name)
    {
        this.name = name;
    }
}
List<User> data = new Select().from(User.class).execute();

⚠ 一定要有預設建構子。
⚠ #execute() 會回傳 null 值。



如果出現以下錯誤,

Suppressed: java.lang.ClassNotFoundException: android.support.v4.util.LruCache
        at java.lang.Class.classForName(Native Method)
        at java.lang.BootClassLoader.findClass(ClassLoader.java:781)
        at java.lang.BootClassLoader.loadClass(ClassLoader.java:841)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:504)
        ... 17 more
在 build.gradle 設定:
    compile 'com.android.support:appcompat-v7:22.2.+'

參考資料:https://github.com/pardom/ActiveAndroid

Android Studio 1.2.1.1
    compileSdkVersion 22
    buildToolsVersion "23.0.0 rc3"

2015年1月9日 星期五

[PHP] is_array() vs. identical (===)

function calc($t0, $t1)
{
    $v0 = array_sum(explode(' ', $t0));
    $v1 = array_sum(explode(' ', $t1));
    $sub = $v1 - $v0;
    echo "$v1 - $v0 = $sub\n";
    return $sub;
}

$a = array();
$b = array(1);
$c = '';

$t0 = microtime();
for ($i = 0; $i < 1E6; $i++) {
    is_array($a);
    is_array($b);
    is_array($c);
}
$t1 = microtime();
$method1 = calc($t0, $t1);     // output: 1420785145.2006 - 1420785142.9861 = 2.2144329547882

$t0 = microtime();
for ($i = 0; $i < 1E6; $i++) {
    $a === (array) $a;
    $b === (array) $b;
    $c === (array) $c;
}
$t1 = microtime();
$method2 = calc($t0, $t1);     // output: 1420785142.986 - 1420785142.2001 = 0.78591012954712

echo ($method1 - $method2) / $method1; // output: 0.64509644428486

從測試中可以知道 is_array() 比較慢。

PHP 5.3.24 (cli) (built: Jun 10 2013 16:42:20)
Copyright (c) 1997-2013 The PHP Group
Zend Engine v2.3.0, Copyright (c) 1998-2013 Zend Technologies
    with Xdebug v2.2.0rc1, Copyright (c) 2002-2012, by Derick Rethans

[Java] Invalid HTTP method: PATCH

最近系統需要使用 Netty4,所以把衝突的 Netty3 拆掉,然後就出現了例外。 pom.xml <dependency> <groupId>com.ning</groupId> <artifactId>as...