android快速獲取系統(tǒng)中的圖片和視頻

本文來源于實(shí)際項(xiàng)目遇到的需求。如果想要直接看源碼(實(shí)際項(xiàng)目是java所寫淋叶,但git上的demo是kotlin所寫胚吁,畢竟android目標(biāo)是將kotlin逐步替代java),訪問:https://github.com/life2smile/PhotoAlbum.git恋腕。切記這只是個(gè)demo。

一逆瑞、需求背景

需要掃描出系統(tǒng)中存在的視頻及圖片荠藤,并展示在宮格視圖中,同時(shí)圖片以其所在文件夾進(jìn)行分組區(qū)分(demo中并未實(shí)現(xiàn)获高,可自行實(shí)現(xiàn))哈肖。

二、目標(biāo)

(1)實(shí)現(xiàn)基本的相冊預(yù)覽功能念秧,包括視頻及圖片淤井。

(2)相冊按時(shí)間創(chuàng)建順序就近排序即新創(chuàng)建的在前面展示。

(3)視頻預(yù)覽展示播放時(shí)長摊趾。

最重要的是:

優(yōu)化掃描速度币狠、優(yōu)化掃描速度、優(yōu)化掃描速度砾层。漩绵。。

為什么強(qiáng)調(diào)優(yōu)化掃描速度肛炮?文章后面會(huì)講止吐。

三宝踪、實(shí)現(xiàn)方案

需求的難點(diǎn)在于既要獲取視頻又要獲取圖片,圖片的預(yù)覽可以很快獲取碍扔,但是視頻預(yù)覽相對要耗時(shí)些瘩燥,所以二者存在著天然的時(shí)間差,這里采用兩個(gè)線程任務(wù)來分別掃描圖片及視頻不同,最后先后合并到一個(gè)集合中厉膀,進(jìn)行數(shù)據(jù)渲染。

所以套鹅,這里首先要有一個(gè)統(tǒng)一的數(shù)據(jù)結(jié)構(gòu)站蝠。眾所周知,android本身已經(jīng)存儲(chǔ)了相冊預(yù)覽的相關(guān)數(shù)據(jù)并通過ContentResolver暴露了查詢接口卓鹿,事實(shí)上這些數(shù)據(jù)有很多的公共性菱魔,比如創(chuàng)建時(shí)間、路徑等吟孙,因此這里可以抽象出一個(gè)多媒體數(shù)據(jù)結(jié)構(gòu) MediaData來進(jìn)行統(tǒng)一表示澜倦。除了這些共有字段外,還需要添加特定多媒體類型下的字段杰妓,比如視頻的duration藻治、相冊的經(jīng)緯度等,統(tǒng)一于MediaData巷挥。

下面逐步討論各模塊實(shí)現(xiàn)桩卵。

四、界面搭建

界面搭建的實(shí)現(xiàn)思路很簡單倍宾,使用RecyclerView + GridLayoutManager布局即可雏节。需要注意的地方是,我們想要的效果是各個(gè)宮格等分居中于屏幕高职,且大小一致钩乍。所以應(yīng)該首先獲取屏幕寬度,基于宮格的列數(shù)進(jìn)行等分怔锌,獲取到size就是每個(gè)宮格的高和寬寥粹。當(dāng)然這個(gè)只是常見的默認(rèn)宮格實(shí)現(xiàn)方案,有其他高寬定制需求的埃元,按照自己需求定制即可涝涤。

五、圖片掃描

首先亚情,抽出一個(gè)圖片掃描的工具類ImageScanHelper妄痪,具體完成的功能會(huì)在代碼架構(gòu)中闡述。

//kotlin中的單例寫法(再也不用糾結(jié)懶加載楞件、多線程下的java寫法了)

//這里當(dāng)然可以改成伴隨對象衫生,以實(shí)現(xiàn)和java static相匹配的方式。

object ImageScanHelper {

//start為對外暴露的掃描接口土浸,在相冊預(yù)覽的activity中罪针,觸發(fā)該方法調(diào)用

//形如:ImageScanHelper.start(this.getApplicationContext(), handler)。

//第一個(gè)參數(shù)為context黄伊,第二個(gè)為handler泪酱,目的是拿到掃描數(shù)據(jù)后通知主線程進(jìn)行ui更新。

fun start(context:Context, handler:Handler) {

//圖片掃描相對比較耗時(shí)还最,這里單獨(dú)開一個(gè)掃描線程

Thread{

            doScan(context,handler)

}.start()

}

private fun doScan(context:Context, handler:Handler) {

//這里完成數(shù)據(jù)查詢墓阀,查詢結(jié)果可通過游標(biāo)cursor拿到

cursor = context.contentResolver.query(...)

parseData(cursor,handler)

}

private fun parseData(cursor:Cursor, handler:Handler) {

//遍歷數(shù)據(jù)拓轻,檢出我們需要的數(shù)據(jù)斯撮,并通過加入到imageList中。

do {

imageList.add(MediaData(id,createTime, ...))

}while(cursor.moveToNext())

//通過handler將數(shù)據(jù)傳遞給ui主線程進(jìn)行界面更新

val msg = Message()

msg.obj = imageList//這里的

msg.what =MediaType.MEDIA_TYPE_IMAGE

handler.sendMessage(msg)

}

六扶叉、視頻掃描

前面說過勿锅,這個(gè)是個(gè)難點(diǎn),原因在于視頻縮略圖的獲取枣氧。android中有多種方案可以獲取視頻縮略圖溢十,如通過MediaMetadataRetriever獲取視頻第一幀、通過ThumbnailUtils獲取第一幀等等达吞。這些方案完全能獲取到視頻縮略圖张弛,but,這些有個(gè)很大的弊端酪劫,就是這些都是非常耗時(shí)的方案吞鸭,用戶從進(jìn)到預(yù)覽界面開始,到真正看到視頻預(yù)覽的效果需要很長時(shí)間契耿,如果視頻數(shù)目較小還能接受瞒大,反之就慢到令人發(fā)指了。所以這些方案實(shí)際上并不可取搪桂。

那么有沒有更快的方案能獲取到視頻縮略圖透敌?當(dāng)然有,那就是查詢系統(tǒng)早就給我們保存好了的視頻縮略圖信息踢械,這樣就大大縮短了獲取速度酗电,但是這個(gè)方案依然存在弊端,那就是很多機(jī)型拿不到最新拍攝的視頻縮略圖内列,甚至有的機(jī)型除非重新啟動(dòng)手機(jī)撵术,才能看到新拍攝的視頻縮略圖,這顯然對用戶來說也是不可接受的话瞧。

那么還有沒有兼容性更好嫩与、掃描速度更快的手段獲取視頻縮略圖寝姿?

有!那就是結(jié)合上述兩種方案划滋。具體闡述如下:

(1)查詢手機(jī)已緩存的縮略圖饵筑,如果有則保存地址

(2)對于沒有縮略圖的視頻,人工生成縮略圖并緩存处坪。然后返回視頻縮略圖地址

實(shí)際上根资,對于沒有縮略圖的視頻畢竟是少數(shù),所以同窘,上述方案很接近單純掃描系統(tǒng)數(shù)據(jù)緩存的時(shí)間消耗玄帕。

代碼結(jié)構(gòu)描述如下:

//功能同圖片掃描

fun start(context:Context, handler:Handler) {

Thread{

        doScan(context,handler)

}.start()

}

//功能同圖片掃描

private fun doScan(context:Context, handler:Handler) {

//這里先掃描視頻數(shù)據(jù)

cursor = context.contentResolver.query(...)

}

//功能同圖片掃描

private fun parseData(context:Context, cursor:Cursor, handler:Handler) {

do {

        try {

//這里會(huì)根據(jù)拿到的視頻數(shù)據(jù),觸發(fā)一次視頻縮略圖的掃描

thumbCursor = context.contentResolver.query(...)

//獲取視頻縮略圖路徑(可能為空)想邦,如果有的話直接獲取裤纹,如果沒有則生成縮略圖

 thumbNailPath = thumbNailPath.isNullOrEmpty().let {

//這里生成縮略圖

generateThumbNail(filePath)

}

//添加掃描出來的視頻及其縮略圖數(shù)據(jù)

videoList.add(MediaData(id, createTime, duration, albumName, filePath, thumbNailPath, mimeType,null,null))

}

}while (cursor.moveToNext())

//發(fā)送消息至ui線程,攜帶有掃描的視頻數(shù)據(jù)

val msg:Message =Message.obtain()

msg.obj = videoList

msg.what =MediaType.MEDIA_TYPE_VIDEO

    handler.sendMessage(msg)

}

至此案狠,掃描視頻的代碼邏輯完成服傍。

七、數(shù)據(jù)合并

前面提到骂铁,圖片的掃描速度遠(yuǎn)遠(yuǎn)快于視頻掃描速度吹零,所以二者存在時(shí)間差,但數(shù)據(jù)最終要合并到一起并渲染拉庵。

其實(shí)到這里已經(jīng)很簡單了灿椅,因?yàn)槎哂泄餐臄?shù)據(jù)結(jié)構(gòu)MediaData,在將一個(gè)類型的數(shù)據(jù)添加到adapter中后,調(diào)用notifyDataSetChanged()即可钞支。

八茫蛹、保證時(shí)間有序

這個(gè)也很簡單,我們只需要在添加數(shù)據(jù)到adapter的時(shí)候?qū)ist進(jìn)行排序即可烁挟。

對于java來說婴洼,只需要MediaData實(shí)現(xiàn)compareTo方法,即可調(diào)用Collections.sort進(jìn)行排序撼嗓。

對于kotlin來說柬采,調(diào)用List.sort{}即可。

九且警、圖片壓縮

由于android對運(yùn)行的應(yīng)用有內(nèi)存限制(具體參考我的另一篇博客http://www.reibang.com/p/a06466971bff)粉捻,所以在處理圖片加載的時(shí)候要尤其注意,稍有不慎就有可能oom斑芜。常見的第三方圖片加載庫都有對圖片進(jìn)行過處理肩刃,這里由于我們采用的是原生控件,所以需要對圖片進(jìn)行處理。代碼如下:

class ImageResizeUtil {
    companion object {
        fun resize(path: String, w: Int, h: Int): Bitmap {//根據(jù)傳入的寬高進(jìn)行圖片裁剪
            val options = BitmapFactory.Options()
            options.inJustDecodeBounds = true;
            BitmapFactory.decodeFile(path, options)

            //獲取縮放比例盈包,主要在decode失敗的時(shí)候options測量的寬高值是-1沸呐,要考慮這種情況進(jìn)行處理
            options.inSampleSize = Math.max(1, Math.ceil(Math.max(
                    options.outWidth / w, options.outHeight / h
            ).toDouble()).toInt())

            options.inJustDecodeBounds = false
            return BitmapFactory.decodeFile(path, options)
        }
    }
}

十、過濾

前面掃描圖片和視頻的過程中有可能產(chǎn)生一些臟數(shù)據(jù)或者不符合我們需要的數(shù)據(jù)续语,所以這里要對數(shù)據(jù)進(jìn)行過濾垂谢。

很簡單我們采用過濾器模式即可厦画,首先抽象出一個(gè)過濾器接口:

//這里采用了泛型的設(shè)計(jì)疮茄,滿足各種數(shù)據(jù)傳入
interface IFilter<T> {
    fun doFilter(t: T)
}

接著可以針對不同的類型實(shí)現(xiàn)過濾功能,比如過濾掉不符合大小的圖片(這里僅僅列舉個(gè)例子根暑,具體可以參考git代碼):

class ImageSizeFilter : IFilter<MutableList<MediaData>> {
    override fun doFilter(list: MutableList<MediaData>) {
        val iterator = list.iterator()//這里必須要采用迭代器刪除力试,避免遍歷的時(shí)候有數(shù)據(jù)改動(dòng)引起異常
        while (iterator.hasNext()) {
            val mediaData: MediaData = iterator.next()
            val options: BitmapFactory.Options = BitmapFactory.Options()
            BitmapFactory.decodeFile(mediaData.filePath, options)
            if (options.outWidth <= 50 || options.outHeight <= 50) {
                iterator.remove()
            }
        }
    }
}

十一、The End

最后排嫌,首尾呼應(yīng)畸裳,源碼地址見:https://github.com/life2smile/PhotoAlbum.git。再次強(qiáng)調(diào)淳地,代碼是基于kotlin寫的怖糊,如果想要java版的,自己可以參照邏輯實(shí)現(xiàn)一遍颇象,或者使用插件轉(zhuǎn)換一下即可伍伤。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市遣钳,隨后出現(xiàn)的幾起案子扰魂,更是在濱河造成了極大的恐慌,老刑警劉巖蕴茴,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件劝评,死亡現(xiàn)場離奇詭異,居然都是意外死亡倦淀,警方通過查閱死者的電腦和手機(jī)蒋畜,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來撞叽,“玉大人姻成,你說我怎么就攤上這事∧馨牵” “怎么了佣渴?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長初斑。 經(jīng)常有香客問我辛润,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任砂竖,我火速辦了婚禮真椿,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘乎澄。我一直安慰自己突硝,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布置济。 她就那樣靜靜地躺著解恰,像睡著了一般。 火紅的嫁衣襯著肌膚如雪浙于。 梳的紋絲不亂的頭發(fā)上护盈,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天,我揣著相機(jī)與錄音羞酗,去河邊找鬼腐宋。 笑死,一個(gè)胖子當(dāng)著我的面吹牛檀轨,可吹牛的內(nèi)容都是我干的胸竞。 我是一名探鬼主播,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼参萄,長吁一口氣:“原來是場噩夢啊……” “哼卫枝!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起拧揽,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤剃盾,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后淤袜,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體痒谴,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年铡羡,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了积蔚。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,785評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡烦周,死狀恐怖尽爆,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情读慎,我是刑警寧澤漱贱,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站夭委,受9級特大地震影響幅狮,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一崇摄、第九天 我趴在偏房一處隱蔽的房頂上張望擎值。 院中可真熱鬧,春花似錦逐抑、人聲如沸鸠儿。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽进每。三九已至,卻和暖如春腐巢,著一層夾襖步出監(jiān)牢的瞬間品追,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工冯丙, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人遭京。 一個(gè)月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓胃惜,卻偏偏與公主長得像,于是被迫代替她去往敵國和親哪雕。 傳聞我的和親對象是個(gè)殘疾皇子船殉,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評論 2 354

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,099評論 25 707
  • 用兩張圖告訴你,為什么你的 App 會(huì)卡頓? - Android - 掘金 Cover 有什么料斯嚎? 從這篇文章中你...
    hw1212閱讀 12,723評論 2 59
  • 1利虫、通過CocoaPods安裝項(xiàng)目名稱項(xiàng)目信息 AFNetworking網(wǎng)絡(luò)請求組件 FMDB本地?cái)?shù)據(jù)庫組件 SD...
    陽明先生_X自主閱讀 15,980評論 3 119
  • 前文中提到的當(dāng)初那篇長文。 靜落的雪花鋪陳了視野堡僻,一步步輕壓出曾經(jīng)來過的痕糠惫,方有人知,他的旅途钉疫。 初冬硼讽,初雪,他獨(dú)...
    愚書愚己閱讀 216評論 0 0
  • 感謝大家的喜歡與關(guān)注牲阁,我已經(jīng)長久不在簡書寫作了固阁,最新的自我提升、情感城菊、讀書等文章备燃,有興趣可以在豆瓣關(guān)注:艾莉森王,...
    艾莉森王閱讀 1,996評論 0 4