2014年8月11日 星期一

[Android] 解決NetworkOnMainThreadException問題

雖然網路上文章滿多的,但還是紀錄一下以免忘記。

會有這個問題產生主要是在Android 4.0版本以上才會出現,確切原因不清楚,但應該是增加安全

性理由所以才修改成這樣的(?)

這問題就是如同字面上意思:

你在主線程上面呼叫Network類相關API

這時候該如何解決呢??

1)使用分流法(開個新的Thread)來操作網路,但如果我就是要調整並顯示UI介面呢?

2)在setContentView(R.layout.activity_main)下面新增
    if (android.os.Build.VERSION.SDK_INT > 9) {
   StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();
   StrictMode.setThreadPolicy(policy);
       }

即可解決該問題

[Android/Java] Thread(執行緒)的使用+UDP Send / Receive+Socket

在使用Thread的時候,必須要先知道執行緒(Thread)是甚麼?

否則只是看看範例,卻只得其一不得其二。

執行緒是一種可以在程式背景執行的一個Process(程序),同時可以有多個執行緒一起執行。

由於可以多線程,因此大大增加可用性。

Thread的結構大概就是

public class ChatServer extends Thread{

private DatagramSocket server = null;
private static final int PORT = 4000;
        //宣告變數

public ChatServer() throws IOException {
server = new DatagramSocket(PORT);
Log.d("User","new server socket");
}
        //建構子

public void run(){
byte[] byte100 = new byte[100];
DatagramPacket dPacket = new DatagramPacket(byte100, 100);
String txt;
try{
Log.d("User","runing run()");
while(true){
server.receive(dPacket);
Log.d("User","While 1");
while(true)
{
//印出來到螢幕上
Log.d("User","While 2");
if(true) break;
}
//CloseSocket(client);//關閉
}
}
catch(IOException e)
{}
}
        //主要在跑的地方(主程式)

在呼叫Thread的時候,以上面範例為例,我們呼叫時使用:  

       chatserver = new ChatServer();
       new這個class,他會跑進建構子裡面,但還不會進去run()函式
       chatserver.start();
       執行run()這個函式,由於run裡面有while()迴圈,所以會一直跑不停

       (在這兩行外面要夾try catch否則會有error)

-----------------------------------------------------------------------------------------------------------
上面解釋完Thread了,下面要講的是如何在Android上面使用執行緒的概念做UDP的發送與接收,

但這目前有個問題就是模擬器(emulator)與電腦的連線還沒有試做成功,在Emulator-Emulator是可

以成功傳送-接收的。

傳送部分:

目標:寫一個Thread,讓他不停傳送給指定IP+指定Port

內容:

public class ChatSender extends Thread{

public void run(){
DatagramSocket socket = null;
try
{
socket = new DatagramSocket();
InetAddress serverAddress = InetAddress.getByName("127.0.0.1");
Log.d("IP Address", serverAddress.toString());
String str = "Test Thread sender";
               //創建一個用於發送的DatagramPacket對象
               DatagramPacket packet=new DatagramPacket(str.getBytes(),str.length(),serverAddress,4000);
            //發送數據
            while(true)
            {
            socket.send(packet);
            Thread.sleep(1000);
            }
}
catch(SocketException e)
{
e.printStackTrace();
String error = e.toString();
Log.e("Error by Sender", error);
}
catch(UnknownHostException e)
{
e.printStackTrace();
String error = e.toString();
Log.e("Error by Sender", error);
}
catch(IOException e)
{
e.printStackTrace();
String error = e.toString();
Log.e("Error by Sender", error);
}
catch(Exception e)
{
e.printStackTrace();
String error = e.toString();
Log.e("Error by Sender", error);
}
finally{
if(socket != null){
socket.close();
}

}
}
}

大概可以看到DatagramSocket+DatagramPacket這兩個API,

DatagramSocket我的理解是用於Socket連線,而DatagramPacket則是負責傳送的封包內容。

*DatagramPacket(封包的byte[]陣列,封包的長度,IP位址,Port); 

至於InetAddress的部分是為了要確定本機IP是多少。


接收部分:

目標:寫一個Thread,讓他不停到指定的Port接收封包,直到有接收到才印出來,之後繼續hold住等待封包傳送過來。

內容:

public class ChatServer extends Thread{

private DatagramSocket server = null;
private static final int PORT = 4000;

public ChatServer() throws IOException {
server = new DatagramSocket(PORT);
Log.d("User","new server socket");
}
public void run(){
byte[] byte100 = new byte[100];
DatagramPacket dPacket = new DatagramPacket(byte100, 100);
String txt;
try{
Log.d("User","runing run()");
while(true){
server.receive(dPacket);
Log.d("User","While 1");
while(true)
{
//印出來到螢幕上
Log.d("User","While 2");
if(true) break;
}
//CloseSocket(client);//關閉
}
}
catch(IOException e)
{}
}

private void CloseSocket(DatagramSocket socket) throws IOException{
socket.close();
}
}

UDP接收的部分同樣也需要DatagramSocket/DatagramPacket,不過剛剛是Send 現在則是receive

為了存放接收的資料,前面會先new一個byte[]陣列用以存放資料。

在server.receive(dPacket);這邊如果沒有資料進來的話,他不會往下跑,會一直等待在這邊,直到接

收到資料才會執行下一行。這是必須特別注意的!


MainActivity的部分:

public class MainActivity extends ActionBarActivity {

private Handler exHandler;
private TextView txv;
private ChatServer chatserver;
private ChatSender chatsender;
public static InetAddress IP;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
txv = (TextView) findViewById(R.id.textView);
try{
//IP = InetAddress.getLocalHost();
chatserver = new ChatServer();
chatserver.start();
chatsender = new ChatSender();
chatsender.start();
Log.d("User","Thread start...");
}catch(Exception e)
{
String str = e.toString();
Log.e("Error by User", str);
}
}
protected void onPause(){
super.onPause();
if(chatserver != null)
{
if(!chatserver.isInterrupted())
{
chatserver.interrupt();
}
}
}
} 

然後要記得AndroidManifest.xml這邊要增加INTERNET的Uses-Permission

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.thread_example"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="19" />
    <uses-permission android:name="android.permission.INTERNET"/>

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name="com.example.thread_example.MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>


結論: UDP 傳送及接收其實不容易,看似簡單但很多細節如果沒有注意到就會失敗!



2014年7月15日 星期二

[Android] Eclipse好用快捷鍵

在使用Eclipse時候常常很苦惱,因為不是很好用(沒背快捷鍵)

這邊記錄一下,好用的

1.Ctrl +D  = 整行刪除

2.圈起來 + Ctrl+Shift+f  = 該區域自動排版

3.圈起來 + Ctrl+/ = 該區域註解掉

4.Ctrl+Shift+O = 自動import所需的類別

大概就這些,背起來會很有用喔!

2014年7月10日 星期四

[Android] UDP Socket Emulator to PC Sender / UDP Socket 使用Android 模擬器傳送給PC端 實作

歷經千辛萬苦,終於把這個東西給搞定了....一部分(接收的部分還不確定)
首先進去命令提示字元(按開始->輸入cmd)

輸入"telnet localhost 5554"(這邊要特別注意因為android emulator是5554所以才用telnet登入5554)

進入畫面如上,記得模擬器要開起來,否則Telnet會無法連線!

接著我們要重新定址(增加Port) 

輸入指令redir add udp:5000:4000(這兩個數字可以任意變換, Local端:Guest端)

測試程式,隨意輸入文字,可以看到另一邊有收到(但是是ASCII碼)

如此就完成初步了,當然!!!!最重要的是程式碼,否則即使開啟了模擬器的port也無法傳送。



 package com.example.sendudp;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.UnknownHostException;

import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v7.app.ActionBarActivity;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;

public class MainActivity extends ActionBarActivity implements OnClickListener {

Button Sender;
TextView err;
TextView check_send_data;
EditText input_str;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Sender = (Button) findViewById(R.id.sender);
err = (TextView) findViewById(R.id.error_txv);
check_send_data = (TextView) findViewById(R.id.send_txv);
input_str = (EditText) findViewById(R.id.edt);
Sender.setOnClickListener(this);
}

@Override
public boolean onCreateOptionsMenu(Menu menu) {

// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}

/**
* A placeholder fragment containing a simple view.
*/
public static class PlaceholderFragment extends Fragment {

public PlaceholderFragment() {
}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_main, container,
false);
return rootView;
}
}

@Override
public void onClick(View arg0) {
// TODO Auto-generated method stub
DatagramSocket socket = null;
try
{
socket = new DatagramSocket();
InetAddress serverAddress = InetAddress.getByName("10.192.172.130");
String str = input_str.getText().toString();
            //創建一個用於發送的DatagramPacket對象 
            DatagramPacket packet=new DatagramPacket(str.getBytes(),str.length(),serverAddress,4000); 
            //發送數據
            //socket.connect(serverAddress, 6000);
            socket.send(packet);
            check_send_data.setText(str);//顯示傳送的文字
}
catch(SocketException e)
{
e.printStackTrace();
String error = e.toString();
err.setText(error);
}
catch(UnknownHostException e)
{
e.printStackTrace();
String error = e.toString();
err.setText(error);
}
catch(IOException e)
{
e.printStackTrace();
String error = e.toString();
err.setText(error);
}
catch(Exception e)
{
e.printStackTrace();
String error = e.toString();
err.setText(error);
}
finally{
if(socket != null){
socket.close();
}

}
}

}


然後一定會有人發現,即使如此還是沒法傳送,會出現Permission denied的exception

因此我們需要在bin/res/AndroidManifest.xml這邊修改,修改如下
記得要存檔,才會改變設定。

把權限開了,程式碼也沒有問題,Port也有設定好,大致上Android UDP Socket就算完成了。

不過因為我還沒寫接收的部分,所以接收的部分要在等寫完才能有教學

2014年6月25日 星期三

[C#/winform] MSChart使用實測+功能介紹

關於這個Microsoft Chart Control,網路上真的資料不多(不然就是簡體居多),有鑒於此,

這邊研究了很久以後...終於了解了一個大概,這邊寫一下心得跟使用說明。
























可以看到Series就代表一條線(或是一組線,或數據),你可以透過Series調整他的各項功能

ChartArea: 這線所畫的區域

ChartType: 要怎樣的圖形表示呢? 折線/直條/橫條...等

像我這這邊加入了4個Series,因為我要做四條線(但下面範例我只有設定2條而已)



這邊可以看到Legends代表上面那個線條註釋,而底下如果太多數據要顯示(內建會變成一個顯示一個不顯示)就要用程式碼修改為不重疊(交叉顯示)。

以下範例是參考 SUEHILARY的學習手記 修改而成的:

------------------------------------------------------------------------------------------------------------

            DateTime a = DateTime.Now;
            DateTime b = DateTime.Now;

            List<String> xValue = new List<string>();

            xValue.Add(a.ToString());
            xValue.Add(b.ToString());

            string[] titleArr = { "活動1", "活動2" };
            double[] yValues = { 269.23, 94 };
            double[] yValues2 = { 120.14, 116 };

            Chart1.Legends.Add("Legends1"); //圖例集合

            //設定 Chart
            Title title = new Title();
            title.Text = "統計圖";
            title.Alignment = ContentAlignment.MiddleCenter;
            title.Font = new System.Drawing.Font("Trebuchet MS", 14F, FontStyle.Bold);
            Chart1.Titles.Add(title);

            //設定 ChartArea----------------------------------------------------------------------
         
            //-----------------------------設定3D------------------------------//
            Chart1.ChartAreas["ChartArea1"].Area3DStyle.Enable3D = true; //3D效果
            Chart1.ChartAreas["ChartArea1"].Area3DStyle.IsClustered = true; //並排顯示
            Chart1.ChartAreas["ChartArea1"].Area3DStyle.Rotation = 40; //垂直角度
            Chart1.ChartAreas["ChartArea1"].Area3DStyle.Inclination = 50; //水平角度
            Chart1.ChartAreas["ChartArea1"].Area3DStyle.PointDepth = 10; //數據條厚度
            Chart1.ChartAreas["ChartArea1"].Area3DStyle.WallWidth = 0; //外牆寬度
            Chart1.ChartAreas["ChartArea1"].Area3DStyle.LightStyle = LightStyle.Realistic; //光源
            //-----------------------------設定3D------------------------------//

            Chart1.ChartAreas["ChartArea1"].BackColor = Color.FromArgb(240, 240, 240); //背景色
            Chart1.ChartAreas["ChartArea1"].AxisX.Enabled = AxisEnabled.True;
            Chart1.ChartAreas["ChartArea1"].AxisX2.Enabled = AxisEnabled.False; //隱藏 X2 標示
            Chart1.ChartAreas["ChartArea1"].AxisY2.Enabled = AxisEnabled.False; //隱藏 Y2 標示
            Chart1.ChartAreas["ChartArea1"].AxisY2.MajorGrid.Enabled = false;   //隱藏 Y2 軸線
            Chart1.ChartAreas["ChartArea1"].AxisX.MajorGrid.LineColor = Color.FromArgb(150, 150, 150);//X 軸線顏色
            Chart1.ChartAreas["ChartArea1"].AxisY.MajorGrid.LineColor = Color.FromArgb(150, 150, 150);//Y 軸線顏色


            Chart1.ChartAreas["ChartArea1"].AxisY.LabelStyle.Format = "#.###";//設定小數點

            //設定 Legends------------------------------------------------------------------------              
            Chart1.Legends["Legends1"].DockedToChartArea = "ChartArea1"; //顯示在圖表內
            //Chart1.Legends["Legends1"].Docking = Docking.Bottom; //自訂顯示位置
            Chart1.Legends["Legends1"].BackColor = Color.FromArgb(235, 235, 235); //背景色
            //斜線背景
            Chart1.Legends["Legends1"].BackHatchStyle = ChartHatchStyle.DarkDownwardDiagonal;
            Chart1.Legends["Legends1"].BorderWidth = 1;
            Chart1.Legends["Legends1"].BorderColor = Color.FromArgb(200, 200, 200);

            //設定 Series-----------------------------------------------------------------------
            //Chart1.Series["Series1"].ChartType = SeriesChartType.Line; //直條圖(Column),折線圖(Line),橫條圖(Bar)
            //Chart1.Series["Series2"].ChartType = SeriesChartType.Line; //直條圖(Column),折線圖(Line),橫條圖(Bar)
            //Chart1.Series["Series1"].ChartType = SeriesChartType.Bar; //橫條圖

            Chart1.Series["Series1"].Points.DataBindXY(xValue, yValues);//Series1的XY數值放入圖中

            Chart1.Series["Series1"].Legend = "Legends1";
            Chart1.Series["Series1"].LegendText = titleArr[0];
            Chart1.Series["Series1"].LabelFormat = "#.###"; //小數點
            Chart1.Series["Series1"].MarkerSize = 8; //Label 範圍大小
            Chart1.Series["Series1"].LabelForeColor = Color.FromArgb(0, 90, 255); //字體顏色
            //字體設定
            Chart1.Series["Series1"].Font = new System.Drawing.Font("Trebuchet MS", 10, System.Drawing.FontStyle.Bold);
            //Label 背景色
            Chart1.Series["Series1"].LabelBackColor = Color.FromArgb(150, 255, 255, 255);
            Chart1.Series["Series1"].Color = Color.FromArgb(240, 65, 140, 240); //背景色
            Chart1.Series["Series1"].IsValueShownAsLabel = true; // Show data points labels

            Chart1.Series["Series2"].Points.DataBindXY(xValue, yValues2);

            Chart1.Series["Series2"].Legend = "Legends1";
            Chart1.Series["Series2"].LegendText = titleArr[1];
            Chart1.Series["Series2"].LabelFormat = "#.###"; //小數點
            Chart1.Series["Series2"].MarkerSize = 8; //Label 範圍大小
            Chart1.Series["Series2"].LabelForeColor = Color.FromArgb(255, 103, 0);
            Chart1.Series["Series2"].Font = new System.Drawing.Font("Trebuchet MS", 10, FontStyle.Bold);
            Chart1.Series["Series2"].LabelBackColor = Color.FromArgb(150, 255, 255, 255);
            Chart1.Series["Series2"].Color = Color.FromArgb(240, 252, 180, 65); //背景色
            Chart1.Series["Series2"].IsValueShownAsLabel = true; //顯示數據

            Chart1.Series["Series3"].Color = Color.FromArgb(22, 24, 18, 65); //背景色

            Chart1.ChartAreas[0].AxisX.Interval = 1;   //設置X軸坐標的間隔為1
            Chart1.ChartAreas[0].AxisX.IntervalOffset = 1;  //設置X軸坐標偏移為1
            Chart1.ChartAreas[0].AxisX.LabelStyle.IsStaggered = true;   //設置是否交錯顯示,比如數據多的時間分成兩行來顯示

------------------------------------------------------------------------------------------------------------

原本應該是要先宣告

Chart Chart1 = new Chart();

但我們已經拉了一個元件叫Chart1因此就不用再new(再new就會重複,造成錯誤)

而Series1,2也是因為在winform當中有設定過因此無須重複設定(一樣造成錯誤)

3D的部分,因人而異,但個人是建議

            Chart1.ChartAreas["ChartArea1"].Area3DStyle.IsClustered = true; //並排顯示
            Chart1.ChartAreas["ChartArea1"].Area3DStyle.Rotation = 40; //垂直角度
            Chart1.ChartAreas["ChartArea1"].Area3DStyle.Inclination = 50; //水平角度
            Chart1.ChartAreas["ChartArea1"].Area3DStyle.PointDepth = 10; //數據條厚度
            Chart1.ChartAreas["ChartArea1"].Area3DStyle.WallWidth = 0; //外牆寬度
            Chart1.ChartAreas["ChartArea1"].Area3DStyle.LightStyle = LightStyle.Realistic; //光源

這樣的設定比較好看,當然也可以改為不要並排顯示,一切取決於你的程式/數據多寡

背景色的部分應該無須多講解吧?

線條顏色修改的部分在Chart1.Series["Series"].Color = Color.FromArgb(自行設定色彩);

然後要修改Legend的文字則是在Chart1.Series["Series"].LegendText = "要顯示的文字";

目前遇到的問題跟解法都寫在這邊了,剩下的部分就等寫完再補一篇新的。

End