[Android] pdf open with instantly(immediatly) close error + Failed to find configured root that contains /data~

2021. 4. 14. 16:11모바일/Android_Java

pdf open with instantly(immediatly) close error + Failed to find configured root that contains /data~

pdf open 기존 방식

        Intent intent = new Intent(android.content.Intent.ACTION_VIEW);
        Uri path = Uri.fromFile(new File(FileUtil.makeDir(mFileDir) + mFileName));
//        intent.addCategory(Intent.CATEGORY_DEFAULT);
        intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
        intent.setDataAndType(path, "application/pdf");
        if (canUseExternalMemory()) {
            try {
                //파일명이 Pdf라면 인텐트를 날린다.
                if (FileUtil.getFileFind(mFileName).compareTo("pdf") == 0) {
//                    ((Activity) context).startActivity(intent);
                    ((Activity) context).startActivityForResult(intent, 20201);
                }
            } catch (Exception e) {
                //Exception 에러가 나면은 마켓에서 다운받을지 동의의사를 물어본다.
                AlertUtil.Alert(context, "알림", "PDF뷰어 관련 어플리케이션이 없습니다. 마켓에서 PDF뷰어를 다운받으시겠습니까?",
                        new DialogInterface.OnClickListener() {

                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                // TODO Auto-generated method stub
                                ((Activity) context).startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(context.getResources().getString(R.string.config_ThinkFreeMarketUrl))));
                            }
                        });
            }
        }
    public static String makeDir(String dir) {
        String rootPath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/" + dir;

        try {
            File file = new File(rootPath);
            if (!file.exists()) {
                if (!file.mkdirs()) {
                }
            }
        } catch (Exception e) {
            rootPath = "-1";
        }
        return rootPath + "/";
    }

이렇게

Uri.fromFile(new File(FileUtil.makeDir(mFileDir) + mFileName));

코드로 임시 다운받은 pdf 를 열었더니 google pdf 뷰어 or 드라이브 pdf 는 바로 꺼지는 현상이 발생했다

원인은 아직 파악중이지만 FileProvider를 통해 해결했다.

수정(원인 - 추측)

파일 선택에 응답
사용자가 공유 파일을 선택하면 애플리케이션에서는 선택된 파일을 확인한 후 파일의 콘텐츠 URI를 생성해야 합니다. Activity는 사용 가능한 파일 목록을 ListView에 표시하므로 사용자가 파일 이름을 클릭하면 시스템에서는 onItemClick() 메서드를 호출합니다. 이를 통해 선택된 파일을 가져올 수 있습니다.

인텐트를 사용하여 한 앱에서 다른 앱으로 파일의 URI를 전송할 때 다른 앱에서 읽을 수 있는 URI를 가져오도록 주의해야 합니다. Android 6.0(API 수준 23) 이상을 실행 중인 기기에서 이렇게 하려면 특별한 주의가 필요합니다. 이 Android 버전의 권한 모델이 변경되어 특히 READ_EXTERNAL_STORAGE가 수신 앱에는 없을 수 있는 위험한 권한이 되기 때문입니다.

이러한 사항을 고려할 때 몇 가지 단점이 있는 Uri.fromFile()은 사용하지 않는 것이 좋습니다. 이 메서드의 특성은 다음과 같습니다.

프로필 간 파일 공유를 허용하지 않습니다.
Android 4.4(API 수준 19) 이하를 실행 중인 기기에서는 앱에 WRITE_EXTERNAL_STORAGE 권한이 있어야 합니다.
수신 앱에 READ_EXTERNAL_STORAGE 권한이 있어야 합니다. 이 권한은 이 권한이 없는 Gmail과 같은 중요 공유 타겟에서는 실패합니다.
Uri.fromFile()을 사용하는 대신 URI 권한을 사용하여 다른 앱에 특정 URI에 액세스할 수 있는 권한을 부여할 수 있습니다. URI 권한은 Uri.fromFile()에서 생성한 file:// URI에서는 작동하지 않지만 콘텐츠 제공업체에 연결된 URI에서는 작동합니다. FileProvider API는 이러한 URI를 만드는 데 도움이 될 수 있습니다. 또한 이 방법은 외부 저장소가 아니라 인텐트를 전송하는 앱의 로컬 저장소에 있는 파일에도 사용할 수 있습니
출처 : https://developer.android.com/training/secure-file-sharing/share-file?hl=ko

새로운 구현 방법

위에 코드를

        Uri path = FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID+".fileprovider",new File(FileUtil.makeDir(mFileDir) + mFileName));

로 변경하고 manifest.xml에 provider를 추가한다

        <provider
            android:name="android.support.v4.content.FileProvider"
            android:authorities="kr.co.koreastock.mts.android.fileprovider"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/file_paths" />

        </provider>

Drawable/xml에 file_paths.xml을 추가한다.

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<!--    <root-path-->
<!--        name="name"-->
<!--        path="."/>-->

    <external-path
        name="name"
        path="."/>
</paths>

paths태그 안의 태그명은 아래와 같다

  • <files-path/> --> Context.getFilesDir()
  • <cache-path/> --> Context.getCacheDir()
  • <external-path/> --> Environment.getExternalStorageDirectory()
  • <external-files-path/> --> Context.getExternalFilesDir(String)
  • <external-cache-path/> --> Context.getExternalCacheDir()
  • <external-media-path/> --> Context.getExternalMediaDirs()

Failed to find configured root that contains /data~ 이 에러는 대신 태그로 선언했더니 발생했던 에러임

어떤 글에는 <root-path 태그로도 된다고 해서 해보니까 저것도 정상적으로 열린다.

출처 : https://stackoverflow.com/questions/42516126/fileprovider-illegalargumentexception-failed-to-find-configured-root

참고) 파일 다운코드

            DownloadFile down = new DownloadFile();
            down.fileName(mFileName);
            down.execute(mUrlName);


    private class DownloadFile extends AsyncTask<String, Integer, String> {
        private String fileName = "";
        long total = 0;

        public String fileName(String fileName) {
            return this.fileName = fileName;
        }

        @Override
        protected String doInBackground(String... paramUrl) {

            HttpURLConnection conn = null;

            try {
                URL url = new URL(paramUrl[0]);
                conn = (HttpURLConnection) url.openConnection();
                conn.setRequestMethod("GET");
                conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
                conn.setRequestProperty("Content-Language", "EUC-KR");
                conn.setDoOutput(true);
                conn.setUseCaches(false);

                conn.connect();

                String filePath = FileUtil.makeDir(mFileDir);
                File file = new File(filePath, fileName);

                if (file.exists()) {
                    file.delete();
                } else {
                    file.createNewFile();
                }

                InputStream is = conn.getInputStream();
                BufferedInputStream bis = new BufferedInputStream(is);

                OutputStream os = new FileOutputStream(file, false);
                BufferedOutputStream bos = new BufferedOutputStream(os);

                int tmp = 0;
                int MAX_BUFFER_LENGTH = 5120;
                byte data[] = new byte[MAX_BUFFER_LENGTH];

                //progress
                //그냥 의미없이 진행 상태 보여주기
                int prog = 0;

//                Log.e("bis.available()", String.valueOf(bis.available()));

                while ((tmp = bis.read(data)) != -1) {
                    bos.write(data, 0, tmp);
                    total += tmp;

                    prog++;
                    publishProgress((int) ((prog % 2000) * 100 / 2000));
                }
                //100%
                publishProgress(100);

                bos.flush();
                os.flush();

                try {
                    bos.close();
                } catch (Exception eee) {
                }
                try {
                    os.close();
                } catch (Exception eee) {
                }

                try {
                    bis.close();
                } catch (Exception eee) {
                }
                try {
                    is.close();
                } catch (Exception eee) {
                }

            } catch (Exception e) {
                if (mProgressDialog.isShowing()) {
                    mProgressDialog.dismiss();
                }
                mHandler.sendEmptyMessage(NETWORKFAIL);
//                Log.e("Exception", e.getMessage());
                e.getStackTrace();
                return "fail";
            } finally {
                if (conn != null) {
                    conn.disconnect();
                }
            }

            return Integer.toString(DOWNLOADSUECCS);
        }

        @Override
        protected void onPreExecute() {
            super.onPreExecute();
        }

        @Override
        public void onProgressUpdate(Integer... args) {
            mProgressDialog.setProgress(args[0]);
        }

        /**
         * 백그라운드 작업이 완료시 수행
         *
         * @param result
         */
        @Override
        protected void onPostExecute(String result) {
            if (result.compareTo(Integer.toString(DOWNLOADSUECCS)) == 0) {
                if (mProgressDialog.isShowing()) {
                    mProgressDialog.dismiss();
                }
                mHandler.sendEmptyMessage(DOWNLOADSUECCS);
            }
        }

        /**
         * 비정상종료
         */
        @Override
        protected void onCancelled() {
            super.onCancelled();
            mProgressDialog.dismiss();
        }
    }