今天來寫些關於輸出CSV檔案的內容吧(・ω・)b
CSV檔案,全名為Comma-Separated Values
中文名稱為"逗號分隔檔"
其實我覺得大多數人對這個檔案的認知應該是
"喔,這就是那個Excal的檔案啊"
"就是輸出Excal檔案嘛"
...這個還算是有認知的,有些人連這是什麼都不知道(´・_・`)
其實把它說成是輸出成Excal檔案也沒有錯,因為Excal本身就是一個"逗號分隔檔的讀取器"
逗號分隔檔顧名思義,就是指說這種檔案他會輸出成一格字串
而中間每個資料就是以逗號分開
但是請別忘了一件事,Excel是可以編輯的,像是設定某格內的字體置中或是粗體
但是CSV檔案並沒有編輯,如果把檔案編輯了,那輸出的檔案就是Excel的xls檔案了!
好的,那在科普一下CSV檔案的用途_(:3」∠)_
一般來說CSV檔案除了輸出給一般Excal閱讀或是給numbers讀取之外
有很多研究單位會在電腦製作可讀取CSV檔案的應用程式,並將之轉換為圖表或曲線
其實網路一找應該不少..但是我用得不多我就不特別提供了zz
那介紹到這,接下來就是要來實作製作CSV的程式了
首先是功能
輸出的csv檔案
以及眾所期待的GitHub
->https://github.com/thumbb13555/CSVExample
好的就開始吧(・ωー)~☆
1.建立基礎配置
1-1.建立畫面
首先先建立畫面
這次也沒有特別做哪些東西,就是一個按鈕...
直接給你主畫面吧(´υ`)
<?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" />
1-2.加入權限
再來是加入按鈕功能以及加入寫出檔案的權限
要把資料寫出到外部SD卡或是內部儲存空間的話,必須在AndroidManifest.xml內加入相關權限
首先找到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就會向你要求權限囉~(•ө•)♡
2.撰寫內容
接著就是要撰寫內容了,在這邊再次強調CSV檔案是
逗號分隔檔 (・∀・)(・∀・)(・∀・)
所以我們的目標就是要製作字串的方形矩陣
哇賽聽起來真專業R (´・_・`)
其實也沒有多厲害,就是要使程式輸出成以下這個樣式
...就這樣而已_(: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畫面中看到這個字串囉
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
注意這行
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的全部
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
我待的公司是做傳感器的,所以什麼數據傳送啊還是該會的報表總是少不了
今天的內容應該不算很難吧!哈哈
我也期許你們會在這篇文章中收穫到你們需要的東西
那我們下篇文章見!
留言列表