今天來寫些關於輸出CSV檔案的內容吧(・ω・)b

CSV檔案,全名為Comma-Separated Values

中文名稱為"逗號分隔檔"

其實我覺得大多數人對這個檔案的認知應該是

 

"喔,這就是那個Excal的檔案啊"

"就是輸出Excal檔案嘛"

 

...這個還算是有認知的,有些人連這是什麼都不知道(´・_・`)

其實把它說成是輸出成Excal檔案也沒有錯,因為Excal本身就是一個"逗號分隔檔的讀取器"

逗號分隔檔顧名思義,就是指說這種檔案他會輸出成一格字串

而中間每個資料就是以逗號分開

 

但是請別忘了一件事,Excel是可以編輯的,像是設定某格內的字體置中或是粗體

但是CSV檔案並沒有編輯,如果把檔案編輯了,那輸出的檔案就是Excel的xls檔案了!

 

好的,那在科普一下CSV檔案的用途_(:3」∠)_

一般來說CSV檔案除了輸出給一般Excal閱讀或是給numbers讀取之外

有很多研究單位會在電腦製作可讀取CSV檔案的應用程式,並將之轉換為圖表或曲線

其實網路一找應該不少..但是我用得不多我就不特別提供了zz

那介紹到這,接下來就是要來實作製作CSV的程式了

首先是功能

正常輸出

 

輸出的csv檔案

Screenshot_20200516-160928_Excel

以及眾所期待的GitHub

->https://github.com/thumbb13555/CSVExample

好的就開始吧(・ωー)~☆


 

1.建立基礎配置

1-1.建立畫面

首先先建立畫面

這次也沒有特別做哪些東西,就是一個按鈕...

直接給你主畫面吧(´υ`)

 

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/button"
        android:layout_width="250dp"
        android:layout_height="250dp"
        android:text="輸出CSV"
        android:textSize="36sp"
        android:textStyle="bold"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

 

截圖 2020-05-16 下午5.48.23

 

1-2.加入權限

再來是加入按鈕功能以及加入寫出檔案的權限

要把資料寫出到外部SD卡或是內部儲存空間的話,必須在AndroidManifest.xml內加入相關權限

首先找到AndroidManifest.xml

截圖 2020-05-16 下午5.58.39

再來加入權限

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.jetec.csvexample">
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:requestLegacyExternalStorage="true"
        android:theme="@style/AppTheme">
        <!--Android10 必須要加入android:requestLegacyExternalStorage="true"這一行-->
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

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

</manifest>

 

1-3.向使用者索取存取權限

接下來來到MainActivity.java

這邊我們要跟使用者要求寫出到外部儲存裝置的權限(・`ω´・)

剛才在AndroidManifast只是讓這隻App能夠寫檔案到外部而已

在Android5之後,Google為了加強資安,因此每個APP還要跟使用者要求權限才有用。

另外我就順便連按鈕功能的點擊也PO出來囉~

public class MainActivity extends AppCompatActivity {
    public static final String TAG = MainActivity.class.getSimpleName() + "My";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        /**取得讀寫權限*/
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
                && checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)
                != PackageManager.PERMISSION_GRANTED) {
            requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1000);
        }
        Button button = findViewById(R.id.button);
        button.setOnClickListener(v -> {
            /**製作CSV*/
        });
    }//onCreate
}

 

這時候按下執行,APP就會向你要求權限囉~(•ө•)♡

Screenshot_20200516-171512_Permission controller


 

2.撰寫內容

接著就是要撰寫內容了,在這邊再次強調CSV檔案是

逗號分隔檔 (・∀・)(・∀・)(・∀・)

所以我們的目標就是要製作字串的方形矩陣

 

哇賽聽起來真專業R (´・_・`)

其實也沒有多厲害,就是要使程式輸出成以下這個樣式

截圖 2020-05-16 下午4.09.06

...就這樣而已_(:3」∠)_

到這邊,大家可以先試著思考一下這樣的字串要怎麼做

動動腦才不會癡呆哦XDD

 

3

2

1

 

好來PO程式吧

private void makeCSV() {
    new Thread(() -> {
        /**決定檔案名稱*/
        String date = new SimpleDateFormat("yyyy-MM-dd",
                Locale.getDefault()).format(System.currentTimeMillis());
        String fileName = "[" + date + "]碼農日常輸出的CSV.csv";
        /**撰寫內容*/
        //以下用詞:直行橫列
        //設置第一列的內容
        String[] title ={"Id","Chinese","English","Math","Physical"};
        StringBuffer csvText = new StringBuffer();
        for (int i = 0; i < title.length; i++) {
            csvText.append(title[i]+",");
        }
        //設置其餘內容,共15行
        for (int i = 0; i < 15; i++) {
            csvText.append("\n" + (i+1));
            //此處巢狀迴圈為設置每一列的內容
            for (int j = 1; j < title.length; j++) {
                int random = new Random().nextInt(80) + 20;
                csvText.append(","+random);
            }
        }
        Log.d(TAG, "makeCSV: \n"+csvText);//可在此監視輸出的內容
    }).start();
}//makeCSV

 

關於Thread(),雖然輸出CSV不是什麼耗時工作,但是我還是習慣將它寫在子執行緒中,以保證畫面順暢(・ω・)b

這時候執行下去,就可以在LogCat畫面中看到這個字串囉

截圖 2020-05-16 下午4.09.06


 

3.輸出檔案

接著是輸出檔案的部分

我個人是已經寫好了模組了XD

直接給你吧~

PS.因為這部分是跟畫面互動有關的部分,要記得寫在runOnUiThread內哦

Log.d(TAG, "makeCSV: \n"+csvText);//可在此監視輸出的內容
runOnUiThread(() -> {
    try {
        FileOutputStream out = openFileOutput(fileName, Context.MODE_PRIVATE);
        out.write((csvText.toString().getBytes()));
        out.close();
        File fileLocation = new File(Environment.
                getExternalStorageDirectory().getAbsolutePath(), fileName);
        FileOutputStream fos = new FileOutputStream(fileLocation);
        fos.write(csvText.toString().getBytes());
        Uri path = Uri.fromFile(fileLocation);
        Intent fileIntent = new Intent(Intent.ACTION_SEND);
        fileIntent.setType("text/csv");
        fileIntent.putExtra(Intent.EXTRA_SUBJECT, fileName);
        fileIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
        fileIntent.putExtra(Intent.EXTRA_STREAM, path);
        startActivity(Intent.createChooser(fileIntent, "輸出檔案"));
    } catch (IOException e) {
        e.printStackTrace();
        Log.w(TAG, "makeCSV: "+e.toString());
    }
});

 

好喔~接下來按下執行

閃退

 

凸(`0´)凸凸(`0´)凸閃退...

這時候看一下LogCat

截圖 2020-05-16 下午4.16.11

注意這行

android.os.FileUriExposedException:file:///storage/emulated/0/xxx exposed beyond app throughClipData.Item.getUri()

什麼鬼啊...(╯=▃=)╯︵┻━┻

 

原因是因為,從Android 7.0開始,一個應用提供自身檔案給其它應用使用時,如果給出一個file://格式的URI的話,應用會丟擲FileUriExposedException

這是由於谷歌認為目標app可能不具有檔案許可權,會造成潛在的問題。所以讓這一行為快速失敗

 

好啦有問題就是要解決,我的做法是加入以下程式

runOnUiThread(() -> {
    try {
        //->遇上exposed beyond app through ClipData.Item.getUri() 錯誤時在onCreate加上這行
        StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder();
        StrictMode.setVmPolicy(builder.build());
        builder.detectFileUriExposure();
        //->遇上exposed beyond app through ClipData.Item.getUri() 錯誤時在onCreate加上這行
        FileOutputStream out = openFileOutput(fileName, Context.MODE_PRIVATE);
        out.write((csvText.toString().getBytes()));
        out.close();
        File fileLocation = new File(Environment.
                getExternalStorageDirectory().getAbsolutePath(), fileName);
        FileOutputStream fos = new FileOutputStream(fileLocation);
        fos.write(csvText.toString().getBytes());
        Uri path = Uri.fromFile(fileLocation);
        Intent fileIntent = new Intent(Intent.ACTION_SEND);
        fileIntent.setType("text/csv");
        fileIntent.putExtra(Intent.EXTRA_SUBJECT, fileName);
        fileIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
        fileIntent.putExtra(Intent.EXTRA_STREAM, path);
        startActivity(Intent.createChooser(fileIntent, "輸出檔案"));
    } catch (IOException e) {
        e.printStackTrace();
        Log.w(TAG, "makeCSV: "+e.toString());
    }
});

 

就成功解決囉~

 

這時候執行下去,就可以看到你所希望的畫面摟

正常輸出

 

最後再補上MainActivity的全部

MainAvtivity.java

public class MainActivity extends AppCompatActivity {
    public static final String TAG = MainActivity.class.getSimpleName()+"My";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        /**取得讀寫權限*/
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)
                != PackageManager.PERMISSION_GRANTED) {
            requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1000);
        }
        Button button = findViewById(R.id.button);
        button.setOnClickListener(v -> {
            /**製作CSV*/
            makeCSV();
        });
    }//onCreate
    private void makeCSV() {
        new Thread(() -> {
            /**決定檔案名稱*/
            String date = new SimpleDateFormat("yyyy-MM-dd",
                    Locale.getDefault()).format(System.currentTimeMillis());
            String fileName = "[" + date + "]碼農日常輸出的CSV.csv";
            /**撰寫內容*/
            //以下用詞:直行橫列
            //設置第一列的內容
            String[] title ={"Id","Chinese","English","Math","Physical"};
            StringBuffer csvText = new StringBuffer();
            for (int i = 0; i < title.length; i++) {
                csvText.append(title[i]+",");
            }
            //設置其餘內容,共15行
            for (int i = 0; i < 15; i++) {
                csvText.append("\n" + (i+1));
                //此處巢狀迴圈為設置每一列的內容
                for (int j = 1; j < title.length; j++) {
                    int random = new Random().nextInt(80) + 20;
                    csvText.append(","+random);
                }
            }
            Log.d(TAG, "makeCSV: \n"+csvText);//可在此監視輸出的內容
            runOnUiThread(() -> {
                try {
                    //->遇上exposed beyond app through ClipData.Item.getUri() 錯誤時在onCreate加上這行
                    StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder();
                    StrictMode.setVmPolicy(builder.build());
                    builder.detectFileUriExposure();
                    //->遇上exposed beyond app through ClipData.Item.getUri() 錯誤時在onCreate加上這行
                    FileOutputStream out = openFileOutput(fileName, Context.MODE_PRIVATE);
                    out.write((csvText.toString().getBytes()));
                    out.close();
                    File fileLocation = new File(Environment.
                            getExternalStorageDirectory().getAbsolutePath(), fileName);
                    FileOutputStream fos = new FileOutputStream(fileLocation);
                    fos.write(csvText.toString().getBytes());
                    Uri path = Uri.fromFile(fileLocation);
                    Intent fileIntent = new Intent(Intent.ACTION_SEND);
                    fileIntent.setType("text/csv");
                    fileIntent.putExtra(Intent.EXTRA_SUBJECT, fileName);
                    fileIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
                    fileIntent.putExtra(Intent.EXTRA_STREAM, path);
                    startActivity(Intent.createChooser(fileIntent, "輸出檔案"));
                } catch (IOException e) {
                    e.printStackTrace();
                    Log.w(TAG, "makeCSV: "+e.toString());
                }
            });
        }).start();
    }//makeCSV
}

 


 

結語

我會認識製作CSV的原因其實也是公司要求XD

我待的公司是做傳感器的,所以什麼數據傳送啊還是該會的報表總是少不了

今天的內容應該不算很難吧!哈哈

我也期許你們會在這篇文章中收穫到你們需要的東西

 

那我們下篇文章見!

thank-you-furry-much-memes

 

arrow
arrow

    碼農日常 發表在 痞客邦 留言(2) 人氣()