今天要來撰寫的部分是關於Android 之中的檔案輸出
檔案輸出顧名思義就是利用手機App來把文字/音樂/影片等檔案放到手機的內建儲存的方法
比方說以前我的網誌有更新過PDF以及CSV的方法
在這
->碼農日常-『Android studio』如何以Android手機製作CSV檔案
->碼農日常-『Android studio』如何在Android手機以JAVA撰寫中文PDF檔案並分享輸出
其實在這兩篇當中,我已經使用了今天的相關技術了
但是當時文章的重點是放在輸出PDF及CSV,而非"檔案輸出"本身
因此今天文章的重點就是檔案輸出本身以及附帶取得在Android中動態取得權限的方法(-‿◦)
上示範(・ωー)~☆
Github..今天程式太少我懶得PO โ๏∀๏ใ
有需要的話請留言給我~
1. 設置AndroidManifest.xml相關權限
首先請先找到AndroidManifest.xml這個檔案
再來請輸入以下粉底白字以及綠底白字的內容
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" package="com.jetec.fileexportdemo"> <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:theme="@style/AppTheme" android:requestLegacyExternalStorage="true" tools:targetApi="q"> <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>
粉底白字的部分是設定本APP需要的相關權限
像今天所寫的
這句話就是指需要有將檔案輸出到指定資料夾之權限
其他還有相當多的權限
去找吧,Google把所有權限都放在那裡了
->https://developer.android.com/guide/topics/security/permissions?hl=zh-cn#perm-groups
至於綠底白字的部分是Android 10 之後所新增的
在Android 10 要撰寫檔案輸出的時候必須要加這行允許他輸出,才會確實輸出喔!(・ω・)b
OS: Android 10 毛真的很多..像之前的Wifi功能..((遠目
->碼農日常-『Android studio』Android Wifi 搜尋、連線與斷線機制實作(下)
2. 邀請權限與相關操作
設置完權限還不夠,第一段的內容只是在跟Android系統呈報說你這個APP有需要哪些權限
而你在實際操作的時候你還是得再向你的用戶報告說"我要需要你給我這個權限哦!"
用比喻來講,就是說
假設你的餐廳沒有主動為客人端茶水的服務
然後今天你想要為客人新增一個端茶水的服務
你必須要先通報你的上級,讓他們決定要不要讓你運行這個服務
等許可後,你就可以執行端茶水的服務了
但是真正等客人上門後,你還是要跟他說"請讓我為你端茶水喔!"
當然...客人也可以很ㄟˇㄒㄧㄠˊ的拒絕啦(詢問權限是可以拒絕的)
不過我的意義在於說,這就像是你要先請示過主管,再請示客人一樣
而先前的AndroidManifest.xml就是請示主管
而現在要完成的事項就是請示客人
相關介紹Google官方也有寫
->https://developer.android.com/guide/topics/permissions/overview?hl=zh-cn#dangerous-permission-prompt
我先PO全部,再回頭解釋
public class MainActivity extends AppCompatActivity { private boolean isPermissionPassed = false; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); getPermission(); } /** * 取得將檔案寫入手機的權限 */ private void getPermission() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions( this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 100); } else { isPermissionPassed = true; } } /** * 回傳使用者所做的權限選擇(接受/拒絕) */ @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (requestCode == 100) { if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { /**如果用戶同意*/ isPermissionPassed = true; } else { /**如果用戶不同意*/ if (ActivityCompat.shouldShowRequestPermissionRationale(this , Manifest.permission.WRITE_EXTERNAL_STORAGE)) { Toast.makeText(this, "你搞毛啊!", Toast.LENGTH_SHORT).show(); getPermission(); } } } }//onRequestPermissionsResult }
Okay,那一部份一部份講
首先是getPermission()這部分
/**取得將檔案寫入手機的權限*/ private void getPermission() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions( this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 100); } else { isPermissionPassed = true; } }
這部分就是判斷這個APP之前是否有取得權限了?
如果沒有就進入Else
最後的100是識別碼,你可以隨意給;但是識別碼會作為回傳值之辨識之用,稍後在回傳的部分就會知道囉:D
至於米白底紫字的部分則是要受判斷的權限
Write_external_storage就是將資料寫出到外部儲存的意思
其他權限有不同的寫法~摸索看看囉 :))
2-2 取得使用者的選擇
接著是回傳使用者的選擇
首先加入複寫onRequestPermissionsResult
請按下control+O
找到他
按~下去
再加入以下內容
/**回傳使用者所做的權限選擇(接受/拒絕)*/ @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (requestCode == 100) { if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { /**如果用戶同意*/ isPermissionPassed = true; } else { /**如果用戶不同意*/ if (ActivityCompat.shouldShowRequestPermissionRationale(this , Manifest.permission.WRITE_EXTERNAL_STORAGE)) { Toast.makeText(this, "你搞毛啊!", Toast.LENGTH_SHORT).show(); getPermission(); } } } }//onRequestPermissionsResult
便完成詢問權限的操作摟!(;´д`)ゞ
3. 輸出檔案
輸出檔案的話有幾個要點需注意
首先是路徑,一般來說Android系統中在公開資料夾內都可以使用"絕對路徑"
在這個路徑底下的檔案都是可以被Android主系統本身公開搜尋到的
他會放在你手機的檔案總管(三星叫做我的檔案)的內部儲存空間裡面
還有一種則是私有路徑,這種路徑底下的檔案是不會被搜尋到的;而且他會隨著你刪APP後消失
私有路徑的寫法我以後再為他寫一篇文章,我們先來處理公開路徑
公開路徑來說,他的路徑名稱叫做"/storage/emulated/0"
而後面你想加什麼就可以加什麼
像是今天我輸出的話路徑就會是"/storage/emulated/0/BlogExport/碼農日常檔案輸出.txt"
但是要記得!!如果你的路徑中存在你手機內原本沒有的路徑,輸出就會失敗喔!
比方說,我如果直接輸出檔案到"/storage/emulated/0/BlogExport/碼農日常檔案輸出.txt"
但是我實際上手機並沒有Blogexport這個資料夾
那程式就會報錯並輸出失敗
那我們就來看看程式吧
/**輸出檔案*/ private void exportFile() { if (!isPermissionPassed) { Toast.makeText(this, "你沒有權限!!", Toast.LENGTH_SHORT).show(); return; } /**決定檔案名稱*/ String fileName = "碼農日常檔案輸出"; /**決定檔案被存放的路徑*/ // "/storage/emulated/0/BlogExport" String absoluteFilePath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/BlogExport"; StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder(); StrictMode.setVmPolicy(builder.build()); builder.detectFileUriExposure(); try { /**新增BlogExport的資料夾*/ File file = new File(absoluteFilePath); if (file.mkdir()) { System.out.println("新增資料夾"); } else { System.out.println("資料夾已存在"); } /*檔案輸出-> /storage/emulated/0/BlogExport/碼農日常檔案輸出.txt*/ File fileLocation = new File(absoluteFilePath + "/" + fileName + ".txt"); /**撰寫檔案內容*/ FileOutputStream fos = new FileOutputStream(fileLocation); fos.write("寫點東西吧".getBytes()); fos.close(); /**將檔案分享出去的視窗*/ Uri path = Uri.fromFile(fileLocation); Intent fileIntent = new Intent(Intent.ACTION_SEND); fileIntent.setType("text/txt"); 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(); } }
首先是一開始
if (!isPermissionPassed) { Toast.makeText(this, "你沒有權限!!", Toast.LENGTH_SHORT).show(); return; }
的部分
這部分是我設置的權限邀請的最後一道防線
為了怕有人給我按下拒絕且不再顯示,然後又再ㄎ ㄅ 說為什麼不讓他使用服務所設置的
再來往下看
程式中紫底白字的下面幾行可以的看到,我先給了一個資料夾的路輕,並讓程式去試探我有沒有創過這個資料夾
確認好路徑後,便是製作檔案及撰寫檔案內容
其實到了fos.close的那行整個輸出就結束了,不過下面還有一部分是我加開檔案分享的功能
使已經被輸出的檔案,可以再經由Email、Line等手段被傳送出去
可以為使用者在分享檔案的事上輕鬆很多(・ωー)~☆
最後是所有程式,一共118行,請笑納!
public class MainActivity extends AppCompatActivity { private boolean isPermissionPassed = false; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); getPermission(); Button btExport = findViewById(R.id.button_Export); btExport.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { /**輸出檔案*/ exportFile(); } }); } /**取得將檔案寫入手機的權限*/ private void getPermission() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions( this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 100); } else { isPermissionPassed = true; } } /**回傳使用者所做的權限選擇(接受/拒絕)*/ @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (requestCode == 100) { if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { /**如果用戶同意*/ isPermissionPassed = true; } else { /**如果用戶不同意*/ if (ActivityCompat.shouldShowRequestPermissionRationale(this , Manifest.permission.WRITE_EXTERNAL_STORAGE)) { Toast.makeText(this, "你搞毛啊!", Toast.LENGTH_SHORT).show(); getPermission(); } } } }//onRequestPermissionsResult /**輸出檔案*/ private void exportFile() { if (!isPermissionPassed) { Toast.makeText(this, "你沒有權限!!", Toast.LENGTH_SHORT).show(); return; } /**決定檔案名稱*/ String fileName = "碼農日常檔案輸出"; /**決定檔案被存放的路徑*/ // "/storage/emulated/0/BlogExport" String absoluteFilePath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/BlogExport"; StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder(); StrictMode.setVmPolicy(builder.build()); builder.detectFileUriExposure(); try { /**新增BlogExport的資料夾*/ File file = new File(absoluteFilePath); if (file.mkdir()) { System.out.println("新增資料夾"); } else { System.out.println("資料夾已存在"); } /*檔案輸出-> /storage/emulated/0/BlogExport/碼農日常檔案輸出.txt*/ File fileLocation = new File(absoluteFilePath + "/" + fileName + ".txt"); /**撰寫檔案內容*/ FileOutputStream fos = new FileOutputStream(fileLocation); fos.write("寫點東西吧".getBytes()); fos.close(); /**將檔案分享出去的視窗*/ Uri path = Uri.fromFile(fileLocation); Intent fileIntent = new Intent(Intent.ACTION_SEND); fileIntent.setType("text/txt"); 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(); } } }
結語
這個項目當時我也是花了一番功夫完成的(´∀`)
雖然沒什麼坑,但是我前面有標示的幾個地方都是我在寫的時候遇到的一些問題,然後經多方查詢資料才寫出來的
Android9→10就如我所說的,真的很多雜七雜八的細節,實在是很煩...
不過秉持著關關難過關關過的道理,總算是把坑給填了
最後~