Android 系統(tǒng)Intent發(fā)郵件豆瘫,添加附件,autoLink動作攔截

干貨提要菊值,涉及到的技術(shù):
1外驱、調(diào)用系統(tǒng)郵件:Intent.ACTION_SENDTO
2、提供分享 FileProvider腻窒,解決android N之后發(fā)送附件的問題昵宇,解決android.os.FileUriExposedException
3、附件寫入郵件
4儿子、autoLink動作攔截

最近遇到一個需求瓦哎,想要把APP中各處反饋的郵箱后面加上我們收集到的日志信息作為附件,客戶反饋情況時方便直接定位問題柔逼。

我一看這需求蒋譬,很簡單嘛,半小時搞定(其中包括休息的一刻鐘)愉适,然后CTRL + H搜了一下“outlook.com”犯助,分析了APP中所有涉及到郵箱的調(diào)用之后,覺得問題貌似變復雜了一點维咸。

第一種:TextView布局文件autoLink方式
由于APP中調(diào)用郵箱的地方有些代碼沒有要求剂买,直接在xml中引入資源,文本上會顯示劃線的郵箱癌蓖∷埠撸控件不用實例化,系統(tǒng)就自帶了點擊事件響應實現(xiàn)费坊。

代碼:第一種方式倒槐,最簡單
string資源network_issue_explain中包含郵箱,TextView識別到之后響應點擊事件附井,跳轉(zhuǎn)系統(tǒng)郵箱

<TextView
        android:id="@+id/send_email"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="10dp"
        android:textColor="@color/colorMyGrayer"
        android:textSize="14sp"
        android:autoLink="email"
        android:text="@string/network_issue_explain" />

但是這種方式怎么帶附件呢讨越?我們就需要攔截autoLink動作了两残。

// 實例化控件TextView
TextView textView = findViewById(R.id.send_text);
CharSequence text = textView.getText();
if (text instanceof Spannable) {
            int end = text.length();
            Spannable sp = (Spannable) text;
            URLSpan[] urls = sp.getSpans(0, end, URLSpan.class);
            SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder(text);
            spannableStringBuilder.clearSpans();
            for (URLSpan urlSpan : urls) {
                //攔截點擊
                InterceptUrlSpan interceptUrlSpan = new InterceptUrlSpan(urlSpan.getURL());
                spannableStringBuilder.setSpan(interceptUrlSpan, sp.getSpanStart(urlSpan), sp.getSpanEnd(urlSpan), Spannable.SPAN_EXCLUSIVE_INCLUSIVE);
            }
            textView.setText(spannableStringBuilder);
        } else {
            SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder(text);
            spannableStringBuilder.clearSpans();
            textView.setText(spannableStringBuilder);
        }

代碼: 自定義的InterceptUrlSpan 中拿到url,再去點擊回調(diào)事件中對行為判斷把跨,執(zhí)行自己希望的動作

private class InterceptUrlSpan extends ClickableSpan {
        String url;

        public InterceptUrlSpan(String url) {
            this.url = url;
            Log.i("InterceptUrlSpan", "InterceptUrlSpan " + url);
        }

        @Override
        public void onClick(@NonNull View widget) {
            Log.i("InterceptUrlSpan", "InterceptUrlSpan 被點擊了");   
            // 拿到連接了人弓,在點擊回調(diào)中可以為所欲為了!
            // mailto:xxx@outlook.com
            if(url != null && url.startsWith("mailto:")) {
                String emailAddress = url.replace("mailto:", "").trim();
                // 攔截
                Log.i("InterceptUrlSpan", "攔截郵件跳轉(zhuǎn)着逐,主動跳轉(zhuǎn): " + emailAddress);
                // 沒有附件
                composeEmail();
                //有附件的情況(方法在下面)
                //composeEmailWithAttach 
            }
        }

        @Override
        public void updateDrawState(TextPaint ds) {
            //自定義顏色和下劃線
            ds.setColor(Color.BLUE);
            ds.setUnderlineText(true);
        }
    }

第二種:按鈕自實現(xiàn)方式
簡單的按鈕(或者文本崔赌、圖片),用戶點擊之后代碼實現(xiàn)Intent調(diào)用系統(tǒng)郵箱耸别,注意:帶附件和不帶附件方法不同健芭。
在Android中,調(diào)用Email有三種類型的Intent:
* Intent.ACTION_SENDTO 無附件的發(fā)送
* Intent.ACTION_SEND 帶附件的發(fā)送
* Intent.ACTION_SEND_MULTIPLE 帶有多附件的發(fā)送

image.png

好了秀姐,上代碼

    <Button
        android:id="@+id/send_email_btn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="10dp"
        android:text="郵件反饋"/>

點擊響應的代碼(沒有附件)

    /**
     * 直接發(fā)送郵件
     * @param activity 發(fā)起的Activity
     * @param addresses 發(fā)送地址
     * @throws Exception 任意異常
     */
    private static void composeEmail(Activity activity, String[] addresses) throws Exception {
        String subject = "主題:反饋信息 版本:" + BuildConfig.VERSION_NAME;
        String body = "\n\n\n Any Append Info";// Any Append Info 一般用于攜帶設備信息慈迈,說明信息等

        Intent intent = new Intent(Intent.ACTION_SENDTO);
        intent.setData(Uri.parse("mailto:")); // only email apps should handle this
        intent.putExtra(Intent.EXTRA_EMAIL, addresses);
        intent.putExtra(Intent.EXTRA_SUBJECT, subject);
        intent.putExtra(Intent.EXTRA_TEXT, body);
        activity.startActivity(intent);
    }

    /**
     * 帶附件發(fā)送郵件
     * @param activity 發(fā)起的Activity
     * @param addresses 發(fā)送地址
     * @throws Exception 任意異常
     */
   private static void composeEmailWithAttach(Activity activity, String[] addresses) throws Exception {
        String subject = "主題:反饋信息 版本:" + BuildConfig.VERSION_NAME;
        String body = "\n\n\n Any Append Info"; // Any Append Info 一般用于攜帶設備信息,說明信息等

        Intent intent = new Intent(Intent.ACTION_SEND);
        intent.setData(Uri.parse("mailto:")); // only email apps should handle this
        intent.putExtra(Intent.EXTRA_EMAIL, addresses);
        intent.putExtra(Intent.EXTRA_SUBJECT, subject);
        intent.putExtra(Intent.EXTRA_TEXT, body);
        intent.setType("text/plain");

        // 壓縮附件文件夾
        if( ContextCompat.checkSelfPermission(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE)
                == PackageManager.PERMISSION_GRANTED) {
            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss", Locale.getDefault());
            String targetZip = new File(getLogDir(activity)).getParent() + "/logs_" + dateFormat.format(new Date()) + ".zip";

            ZipFolder(getLogDir(activity), targetZip);

            Uri contentUri;
            File tmpFile = new File(targetZip);
            if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                // 授予目錄臨時共享權(quán)限
                intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
                contentUri = FileProvider.getUriForFile(activity, BuildConfig.APPLICATION_ID + ".fileprovider", tmpFile);
                // 找到指定的APP臨時授權(quán)訪問文件
                List<ResolveInfo> resInfoList = activity.getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
                for (ResolveInfo resolveInfo : resInfoList) {
                    String packageName = resolveInfo.activityInfo.packageName;
                    activity.grantUriPermission(packageName, contentUri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
                }
            } else {
                //  安卓N以后其他APP不能直接拿到當前文件
                contentUri = Uri.fromFile(tmpFile);
                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            }
            intent.putExtra(Intent.EXTRA_STREAM, contentUri);
            activity.startActivity(Intent.createChooser(intent, ""));
        }
    }

    /**
     * 返回APP緩存日志路徑
     * @param activity
     * @return
     */
    public static String getLogDir(Activity activity) {
        return activity.getCacheDir() + "/log";
    }

    /**
     * 壓縮文件和文件夾
     * @param srcFileString 要壓縮的文件或文件夾
     * @param zipFileString 壓縮完成的Zip路徑
     * @throws Exception
     */
    public static void ZipFolder(String srcFileString, String zipFileString) throws Exception {
        File zipFile = new File(zipFileString);
        if(!zipFile.exists()) {
            zipFile.createNewFile();
        }
        //創(chuàng)建ZIP
        ZipOutputStream outZip = new ZipOutputStream(new FileOutputStream(zipFileString));
        //創(chuàng)建文件
        File file = new File(srcFileString);
        //壓縮
        Log.i("ZipFolder", "---->" + file.getParent() + "===" + file.getAbsolutePath());
        ZipFiles(file.getParent() + File.separator, file.getName(), outZip);
        //完成和關(guān)閉
        outZip.finish();
        outZip.close();
    }


    /**
     * 壓縮文件
     * @param folderString
     * @param fileString
     * @param zipOutputSteam
     * @throws Exception
     */
    private static void ZipFiles(String folderString, String fileString, ZipOutputStream zipOutputSteam) throws Exception {
        Log.i("ZipFolder", "folderString:" + folderString + "\nfileString:" + fileString);
        if (zipOutputSteam == null)
            return;
        File file = new File(folderString + fileString);
        if (file.isFile()) {
            ZipEntry zipEntry = new ZipEntry(fileString);
            FileInputStream inputStream = new FileInputStream(file);
            zipOutputSteam.putNextEntry(zipEntry);
            int len;
            byte[] buffer = new byte[8192];
            while ((len = inputStream.read(buffer)) != -1) {
                zipOutputSteam.write(buffer, 0, len);
            }
            zipOutputSteam.closeEntry();
        } else {
            //文件夾
            String fileList[] = file.list();
            //沒有子文件和壓縮
            if (fileList.length <= 0) {
                ZipEntry zipEntry = new ZipEntry(fileString + File.separator);
                zipOutputSteam.putNextEntry(zipEntry);
                zipOutputSteam.closeEntry();
            }
            //子文件和遞歸
            for (int i = 0; i < fileList.length; i++) {
                ZipFiles(folderString+fileString+"/",  fileList[i], zipOutputSteam);
            }
        }
    }

最后省有,經(jīng)過多次測試痒留,由于安卓系統(tǒng)和用戶環(huán)境實在是太復雜,還需要一種保底的方式蠢沿,直接讀取附件內(nèi)容伸头,直接寫入郵件正文,就是這么簡單粗暴舷蟀。

    /**
     * 發(fā)送郵件 附件作為正文
     * @param activity 發(fā)起的Activity
     * @param addresses 發(fā)送地址
     * @throws Exception 任意異常
     */
    private static void composeEmailAttachInContent(Activity activity, String[] addresses) throws Exception {
        String subject = "主題:反饋信息 版本:" + BuildConfig.VERSION_NAME;
        String body = "\n\n\n Any Append Info"; // Any Append Info 一般用于攜帶設備信息恤磷,說明信息等

        Intent intent = new Intent(Intent.ACTION_SENDTO);
        intent.setData(Uri.parse("mailto:"));
        intent.putExtra(Intent.EXTRA_EMAIL, addresses);
        intent.putExtra(Intent.EXTRA_SUBJECT, subject);

        File dir = new File(getLogDir(activity));
        if(dir.isDirectory() && dir.listFiles().length > 0) {
            List<File> fileList = Arrays.asList(dir.listFiles());
            Collections.sort(fileList);
            Collections.reverse(fileList);
            StringBuffer sBuffer = new StringBuffer();
            sBuffer.append("\n\n\n>>Important! The following error logs can help developers locate the problem\n\n");

            int max_count = fileList.size() > 6 ? 6 : fileList.size();
            int count = 0;
            for(File file : fileList) {
                if(!file.exists() || file.isDirectory() || file.length() == 0) {
                    continue;
                }

                String fileName = file.getName();
                if(fileName.length() < 11 || !fileName.substring(fileName.length() - 10).toLowerCase().equals("_crash.txt")) {
                    continue;// 不符合日志文件名格式
                }

                sBuffer.append("--------" + fileName + "--------");
                sBuffer.append(readFileContent(file.getAbsolutePath()));
                sBuffer.append("\n\n");

                count ++;
                if(count >= max_count) {
                    break;
                }
            }
            body = sBuffer.toString();
        }
        intent.putExtra(Intent.EXTRA_TEXT, body);
        activity.startActivity(intent);
    }

    public static String readFileContent(String fileName) {
        File file = new File(fileName);
        BufferedReader reader = null;
        StringBuffer sbf = new StringBuffer();
        try {
            reader = new BufferedReader(new FileReader(file));
            String tempStr;
            while ((tempStr = reader.readLine()) != null) {
                sbf.append(tempStr);
            }
            reader.close();
            return sbf.toString();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (reader != null) {
                try {
                    reader.close();
                } catch (IOException e1) {
                    e1.printStackTrace();
                }
            }
        }
        return sbf.toString();
    }
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市野宜,隨后出現(xiàn)的幾起案子碗殷,更是在濱河造成了極大的恐慌,老刑警劉巖速缨,帶你破解...
    沈念sama閱讀 217,542評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件锌妻,死亡現(xiàn)場離奇詭異,居然都是意外死亡旬牲,警方通過查閱死者的電腦和手機仿粹,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評論 3 394
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來原茅,“玉大人吭历,你說我怎么就攤上這事±揲伲” “怎么了晌区?”我有些...
    開封第一講書人閱讀 163,912評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我朗若,道長恼五,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,449評論 1 293
  • 正文 為了忘掉前任哭懈,我火速辦了婚禮灾馒,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘遣总。我一直安慰自己睬罗,他們只是感情好,可當我...
    茶點故事閱讀 67,500評論 6 392
  • 文/花漫 我一把揭開白布旭斥。 她就那樣靜靜地躺著容达,像睡著了一般。 火紅的嫁衣襯著肌膚如雪垂券。 梳的紋絲不亂的頭發(fā)上董饰,一...
    開封第一講書人閱讀 51,370評論 1 302
  • 那天,我揣著相機與錄音圆米,去河邊找鬼。 笑死啄栓,一個胖子當著我的面吹牛娄帖,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播昙楚,決...
    沈念sama閱讀 40,193評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼近速,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了堪旧?” 一聲冷哼從身側(cè)響起削葱,我...
    開封第一講書人閱讀 39,074評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎淳梦,沒想到半個月后析砸,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,505評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡爆袍,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,722評論 3 335
  • 正文 我和宋清朗相戀三年首繁,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片陨囊。...
    茶點故事閱讀 39,841評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡弦疮,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出蜘醋,到底是詐尸還是另有隱情胁塞,我是刑警寧澤,帶...
    沈念sama閱讀 35,569評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站啸罢,受9級特大地震影響编检,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜伺糠,卻給世界環(huán)境...
    茶點故事閱讀 41,168評論 3 328
  • 文/蒙蒙 一蒙谓、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧训桶,春花似錦累驮、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至午绳,卻和暖如春置侍,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背拦焚。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評論 1 269
  • 我被黑心中介騙來泰國打工蜡坊, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人赎败。 一個月前我還...
    沈念sama閱讀 47,962評論 2 370
  • 正文 我出身青樓秕衙,卻偏偏與公主長得像,于是被迫代替她去往敵國和親僵刮。 傳聞我的和親對象是個殘疾皇子据忘,可洞房花燭夜當晚...
    茶點故事閱讀 44,781評論 2 354