分類
Uncategorized

JSmooth 介紹與使用

當產生 jar 檔之後,在已經安裝 jre 的環境中可以直接點擊左鍵來執行,如果沒有安裝 jre ,或是非得以 exe 檔執行,那該怎麼辦呢,目前有許多工具可以將 jar 檔轉換成 exe 檔 ,今天介紹 JSmooth ,JSmooth 使用簡單,免費,不用註冊, 我使用的版本為 JSmooth 0.9.9-7 , 先下載吧 官網 http://jsmooth.sourceforge.net/ ,下載完成後執行

Skeleton 頁面是選擇產出 exe 的種類,根據需求來選擇,一般選擇 Windowed Wrapper , 如果想觀察exe檔執行的過程,可以勾選 Debug console

Executable 頁面, Executable Binary 是選擇 exe 檔的名稱和位置,必須以 .exe 為結尾, Executable Icon 為 exe 檔的 icon ,可支援 png, Current directory 為目前路徑,一般來說不必填寫,而會勾選底下的
Sets the executable folder as current directory of the application

Application 頁面,主要決定執行的 jar 檔, ClassPath 選擇要執行的 jar 檔,接著 Embedded jar 也選擇剛剛在 ClassPath 相同的 jar 檔,Use an Embedded jar 也要勾選, 最後 main class 則是選擇執行的類別(main), Application Arguments 則是 執行 exe 時,可傳入的參數

JVM Selection 頁面, 主要是將 jre 和 exe 結合,勾選 Use an JVM bundled with your Application ,並選擇已安裝好的 jre ,預設路徑為 C:Program FilesJava 中

JVM Configuration 頁面, Maximum Memory 填入 512 MB

最後點擊 Compile 圖示,會要求選擇儲存的位置,完成!!!

分類
Uncategorized

使用 JDBC 連接資料庫(SQL Server)

使用 JDBC 連接資料庫相當簡單,首先根據自己的需要下載對應的 jar 檔,以 SQL Server 2000 為例,需要下載 sqljdbc4.jar ,下載之後 add 到專案中, 接著在程式碼中連接資料庫,一個簡單的範例如下
ConnToSQLServer.java

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
public class ConnToSQLServer{
    //show tables in database
    public static String COMM_SHOW_ALLTABLES = "select * from sysobjects where xtype = 'U';";
    //show version of database
    public static String COMM_SHOW_VERSION = "SELECT @@VERSION AS 'SQL Server Version'";
    //show all databases
    public static String COMM_SHOW_ALLDATABASER = "SELECT * FROM master.dbo.sysdatabases";
    //show system config of database
    public static String COMM_SHOW_CONFIGURES = "SELECT * FROM master.dbo.sysconfigures";
    Connection conn;
    String driver = "com.microsoft.sqlserver.jdbc.SQLServerDriver";
    String url;
    String ipNum;
    String portNum;
    String dbName;
    String userName;
    String passWord;
    /**
     *
     * @param ipnum : ip
     * @param portnum : port
     * @param dbname : dbname
     * @param username
     * @param password
     */
    public F_ConnToSQLServer(String ipnum,String portnum,
            String dbname,String username,String password){
        ipNum = ipnum;
        portNum = portnum;
        dbName = dbname;
        userName = username;
        passWord = password;
        try {
            Class.forName(driver);
            url= "jdbc:sqlserver://"+ipNum+";databaseName="+dbName+";integratedSecurity=false";
            try {
                if (userName == null && passWord == null) {
                    conn = DriverManager.getConnection(url);
                } else {
                    // url , user , password
                    conn = DriverManager.getConnection(url, userName, passWord);
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
    public Connection getConn(){
        return conn;
    }
}

 
只要建立 ConnToSQLServer 物件,在建構子中傳入帳號和密碼,就能建立和資料庫連線
使用範例
JdbcExam.java

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
public class ConnectSqlServerExam {
    public static void main(String[] args) {
        F_ConnToSQLServer connpgsql = new F_ConnToSQLServer("192.168.17.128",
                "5432", "dbname", "user", "password");
        Connection conn = connpgsql.getConn();
        String comm = "select * from tablename";
        execComm(conn, comm);
    }
    public static void execComm(Connection conn, String comm) {
        Statement stmt;
        ResultSet rs;
        ResultSetMetaData rsmd = null;
        try {
            stmt = conn.createStatement();
            if (stmt.execute(comm)) {
                rs = stmt.getResultSet();
                rsmd = rs.getMetaData();
                while (rs.next()) {
                    for (int i = 1; i < rsmd.getColumnCount() + 1; i++) {
                        if (i != rsmd.getColumnCount()) {
                            System.out.print(rs.getString(i) + "  ");
                        } else {
                            System.out.print(rs.getString(i));
                        }
                    }
                    System.out.println();
                }
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

最後就能在主控台看到結果了

分類
Uncategorized

使用 Log4J 印出訊息

記錄 Log4J 的使用!!
Log4J不僅能在主控台輸出訊息,還能寫到檔案或資料庫中,相當好用的訊息工具
1.先下載 log4j-1.2.17.jar 並加到專案中
2.建立 log4j 的設定檔,副檔名為 Log4J.properties ,並放到 project_root_dir/logfile/log4j/ 中。
如下

log4j.rootLogger=DEBUG,consoleAppender,fileAppender
#,mailAppender,dbAppender
#Console Log(主控台輸出)
log4j.appender.consoleAppender=org.apache.log4j.ConsoleAppender
log4j.appender.consoleAppender.layout=org.apache.log4j.PatternLayout
log4j.appender.consoleAppender.layout.ConversionPattern=%d{yyyy/MM/dd HH:mm:ss} %-5p %m %l %F %n
#File Log(檔案輸出)
log4j.appender.fileAppender=org.apache.log4j.RollingFileAppender
log4j.appender.fileAppender.File=d:/temp/log4j.log
log4j.appender.fileAppender.MaxFileSize=30MB
log4j.appender.fileAppender.MaxBackupIndex=10000
log4j.appender.fileAppender.layout=org.apache.log4j.PatternLayout
log4j.appender.fileAppender.layout.ConversionPattern=%d{yyyy/MM/dd HH:mm:ss} %-5p %m%n
#Mail Log(使用時必須import javamail相關的jar  功能:一出錯馬上發送mail)
#log4j.appender.mailAppender=org.apache.log4j.net.SMTPAppender
#log4j.appender.MailLog.Threshold=error
#log4j.appender.mailAppender.BufferSize=512
#log4j.appender.mailAppender.From=xxx@xxx.com
#log4j.appender.mailAppender.To=xxx@xxx.com
#log4j.appender.mailAppender.SMTPHost=xxx.com
#log4j.appender.mailAppender.Subject=System Occur Error!!!
#log4j.appender.mailAppender.layout=org.apache.log4j.PatternLayout
#log4j.appender.mailAppender.layout.ConversionPattern=%d{yyyy/MM/dd HH:mm:ss} %-5p %m%n
#Database Log(使用時必須import jdbc driver相關的jar  功能:寫 log 到 db 中)
#log4j.appender.dbAppender=org.apache.log4j.jdbc.JDBCAppender
#log4j.appender.dbAppender.driver=oracle.jdbc.driver.OracleDriver
#log4j.appender.dbAppender.URL=jdbc:oracle:thin:@xxx:1521:xxx
#log4j.appender.dbAppender.user=xxx
#log4j.appender.dbAppender.password=xxx
#log4j.appender.dbAppender.layout=org.apache.log4j.PatternLayout
#log4j.appender.dbAppender.sql=INSERT INTO LOG(STATUS,MESSAGE,LOGDT) VALUES ('%-5p','%m',SYSDATE)
#等級
#Log4j將輸出訊息分成五個等級,分別為DEBUG、INFO、WARN、ERROR、FATAL,在上面設定檔將輸出等級設為INFO,就表示INFO~FATAL等級的訊息將會輸出,因此DEBUG等級的訊息就會被忽略,如果將設定檔的第一行改為log4j.rootLogger=DEBUG, A1, A2,就會兩個訊息都輸出了。除了這五個等級外,也可以設為ALL或OFF, ALL很示全部訊息都輸出,OFF則表示不輸出訊息。
#輸出格式
# %c 輸出日誌訊息所屬的類別的全名
# %d 輸出日誌時間點的日期或時間,指定格式的方式:%d{yyy-MM-dd HH:mm:ss }。
# %l 輸出日誌事件的發生位置,即輸出日誌訊息的語句處於它所在的類別的第幾行。
# %m 輸出訊息,如log(message)中的message。
# %n 輸出一個列尾符號。
# %p 輸出優先階層,即DEBUG,INFO,WARN,ERROR,FATAL。如果是調用debug()輸出的,則為DEBUG,依此類推。
# %r 輸出自應用啟動到輸出該日誌訊息所耗費的毫秒數。
# %t 輸出產生該日誌事件的線程名。
# %r 輸出自應用啟動到輸出該日誌訊息所耗費的毫秒數。
# %f 輸出日誌訊息所屬的類別的類別名。
#appender被設為org.apache.log4j.DailyRollingFileAppender表示Log4j會將LOG檔依日期分開。

最後直接在程式碼中使用,寫了一個簡單的類別(Facade)方便印出訊息
Log4JFacade.java

import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.Properties;
import org.apache.log4j.Logger;
import org.apache.log4j.PropertyConfigurator;
public class Log4JFacade {
    static Properties pro;
    static Logger logger;
    static{
        loadPropertiesForLog4J();
        setConfigForLogger();
    }
    private static void loadPropertiesForLog4J(){
        pro = new Properties();
        try {
            pro.load(new FileReader("logfile/log4j/Log4J.properties"));
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    private static void setConfigForLogger(){
        PropertyConfigurator.configure(pro);
        logger = Logger.getLogger("J2SE");
    }
    public static void printDebug(String mes){
        logger.debug(mes);
    }
    public static void printInfo(String mes){
        logger.info(mes);
    }
    public static void printWarning(String mes){
        logger.warn(mes);
    }
    public static void printError(String mes){
        logger.error(mes);
    }
    public static void printFatal(String mes){
        logger.fatal(mes);
    }
}

使用範例

public class Log4J_Exam {
    public static void main(String[] args){
        Log4JFacade.printDebug("your message");
        Log4JFacade.printInfo("your message");
        Log4JFacade.printWarning("your message");
        Log4JFacade.printError("your message");
        Log4JFacade.printFatal("your message");
    }
}

 

分類
Uncategorized

Mac 安裝 Tomcat

安裝過程其實相當簡單
1. 到官網下載 Tomcat 6.0,選擇6.0是因為7.0版本似乎有些問題
2. 解壓縮到自己喜歡的資料夾,並將目錄名稱改名為 tomcat (越短越方便)
3. 啓動終端機,移動到 /tomcat/bin 中,輸入

sh startup.sh ,

如果出現 Permission Denied 代表需要改變權限,輸入

sudo chmod 755 /目錄名稱/tomcat/bin/*.sh

4. 如果出現

Using CATALINA_BASE:   /Users/foxx/Documents/tomcat
Using CATALINA_HOME:   /Users/foxx/Documents/tomcat
Using CATALINA_TMPDIR: /Users/foxx/Documents/tomcat/temp
Using JRE_HOME:        /System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK/Home
Using CLASSPATH:       /Users/foxx/Documents/tomcat/bin/bootstrap.jar

代表可以執行 startup.sh
5. 啓動瀏覽器,並輸入http://localhost:8080 ,並出現以下畫面,代表已測試成功

 

分類
Uncategorized

開啟手機閃光燈(手電筒)(Camera & Camera.Parameters)

開啟閃光燈相當簡單,因為閃光燈是 Camera 類別的功能,所以必須先建立 Camera 物件再搭配 Camera.Parameters以控制 Camera 的設定,如下

      Camera c;
      Parameters p;
      @Override
      public void onCreate(Bundle savedInstanceState) {
          super.onCreate(savedInstanceState);
          setContentView(R.layout.activity_main);
          c = Camera.open();
   
          p = c.getParameters();
          p.setFlashMode(Parameters.FLASH_MODE_TORCH);
          c.setParameters(p);
      }

事實上打開閃光燈是靠著第 12 行來達成,之後的關閉閃光燈也是類似的做法
關閉(切換)閃光燈

               String s = p.getFlashMode();
              if(s.equals(Parameters.FLASH_MODE_TORCH)){
                  p.setFlashMode(Parameters.FLASH_MODE_OFF);
              }
              else if(s.equals(Parameters.FLASH_MODE_OFF)){
                  p.setFlashMode(Parameters.FLASH_MODE_TORCH);
              }
   
                  c.setParameters(p);

藉由 setFlashMode 方法來切換閃光燈的開關(第 3 , 6行)
調整閃光燈強度為弱光可以使用

 p.setFocusMode("macro");//調整為弱光
 p.setFocusMode("auto");//恢復正常亮度

來切換
測試結果 samsung s2 可以使用, 不過 htc incredible s 卻沒有 macro 可以使用,應該有其它的方法可以控制才對

分類
Design Pattern

Strategy Pattern(策略模式)

1.把常變動的部分封裝起來
2.使用實作介面來建立物件
3.多用合成少用繼承
範例

  public class Simulator {
      /**
       * @param args
       */
      public static void main(String[] args) {
          // TODO Auto-generated method stub
          Character sprite = new Sprite();//使用多型建立
          Character normalman = new NormalMan();//同上
          Character samuri = new Samuri();//同上
          Character monsterboss = new MonsterBoss();//同上
          //executeAttack 是 Character 類別的方法,方法內容為 ab 呼叫 attack 方法
               //而各 ab 是在每個建構子中建立不同的 ab
          sprite.executeAttack();
          normalman.executeAttack();
          samuri.executeAttack();
          monsterboss.executeAttack();
               //動態改變 ab
          monsterboss.setAttackBehavior(new AttackBySword());
          monsterboss.executeAttack();
          monsterboss.setAttackBehavior(new AttackByMagic());
          monsterboss.executeAttack();
      }
  }

 

Character.java

    
  public abstract class Character {
   
      AttackBehavior ab;//(攻擊行為)常變動的部分封裝成類別,宣告為介面型態可以動態改變
      public void executeAttack(){
          ab.attack();
      }
      public void setAttackBehavior(AttackBehavior ab){
          this.ab = ab;
      }
  }
   
  class NormalMan extends Character{
      public NormalMan(){
          ab = new AttackByHand();
      }
  }
   
  class Sprite extends Character{
      public Sprite(){
          ab = new AttackByMagic();
      }
  }
   
  class Samuri extends Character{
      public Samuri(){
          ab = new AttackBySword();
      }
  }
   
  class MonsterBoss extends Character{
      public MonsterBoss(){
          ab = new AttackByHand();
      }
  }

如果之後還有新的角色(Character)種類,可以繼續擴充,不影響原角色類別

   
  public interface AttackBehavior {
      public void attack();
  }
   
  class AttackByHand implements AttackBehavior{
      public void attack(){
          System.out.println("AttackByHand");
      }
  }
   
  class AttackBySword implements AttackBehavior{
      public void attack(){
          System.out.println("AttackBySword");
      }
  }
   
  class AttackByMagic implements AttackBehavior{
      public void attack(){
          System.out.println("AttackByMagic");
      }
  }

如果之後有新的攻擊行為種類,可以繼續擴充,不影響原攻擊行為類別

分類
Uncategorized

Notification(通知)

通知是什麼?

通知為Notification,基本上是在應用程式的 UI 以外(通常是狀態列)顯示的訊息。
官網參考:https://developer.android.com/guide/topics/ui/notifiers/notifications?hl=zh-tw


如何使用通知

使用通知基本上有 4 個步驟

  1. 建立NotificationChannel
  2. 透過Builder建立通知並設定各種選項
  3. 設定通知要啟動的Action,該Action代表通知按下後要啟動哪個元件
  4. 透過NotificationManager發出通知

 

  1. 建立 NotificationChannel(NC)

NC的用途為分類並管理通知,在NC中可以設定一些通用項目讓屬於該NC的通知共用。
當裝置的android version在26或以上,必須設定NotificationChannel,否則會出現Toast提示錯誤訊息。
重點為建立NC時所指定的參數(NOTIFICATION_CHANNEL_ID )這個值在建立Notification時也必須設定,否則通知會無法顯示。

private static final String NOTIFICATION_CHANNEL_ID = "notification_channel";
private void createNotificationChannel() {
    if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
      NotificationChannel channel = new NotificationChannel(NOTIFICATION_CHANNEL_ID, "name",
          NotificationManager.IMPORTANCE_HIGH);
      channel.setDescription("channel description");
      channel.enableLights(true);
      channel.setLightColor(Color.GREEN);
      channel.enableVibration(true);
//      channel.setShowBadge(false);
//      channel.setSound(System.ALARM_ALERT);
      mNotificationManager.createNotificationChannel(channel);
    }
  }

 

  1. 透過NotificationCompat.Builder建立通知並設定各種選項

a.透過NotificationCompat.Builder建立通知,最少必須包含一個小圖示,一個標題,一段文字。

    Builder builder = new Builder(mContext, NOTIFICATION_CHANNEL_ID);
    builder.setSmallIcon(android.R.drawable.sym_action_chat);
    builder.setContentTitle(title);
    builder.setContentText(text);

b.如果想設定更多的選項也是透過setxxx來達到,如下的設定就能讓該通知具有Heads-up的外觀,也就是在小型浮動視窗中出現。

    builder.setPriority(NotificationCompat.PRIORITY_HIGH);
    builder.setVibrate(new long[]{0, 1000});
    builder.setAutoCancel(true);

 

  1. 設定通知要啟動的Action,該Action代表按下通知後要啟動哪個元件

Action是透過PendingIntent來設定如下

Intent actionIntent = new Intent(mContext, NotificationLaunchActivity.class);
PendingIntent launchNotificationLaunchActivity = PendingIntent
    .getActivity(mContext, 0, actionIntent, PendingIntent.FLAG_UPDATE_CURRENT);
builder.setContentIntent(launchNotificationLaunchActivity);

Intent actionIntent = new Intent(mContext, NotificationLaunchActivity.class);
設定點擊該通知要啟動的Activity。
PendingIntent launchNotificationLaunchActivity = PendingIntent
.getActivity(mContext, 0, actionIntent, PendingIntent.FLAG_UPDATE_CURRENT);
包裝actionIntent的PendingIntent。
最後透過setContentIntent設定要啟動的Intent。

builder.setContentIntent(launchNotificationLaunchActivity);

注意:PendingIntent也可以啟動Service或BroadcastReceiver,使用以下方法來指定要啟動的元件。

PendingIntent.getBroadcast();
PendingIntent.getService()

 

  1. 透過NotificationManager發出通知
mNotificationManager.notify(NOTIFICATION_ID, builder.build());

NOTIFICATION_ID
為識別該通知的數值,但不太需要特別設定。
完整程式碼

public class NotificationWrapper {
  public static final int NOTIFICATION_ID = 888;
  private static final String NOTIFICATION_CHANNEL_ID = "notification_channel";
  public static final String DIRECT_REPLY_BUTTON_KEY = "direct_reply_button_key";
  private final Context mContext;
  private NotificationManager mNotificationManager;
  public NotificationWrapper(Context context) {
    mContext = context;
    mNotificationManager = (NotificationManager) mContext
        .getSystemService(mContext.NOTIFICATION_SERVICE);
  }
  private void createNotificationChannel() {
    if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
      NotificationChannel channel = new NotificationChannel(NOTIFICATION_CHANNEL_ID, "name",
          NotificationManager.IMPORTANCE_HIGH);
      channel.setDescription("channel description");
      channel.enableLights(true);
      channel.setLightColor(Color.GREEN);
      channel.enableVibration(true);
//      channel.setShowBadge(false);
//      channel.setSound(System.ALARM_ALERT);
      mNotificationManager.createNotificationChannel(channel);
    }
  }
  private Builder createNotification(String title, String text) {
    Builder builder = new Builder(mContext, NOTIFICATION_CHANNEL_ID);
    builder.setSmallIcon(android.R.drawable.sym_action_chat);
    builder.setContentTitle(title);
    builder.setContentText(text);
    builder.setPriority(NotificationCompat.PRIORITY_HIGH);
    builder.setVibrate(new long[]{0, 1000});
    builder.setAutoCancel(true);
    setBadge(builder);
    setContentIntent(builder);
    return builder;
  }
  private void setBadge(Builder builder) {
    builder.setNumber(100);
    builder.setBadgeIconType(NotificationCompat.BADGE_ICON_LARGE);
  }
  @Deprecated
  private void addDirectReplyButton(Builder builder) {
    RemoteInput remoteInput = new RemoteInput.Builder(DIRECT_REPLY_BUTTON_KEY)
        .setLabel("label")
        .build();
    int requestCode = 999;
    Intent actionIntent = new Intent(mContext, MyBroadcastReceiver.class);
    PendingIntent replyPendingIntent = PendingIntent.getBroadcast(mContext,
        requestCode,
        actionIntent,
        PendingIntent.FLAG_UPDATE_CURRENT);
    NotificationCompat.Action action =
        new NotificationCompat.Action.Builder(android.R.drawable.sym_action_chat,
            "direct reply action title", replyPendingIntent)
            .addRemoteInput(remoteInput)
            .build();
    builder.addAction(action);
  }
  private void setContentIntent(Builder builder) {
    Intent actionIntent = new Intent(mContext, NotificationLaunchActivity.class);
    PendingIntent launchNotificationLaunchActivity = PendingIntent
        .getActivity(mContext, 0, actionIntent, PendingIntent.FLAG_UPDATE_CURRENT);
    builder.setContentIntent(launchNotificationLaunchActivity);
  }
  public void showNotification(String title, String text) {
    createNotificationChannel();
    Builder builder = createNotification(title, text);
    mNotificationManager.notify(NOTIFICATION_ID, builder.build());
  }
}

使用 NotificationWrapper來發送通知。

new NotificationWrapper(this).showNotification("this is notification title", "this is notification text");


以上是最基本的通知發送範例,下方則是介紹一些進階功能


進階功能

加入動作按鈕
加入動作按鈕和加入設定通知要啟動的Action類似。也是建立PendingIntent,並在其內加入要啟動的元件,最後透過addAction來加入。
需要注意的是動作按鈕最多只有3個,超過3個會忽略。

  private void addActionButton(Builder builder) {
    Intent actionIntent = new Intent(mContext, NotificationActionActivity.class);
    PendingIntent launchNotificationActionActivity =
        PendingIntent.getActivity(mContext, 0, actionIntent, 0);
    builder.addAction(android.R.drawable.sym_action_call, "Launch", launchNotificationActionActivity);
  }

加入動作按鈕後,點擊該按鈕便會啟動指定的元件(NotificationActionActivity)。

 
後續還有多種通知形式可以使用,如加入直接回應按鈕等等
範例可參考
https://github.com/googlesamples/android-Notifications
以及官方教學
https://developer.android.com/guide/topics/ui/notifiers/notifications?hl=zh-tw
 

分類
Uncategorized

上架 google play(android market) 的準備

1.先來做 icon 吧, Android 支援各種不同的尺寸,可以製作 96×96(x), 72×72(h), 48×48(m), 36×36(l), 4種不同尺寸的 icon 圖, 我使用 firework 來作圖, 先製作 96×96 的 icon , 之後的尺寸再調整就可以了
先製作 icon 底圖, 底圖最好小於該尺寸 10 個單位,以 96×96 為例,下面底圖最好是 86×86

 
 
 
 
接著就根據自己的喜好,加上圖案吧,設計完成後,再調整各個尺寸,如 72×72 的 icon 就設定為 62×62 ,接著到 專案resdrawable-xhdpi , resdrawable-hdpi , resdrawable-mdpi , resdrawable-ldpi, 替換原先的 ic_launcher.png
2.更改應用程式名稱,到 AndroidManifest.xml 中尋找 <application 中的 android : label ,修改其內容
3.製作 apk 檔, 製作 apk 檔之前必須先製作金鑰檔,上架時上傳的 apk 必須先經過金鑰來簽名,製作金鑰檔的方式為
開啟命令提示字元 -> 輸入 keytool ,會顯示一連串可用的命令如下

 
 
 
 
 
 
 
接著輸入

keytool -genkey -v -keystore testkey.store -alias testkey.keystore -keyalg RSA -validity 20000

-genkey 為產生key , testkey.store 為 金鑰檔 -keyalg 為演算法, -validity 為有效日 50年(最好超過)

接下來會出現一些填資料的畫面如下

 
 
 
 
 
 
 
最後會產生 1 個名稱為 testkey.store 的金鑰檔,回到 eclipse,在專案上點選右鍵,

 
 
 
 
 
 
 
 
 
 
在必要的地方填入剛剛設定的密碼

 
 
 
 
 
 
 
 
 
最後選擇位置

 
 
 
 
 
 
 
 
 
製作 apk 檔完成!!!
最後就是登入後台並上傳檔案,當然在後台還有一些資料需要設定,不過大部分都是圖案等等

分類
Uncategorized

如何製作上鎖功能(EditText + SharedPreferences)

上鎖功能相當簡單,基本上只需要 EditText 和 SharedPreferences(也可以使用其它的儲存類別如 fileoutputstream,SQLite) 就能完成,,不過為了操作的方便,我還加上了 button 和 textview ,基本介面如下
開鎖畫面

 
 
 
 
 
 
 
 
 
 
輸入密碼畫面

 
 
 
 
 
 
 
 
 
 
 
而實作原理也相當簡單,為了不把主流程複雜化,我把解鎖(UnLock_Flow.java)和加鎖(Lock_Flow.java)分開成2個類別,首先在主流程開始前先判斷是否有密碼鎖,有就先進解鎖,沒有就直接開始主流程,而在主流程中會有個選項是加入密碼鎖的選擇,點選進入 Lock_Flow.java,相當簡單吧
首先在主流程中判斷的方法如下

      void lock() {
          // get data for accountdetail
          SharedPreferences sp = getSharedPreferences("bcn", 0);
   
          // get accountdetail count
          String passstr = sp.getString("adlock", "");
          // no lock
          if(passstr.equals("")){
          }
          //lock exist
          else {
          //call Lock_Flow
              Intent inte = new Intent();
              inte.setClass(All_List.this, UnLock_Flow.class);
              startActivityForResult(inte, 3);
              overridePendingTransition(R.anim.zoom_enter, R.anim.zoom_exit);
          }
      }

其中 SharedPreferences 的用法請參考這篇,本篇不再多加詳述
第 9 行判斷內容是否為空,是的話代表沒有密碼鎖直接進入主流程,否的話就呼叫解鎖類別(16 ~ 20 行)
解鎖類別(UnLock_Flow.java)如下

  package com.example.helloworld;
   
  import FoXxLib.FP;
  import android.annotation.SuppressLint;
  import android.app.Activity;
  import android.app.AlertDialog;
  import android.content.DialogInterface;
  import android.content.SharedPreferences;
  import android.os.Bundle;
  import android.view.KeyEvent;
  import android.view.View;
  import android.view.View.OnClickListener;
  import android.widget.Button;
  import android.widget.EditText;
  import android.widget.TextView;
  import android.widget.Toast;
   
  @SuppressLint("NewApi")
  public class UnLock_Flow extends Activity {
   
      EditText et;
      Button byes;
      Button bno;
   
      TextView tv;
   
      int tryNum = 2;
   
      public void onCreate(Bundle savedInstanceState) {
          super.onCreate(savedInstanceState);
   
          initXml();
   
      }
   
      void initXml() {
          setContentView(R.layout.unlockflow);
   
          tv = (TextView) findViewById(R.id.textView1);
   
          tv.setText("請輸入密碼解鎖");
   
          et = (EditText) findViewById(R.id.username_edit);
          et.setHint("輸入密碼");
   
          byes = (Button) findViewById(R.id.buttonYes);
          byes.setOnClickListener(new OnClickListener() {
   
              @Override
              public void onClick(View v) {
                  // TODO Auto-generated method stub
   
                  checkPassWord();
              }
          });
   
          bno = (Button) findViewById(R.id.buttonNo);
          bno.setOnClickListener(new OnClickListener() {
   
              @Override
              public void onClick(View v) {
                  // TODO Auto-generated method stub
   
                  // use AlertDialog
                  AlertDialog ad = new AlertDialog.Builder(UnLock_Flow.this,
                          AlertDialog.THEME_TRADITIONAL).create();
                  ad.setTitle("警告!!");// 設定警告標題
                  ad.setMessage("確定離開程序??");
   
                  ad.setButton2("確定", new DialogInterface.OnClickListener() {// 設定按鈕2
   
                              @Override
                              public void onClick(DialogInterface dialog,
                                      int which) {
   
                                  // 點選按鈕2後執行的動作
                                  finishEdit(1);
                              }
                          });
                  ad.setButton("取消", new DialogInterface.OnClickListener() {// 設定按鈕2
   
                              @Override
                              public void onClick(DialogInterface dialog,
                                      int which) {
   
                                  // 點選按鈕2後執行的動作
   
                              }
                          });
   
                  ad.setCanceledOnTouchOutside(true);// 當警告提示出現後,點選提示以外範圍,是否會取消提示,預設是true
   
                  ad.setCancelable(true);// 當警告提示出現後,點選其他實體按鈕(backkey等等),是否會取消提示,預設是true
   
                  ad.show();
   
              }
          });
      }
   
      void checkPassWord() {
   
          if (et.getText() == null) {
              // use Toast
              tryNum--;
              Toast.makeText(UnLock_Flow.this, "未輸入密碼!!!" + "還剩" + tryNum + "次",
                      Toast.LENGTH_SHORT).show();
   
              et.setText("");
          } else {
   
              String tempstr = et.getText().toString();
   
              // get data for accountdetail
              SharedPreferences sp = getSharedPreferences("bcn", 0);
   
              // get accountdetail count
              String passstr = sp.getString("adlock", "");
   
              if (tempstr.equals(passstr)) {
                  Toast.makeText(UnLock_Flow.this, "密碼正確!!!", Toast.LENGTH_SHORT)
                          .show();
                  finishEdit(0);
              } else {
                  Toast.makeText(UnLock_Flow.this,
                          "密碼錯誤!!!" + "還剩" + tryNum + "次", Toast.LENGTH_SHORT)
                          .show();
                  tryNum--;
              }
   
          }
   
          if (tryNum < 0) {
   
              finishEdit(1);
          }
      }
   
      public boolean onKeyDown(int keycode, KeyEvent event) {
   
          FP.p("keycode:" + keycode);
          FP.p("event:" + event.getAction());
   
          finishEdit(1);
   
          switch (keycode) {
   
          // menu key
          case 82:
   
              break;
          }
   
          return super.onKeyDown(keycode, event);
      }
   
      /**
       *
       * @param result
       *            0 ok
       */
      void finishEdit(int result) {
   
          if (result == 0) {
              UnLock_Flow.this.setResult(0);
   
              UnLock_Flow.this.finish();
   
              overridePendingTransition(R.anim.zoom_enter, R.anim.zoom_exit);
          } else if (result == 1) {
   
              UnLock_Flow.this.setResult(1);
   
              UnLock_Flow.this.finish();
   
          }
      }
   
  }

其中比較重要的為 101 行的 checkPassWord() 方法,會取得 在 et 中輸入的字串內容並與 passstr 字串做比較(120行),相同則解鎖成功(finishEdit(0)),失敗則嘗試次數減少,當嘗試次數減為 0 ,那麼會直接回到主程序再結束 (finishEdit(1))

以上是解鎖的部分,接下來是上鎖的部分,在主程序中會有個按鈕,用來觸發加鎖動作,如下

//call Lock_Flow
Intent inte = new Intent();
inte.setClass(All_List.this, Lock_Flow.class);
startActivityForResult(inte, 4);
overridePendingTransition(R.anim.zoom_enter, R.anim.zoom_exit);

接著是 Lock_Flow.java的內容,必須注意在沒有上鎖的情況下,可以直接設定密碼,完成上鎖,但相反的若已經有上鎖過,則必須先輸入原先的密碼以解鎖,再上鎖,如下

  package com.example.helloworld;
   
  import FoXxLib.FP;
  import android.annotation.SuppressLint;
  import android.app.Activity;
  import android.app.AlertDialog;
  import android.content.DialogInterface;
  import android.content.SharedPreferences;
  import android.os.Bundle;
  import android.view.KeyEvent;
  import android.view.View;
  import android.view.View.OnClickListener;
  import android.widget.Button;
  import android.widget.EditText;
  import android.widget.TextView;
  import android.widget.Toast;
   
  @SuppressLint("NewApi")
  public class Lock_Flow extends Activity{
   
      EditText et;
      Button byes;
      Button bno;
      TextView tv;
      int tryNum =2;
      //0:lock exist 1:no lock
      int flow =0;
      public void onCreate(Bundle savedInstanceState){
          super.onCreate(savedInstanceState);
          initXml();
      }
      void initXml(){
          setContentView(R.layout.lockflow);
          tv = (TextView)findViewById(R.id.textView1);
          // get data for accountdetail
          SharedPreferences sp = getSharedPreferences("bcn", 0);
   
          // get accountdetail count
          String passstr = sp.getString("adlock", "");
          //no lock
          if(passstr.equals("")){
              flow =1;
              tv.setText("請輸入密碼:");
          }
          //lock exist
          else{
              flow =0;
              tv.setText("請輸入原始密碼:");
          }
          et = (EditText)findViewById(R.id.username_edit);
          et.setHint("輸入密碼");
          byes = (Button)findViewById(R.id.buttonYes);
          byes.setOnClickListener(new OnClickListener() {
              @Override
              public void onClick(View v) {
                  // TODO Auto-generated method stub
                  checkPassWord();
              }
          });
          bno = (Button)findViewById(R.id.buttonNo);
          bno.setOnClickListener(new OnClickListener() {
              @Override
              public void onClick(View v) {
                  // TODO Auto-generated method stub
                  //use AlertDialog
                  AlertDialog ad = new AlertDialog.Builder(Lock_Flow.this,AlertDialog.THEME_TRADITIONAL).create();
                  ad.setTitle("警告!!");//設定警告標題
                  ad.setMessage("未上鎖,確定返回程序??");
                  ad.setButton2("確定", new DialogInterface.OnClickListener() {// 設定按鈕2
   
                              @Override
                              public void onClick(DialogInterface dialog, int which) {
   
                                  // 點選按鈕2後執行的動作
                                  finishEdit(1);
                              }
                          });
                  ad.setButton("取消", new DialogInterface.OnClickListener() {// 設定按鈕2
   
                      @Override
                      public void onClick(DialogInterface dialog, int which) {
   
                          // 點選按鈕2後執行的動作
                      }
                  });
                  ad.setCanceledOnTouchOutside(true);//當警告提示出現後,點選提示以外範圍,是否會取消提示,預設是true
                  ad.setCancelable(true);//當警告提示出現後,點選其他實體按鈕(backkey等等),是否會取消提示,預設是true
                  ad.show();
              }
          });
      }
      void checkPassWord(){
          if(flow==0){
              if(et.getText().toString().equals("")){
                  // use Toast
                  Toast.makeText(Lock_Flow.this, "未輸入密碼!!!"+"還剩"+tryNum+"次",Toast.LENGTH_SHORT).show();
                  tryNum--;
                  et.setText("");
              }
              else{
                  String tempstr = et.getText().toString();
                  // get data for accountdetail
                  SharedPreferences sp = getSharedPreferences("bcn", 0);
   
                  // get accountdetail count
                  String passstr = sp.getString("adlock","");
                  if(tempstr.equals(passstr)){
                      Toast.makeText(Lock_Flow.this, "密碼正確!!!",Toast.LENGTH_SHORT).show();
                      flow =1;
                      tv.setText("輸入新密碼:");
                  }
                  else{
                      Toast.makeText(Lock_Flow.this, "密碼錯誤!!!"+"還剩"+tryNum+"次",Toast.LENGTH_SHORT).show();
                      tryNum--;
                  }
              }
              if(tryNum<0){
                  //use AlertDialog
                  AlertDialog ad = new AlertDialog.Builder(Lock_Flow.this,AlertDialog.THEME_TRADITIONAL).create();
                  ad.setTitle("更改密碼失敗!!");//設定警告標題
                  ad.setMessage("原始密碼不正確");
                  ad.setButton("返回", new DialogInterface.OnClickListener() {// 設定按鈕2
   
                      @Override
                      public void onClick(DialogInterface dialog, int which) {
                          finishEdit(0);
                      }
                  });
                  ad.setCanceledOnTouchOutside(false);//當警告提示出現後,點選提示以外範圍,是否會取消提示,預設是true
                  ad.setCancelable(false);//當警告提示出現後,點選其他實體按鈕(backkey等等),是否會取消提示,預設是true
                  ad.show();
              }
          }
          else if(flow==1){
              if(et.getText().toString().equals("")){
                  // use Toast
                  Toast.makeText(Lock_Flow.this, "未輸入密碼!!!",Toast.LENGTH_SHORT).show();
                  et.setText("");
              }
              else{
                  String tempstr = et.getText().toString();
                  // get data for accountdetail
                  SharedPreferences sp = getSharedPreferences("bcn", 0);
                  SharedPreferences.Editor spe = sp.edit();
                  spe.putString("adlock", tempstr);// for more putXXX
                  spe.commit();
   
                  //use AlertDialog
                  AlertDialog ad = new AlertDialog.Builder(Lock_Flow.this,AlertDialog.THEME_TRADITIONAL).create();
                  ad.setTitle("上鎖完成!!");//設定警告標題
                  ad.setMessage("密碼為"+tempstr);
                  ad.setButton("返回", new DialogInterface.OnClickListener() {// 設定按鈕2
   
                      @Override
                      public void onClick(DialogInterface dialog, int which) {
                          finishEdit(1);
                      }
                  });
                  ad.setCanceledOnTouchOutside(false);//當警告提示出現後,點選提示以外範圍,是否會取消提示,預設是true
                  ad.setCancelable(false);//當警告提示出現後,點選其他實體按鈕(backkey等等),是否會取消提示,預設是true
                  ad.show();
              }
          }
      }
      public boolean onKeyDown(int keycode, KeyEvent event){
          FP.p("keycode:"+keycode);
          FP.p("event:"+event.getAction());
          finishEdit(1);
          switch(keycode){
          // menu key
          case 82:
              break;
          }
          return super.onKeyDown(keycode, event);
      }
      /**
       *
       * @param result 0 ok
       */
      void finishEdit(int result){
          if(result==0){
              Lock_Flow.this.setResult(0);
              Lock_Flow.this.finish();
              overridePendingTransition(R.anim.zoom_enter, R.anim.zoom_exit);
          }
          else if(result==1){
              Lock_Flow.this.setResult(1);
              Lock_Flow.this.finish();
              overridePendingTransition(R.anim.zoom_enter, R.anim.zoom_exit);
          }
      }
  }

 

第 30 行 flow=0代表已經有鎖, 必須先解鎖,而flow=1,代表沒有鎖,可以直接輸入密碼上鎖
結果為

分類
Uncategorized

排序比較的實用(Comparable & Comparator )

在 Collections 有許多排序比較等等的方法已經寫好,基本類型的比較可以直接使用,如

    ArrayList al = new ArrayList();
    for(int i=0; i<10; i++){
        Random r = new Random();
        al.add(r.nextInt(1000));
    }
    Collections.sort(al);

在 al 中的元素將會由小到大排好,但是如果 al 中的元素並不是基本類型,而是自創類別,而想比較的是自創類別中的某個屬性值,那麼可以實作 Comparable 介面,再覆寫 compareTo 方法,如下

  package com.example.helloworld;
   
  import java.util.Comparator;
   
  public class AccountDetail implements Comparable<Object>{
   
      static int dataCount;
      public String time;
      public String money;
      public String remark;
      public AccountDetail(){
          dataCount++;
          time = "0000/00/00";
          money = "0";
          remark = "0";
      }
      public AccountDetail(String time,String money,String type){
          dataCount++;
          this.time = time;
          this.money = money;
          this.remark = type;
      }
   
      @Override
      public int compareTo(Object another) {
          AccountDetail adAnother = (AccountDetail)another;
          if(Integer.valueOf(this.money)>Integer.valueOf(adAnother.money))
          {
              return 1;// move element back
          }
          else if(Integer.valueOf(this.money)<Integer.valueOf(adAnother.money)){
              return -1;//move element forward
          }
          else{
              return 0;
          }
      }
  }
   

AccountDetail 裡面有 3 個屬性,分別為 time , money , remark , 在第 5 行實作 Comparable 介面, 接著第 27 行覆寫 compareTo 方法, 方法裡面就寫出自訂的比較規則, 只要注意回傳值,1 代表往前排, -1 代表往後排,這段程式碼就代表比較 money 並由小排到大,最後使用的方式如下

          ArrayList<AccountDetail> al = new ArrayList<AccountDetail>();
                
               for(int i=0; i<10 ;i++){
              Random r = new Random();
              al.add(new AccountDetail("testtime",""+r.nextInt(1000)+i,"testremark"));
          }
          Collections.sort(al);

 

第 1 ~ 6 行建立 ArrayList al, 最後第 8 行排序,就這麼簡單,不過使用 Comparable 介面的缺點是只能使用單種排序,如果還想針對 time , remark 做排序,可以使用 Comparator 介面,好處除了可對多種數值做排序外,也不必綁定在 AccountDetail 類別中,你可以把它封裝出來(不過我還是寫在一起)

  package com.example.helloworld;
  import java.util.Comparator;
  import FoXxLib.FP;
  public class AccountDetail
  {
      static int dataCount;
      public String time;
      public String money;
      public String remark;
      public AccountDetail(){
          dataCount++;
          time = "0000/00/00";
          money = "0";
          remark = "0";
      }
      public AccountDetail(String time,String money,String type){
          dataCount++;
          this.time = time;
          this.money = money;
          this.remark = type;
      }
      public MoneyComparator moneyComp(){
          MoneyComparator mc = new MoneyComparator();
          return mc;
      }
      public TimeComparator timeComp(){
          TimeComparator tc = new TimeComparator();
          return tc;
      }
  }
  class MoneyComparator implements Comparator<Object>{
      @Override
      public int compare(Object lhs, Object rhs) {
          AccountDetail ad1 = (AccountDetail)lhs;
          AccountDetail ad2 = (AccountDetail)rhs;
          if(Integer.valueOf(ad1.money)>Integer.valueOf(ad2.money)){
              return 1;
          }
          else if(Integer.valueOf(ad1.money)<Integer.valueOf(ad2.money)){
              return -1;
          }
          else{
              return 0;
          }
      }
  }
  class TimeComparator implements Comparator<Object>{
      @Override
      public int compare(Object lhs, Object rhs) {
          int tempReturn =0;
          AccountDetail ad1 = (AccountDetail)lhs;
          AccountDetail ad2 = (AccountDetail)rhs;
          String[] tempstr1 = ad1.time.split("[/]");
          int[] tempint1 = new int[tempstr1.length];
          for(int i=0; i<tempstr1.length; i++){
              tempint1[i] = Integer.valueOf(tempstr1[i]);
          }
          String[] tempstr2 = ad2.time.split("[/]");
          int[] tempint2 = new int[tempstr2.length];
          for(int i=0; i<tempstr2.length; i++){
              tempint2[i] = Integer.valueOf(tempstr2[i]);
          }
          for(int i=0; i<tempint1.length; i++){
              if(tempint1[i]>tempint2[i]){
                  tempReturn = -1;
                  break;
              }
              else if(tempint1[i]<tempint2[i]){
                  tempReturn = 1;
                  break;
              }
              else if(tempint1[i]==tempint2[i])
              {
                  tempReturn =0;
              }
          }
              return tempReturn;
      }
  }

 

先把原先使用 Comparator 介面 的部分註解掉(第 8 行, 第 30 ~ 49 行)
第 51 ~ 56 行建立並回傳 money 的比較器
第 58 ~ 63 行建立並回傳 time 的比較器
第 66 ~ 84 行為 money 比較器的定義, 先實作 Comparator 介面
第 69 行覆寫 compare 方法, lhs 和 rhs 各為比較的物件,至於 compare 的內容就自己定義了
第 86 ~ 129 行為 time 比較器的定義,由於 time 包含了年月日,必須先切割字串,再放入陣列中等待比較,第 109 ~ 123 行為比較的方法
最後使用的方式如下

Collections.sort(al, al.get(0).timeComp());

第 1 個參數為比較的內容,第 2 個參數為比較器, timeComp 會回傳 time 比較器,當然你也可以改為 al.get(0).moneyComp() , 來比較 money
注意事項:
1.如果使用 Comparator 介面,且和要比較的容器類別綁在一起(就是上述的 AccountDetail.java),必須注意當沒有產生任何元素,還要排序時,會出現錯誤,所以排序類別還是拆開比較好如下

 package com.example.helloworld;
  import java.util.Comparator;
  public abstract class AD_Sort implements Comparator<Object>{
  }
  class MoneyComparator extends AD_Sort{
      @Override
      public int compare(Object lhs, Object rhs) {
          AccountDetail ad1 = (AccountDetail)lhs;
          AccountDetail ad2 = (AccountDetail)rhs;
          if(Integer.valueOf(ad1.money)>Integer.valueOf(ad2.money)){
              return -1;
          }
          else if(Integer.valueOf(ad1.money)<Integer.valueOf(ad2.money)){
              return 1;
          }
          else{
              return 0;
          }
      }
  }
  class TimeComparator extends AD_Sort{
      @Override
      public int compare(Object lhs, Object rhs) {
          int tempReturn =0;
          AccountDetail ad1 = (AccountDetail)lhs;
          AccountDetail ad2 = (AccountDetail)rhs;
          String[] tempstr1 = ad1.time.split("[/]");
          int[] tempint1 = new int[tempstr1.length];
          for(int i=0; i<tempstr1.length; i++){
              tempint1[i] = Integer.valueOf(tempstr1[i]);
          }
          String[] tempstr2 = ad2.time.split("[/]");
          int[] tempint2 = new int[tempstr2.length];
          for(int i=0; i<tempstr2.length; i++){
              tempint2[i] = Integer.valueOf(tempstr2[i]);
          }
          for(int i=0; i<tempint1.length; i++){
              if(tempint1[i]>tempint2[i]){
                  tempReturn = -1;
                  break;
              }
              else if(tempint1[i]<tempint2[i]){
                  tempReturn = 1;
                  break;
              }
              else if(tempint1[i]==tempint2[i])
              {
                  tempReturn =0;
              }
          }
              return tempReturn;
      }
  }

然後使用的方式為

          AD_Sort ads;
          ads = new MoneyComparator();
          Collections.sort(al, ads);