今天要來撰寫的部分是關於Android 之中的檔案輸出

檔案輸出顧名思義就是利用手機App來把文字/音樂/影片等檔案放到手機的內建儲存的方法

比方說以前我的網誌有更新過PDF以及CSV的方法

在這

->碼農日常-『Android studio』如何以Android手機製作CSV檔案

->碼農日常-『Android studio』如何在Android手機以JAVA撰寫中文PDF檔案並分享輸出

 

其實在這兩篇當中,我已經使用了今天的相關技術了

但是當時文章的重點是放在輸出PDF及CSV,而非"檔案輸出"本身

因此今天文章的重點就是檔案輸出本身以及附帶取得在Android中動態取得權限的方法(-‿◦)

 

上示範(・ωー)~☆

 

Github..今天程式太少我懶得PO โ๏∀๏ใ

有需要的話請留言給我~

 


 

1. 設置AndroidManifest.xml相關權限

 

首先請先找到AndroidManifest.xml這個檔案

截圖 2020-10-24 下午4.51.05

 

再來請輸入以下粉底白字以及綠底白字的內容

 

<?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

截圖 2020-10-24 下午5.01.26

 

 

 

至於綠底白字的部分是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

找到他

截圖 2020-10-24 下午5.32.02

按~下去

再加入以下內容

/**回傳使用者所做的權限選擇(接受/拒絕)*/
@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

 

便完成詢問權限的操作摟!(;´д`)ゞ

Screenshot_1603521846

 


 

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就如我所說的,真的很多雜七雜八的細節,實在是很煩...

不過秉持著關關難過關關過的道理,總算是把坑給填了

 

 

 

 

最後~

TK

 

 

 

arrow
arrow

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