Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Contribute to GitLab
Sign in
Toggle navigation
I
ImageVideoBanner
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Registry
Registry
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
jiangjiantao
ImageVideoBanner
Commits
d75937a0
Commit
d75937a0
authored
Apr 23, 2021
by
委座-江
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
解决部分内存泄漏
增加二级缓存
parent
61e53d36
Show whitespace changes
Inline
Side-by-side
Showing
21 changed files
with
2155 additions
and
263 deletions
+2155
-263
build.gradle
imageVideoBanner/build.gradle
+1
-1
AndroidManifest.xml
imageVideoBanner/src/main/AndroidManifest.xml
+1
-2
BaseApplication.java
...ain/java/com/widget/imagevideobanner/BaseApplication.java
+19
-0
MainActivity.java
...c/main/java/com/widget/imagevideobanner/MainActivity.java
+8
-4
ImageVideoBanner.java
.../com/widget/imagevideobanner/banner/ImageVideoBanner.java
+12
-68
ImageVideoFragment.java
...om/widget/imagevideobanner/banner/ImageVideoFragment.java
+72
-126
MyVideoView.java
.../java/com/widget/imagevideobanner/banner/MyVideoView.java
+50
-0
BannerBean.java
...ain/java/com/widget/imagevideobanner/bean/BannerBean.java
+20
-17
BitmapCache.java
...n/java/com/widget/imagevideobanner/cache/BitmapCache.java
+257
-0
DiskLruCache.java
.../com/widget/imagevideobanner/cache/disk/DiskLruCache.java
+943
-0
StrictLineReader.java
.../widget/imagevideobanner/cache/disk/StrictLineReader.java
+196
-0
Util.java
...ain/java/com/widget/imagevideobanner/cache/disk/Util.java
+77
-0
BannerLogFileUtils.java
...a/com/widget/imagevideobanner/log/BannerLogFileUtils.java
+87
-0
BannerLogFileUtils.java
...com/widget/imagevideobanner/utils/BannerLogFileUtils.java
+0
-43
BitmapUtils.java
...n/java/com/widget/imagevideobanner/utils/BitmapUtils.java
+1
-1
IMMLeaks.java
...main/java/com/widget/imagevideobanner/utils/IMMLeaks.java
+177
-0
LifecycleCallbacksAdapter.java
...get/imagevideobanner/utils/LifecycleCallbacksAdapter.java
+36
-0
MD5Util.java
.../main/java/com/widget/imagevideobanner/utils/MD5Util.java
+113
-0
ThrowableUtils.java
...ava/com/widget/imagevideobanner/utils/ThrowableUtils.java
+84
-0
load_fail.png
imageVideoBanner/src/main/res/drawable-mdpi/load_fail.png
+0
-0
item_video_view.xml
imageVideoBanner/src/main/res/layout/item_video_view.xml
+1
-1
No files found.
imageVideoBanner/build.gradle
View file @
d75937a0
apply
plugin:
'com.android.
library
'
apply
plugin:
'com.android.
application
'
android
{
compileSdkVersion
28
...
...
imageVideoBanner/src/main/AndroidManifest.xml
View file @
d75937a0
...
...
@@ -6,9 +6,9 @@
<uses-permission
android:name=
"android.permission.WRITE_EXTERNAL_STORAGE"
/>
<uses-permission
android:name=
"android.permission.READ_EXTERNAL_STORAGE"
/>
<application
android:allowBackup=
"true"
android:name=
".BaseApplication"
android:icon=
"@mipmap/ic_launcher"
android:label=
"@string/app_name"
android:roundIcon=
"@mipmap/ic_launcher_round"
...
...
@@ -28,7 +28,6 @@
</activity>
</application>
</manifest>
\ No newline at end of file
imageVideoBanner/src/main/java/com/widget/imagevideobanner/BaseApplication.java
0 → 100644
View file @
d75937a0
package
com
.
widget
.
imagevideobanner
;
import
android.app.Application
;
public
class
BaseApplication
extends
Application
{
private
static
BaseApplication
mApp
;
@Override
public
void
onCreate
()
{
super
.
onCreate
();
mApp
=
this
;
}
public
static
BaseApplication
getApplication
()
{
return
mApp
;
}
}
imageVideoBanner/src/main/java/com/widget/imagevideobanner/MainActivity.java
View file @
d75937a0
...
...
@@ -6,6 +6,7 @@ import android.support.v7.app.AppCompatActivity;
import
com.widget.imagevideobanner.banner.ImageVideoBanner
;
import
com.widget.imagevideobanner.bean.BannerBean
;
import
com.widget.imagevideobanner.utils.IMMLeaks
;
import
java.util.ArrayList
;
import
java.util.List
;
...
...
@@ -22,17 +23,20 @@ public class MainActivity extends AppCompatActivity {
setDatas
();
banner
.
addData
(
list
);
banner
.
startBanner
();
//修复inputManager引发的内存泄漏
IMMLeaks
.
fixFocusedViewLeak
(
BaseApplication
.
getApplication
());
}
private
void
setDatas
()
{
for
(
int
i
=
0
;
i
<
1
;
i
++)
{
for
(
int
i
=
0
;
i
<
2
;
i
++)
{
BannerBean
listBean
=
new
BannerBean
();
if
(
i
==
0
)
{
String
ur
l
=
Environment
.
getExternalStorageDirectory
()
+
"/Video_Compressed_converseBanner_2021_01_05_03_05_11_0.mp4"
;
listBean
.
setUrl
(
ur
l
);
String
ur
i
=
"android.resource://"
+
getPackageName
()
+
"/"
+
R
.
raw
.
default_guide
;
listBean
.
setUrl
(
ur
i
);
listBean
.
setType
(
1
);
list
.
add
(
listBean
);
}
else
if
(
i
==
1
)
{
}
else
{
String
url
=
Environment
.
getExternalStorageDirectory
()
+
"/vertical_test.mp4"
;
listBean
.
setUrl
(
url
);
listBean
.
setType
(
1
);
...
...
imageVideoBanner/src/main/java/com/widget/imagevideobanner/banner/ImageVideoBanner.java
View file @
d75937a0
...
...
@@ -2,7 +2,6 @@ package com.widget.imagevideobanner.banner;
import
android.content.Context
;
import
android.content.res.TypedArray
;
import
android.graphics.Bitmap
;
import
android.media.MediaPlayer
;
import
android.os.Bundle
;
import
android.os.Handler
;
...
...
@@ -13,28 +12,24 @@ import android.support.v4.app.Fragment;
import
android.support.v4.app.FragmentActivity
;
import
android.support.v4.app.FragmentManager
;
import
android.support.v4.app.FragmentStatePagerAdapter
;
import
android.text.TextUtils
;
import
android.util.AttributeSet
;
import
android.view.LayoutInflater
;
import
android.view.MotionEvent
;
import
android.view.View
;
import
android.widget.FrameLayout
;
import
com.bumptech.glide.util.Util
;
import
com.widget.imagevideobanner.R
;
import
com.widget.imagevideobanner.bean.BannerBean
;
import
com.widget.imagevideobanner.
utils.BitmapUtils
;
import
com.widget.imagevideobanner.
cache.BitmapCache
;
import
java.util.ArrayList
;
import
java.util.List
;
import
java.util.Timer
;
import
java.util.TimerTask
;
import
java.util.concurrent.Callable
;
import
java.util.concurrent.ExecutionException
;
import
java.util.concurrent.Executors
;
import
java.util.concurrent.Future
;
import
java.util.concurrent.ScheduledExecutorService
;
import
wseemann.media.FFmpegMediaMetadataRetriever
;
public
class
ImageVideoBanner
extends
FrameLayout
implements
ViewPager
.
OnPageChangeListener
,
com
.
widget
.
imagevideobanner
.
banner
.
ImageVideoFragment
.
OnVideoCompletionListener
{
private
static
final
int
UPTATE_VIEWPAGER
=
0
;
...
...
@@ -77,8 +72,6 @@ public class ImageVideoBanner extends FrameLayout implements ViewPager.OnPageCha
return
true
;
}
});
}
public
void
replaceData
(
List
<
com
.
widget
.
imagevideobanner
.
bean
.
BannerBean
>
listBean
)
{
...
...
@@ -97,56 +90,12 @@ public class ImageVideoBanner extends FrameLayout implements ViewPager.OnPageCha
return
;
}
for
(
BannerBean
bannerBean
:
mList
)
{
if
(
bannerBean
.
getType
()
==
1
&&
null
==
bannerBean
.
getLoadingImage
()
)
{
setLoadingImages
(
bannerBean
);
if
(
bannerBean
.
getType
()
==
1
)
{
BitmapCache
.
getInstance
(
mContext
).
addCache
(
bannerBean
.
getUrl
()
);
}
}
}
/**
* 获取视频的第一帧作为截图
*
* @param bannerBean
*/
private
void
setLoadingImages
(
final
BannerBean
bannerBean
)
{
if
(
sExecutor
==
null
)
{
sExecutor
=
Executors
.
newScheduledThreadPool
(
5
);
}
Future
<
Boolean
>
submit
=
sExecutor
.
submit
(
new
Callable
<
Boolean
>()
{
@Override
public
Boolean
call
()
{
try
{
String
url
=
bannerBean
.
getUrl
();
if
(!
url
.
contains
(
"android.resource"
))
{
FFmpegMediaMetadataRetriever
mmr
=
new
FFmpegMediaMetadataRetriever
();
mmr
.
setDataSource
(
url
);
mmr
.
extractMetadata
(
FFmpegMediaMetadataRetriever
.
METADATA_KEY_ALBUM
);
mmr
.
extractMetadata
(
FFmpegMediaMetadataRetriever
.
METADATA_KEY_ARTIST
);
Bitmap
b
=
mmr
.
getFrameAtTime
();
// frame at 2 seconds
bannerBean
.
setLoadingImage
(
BitmapUtils
.
getBytes
(
b
));
Bitmap
bx
=
BitmapUtils
.
createBlurBitmap
(
b
,
40
);
bannerBean
.
setLoadingBlurImage
(
BitmapUtils
.
getBytes
(
bx
));
b
.
recycle
();
bx
.
recycle
();
mmr
.
release
();
}
return
true
;
}
catch
(
Exception
e
)
{
return
false
;
}
}
});
try
{
submit
.
get
();
}
catch
(
InterruptedException
e
)
{
e
.
printStackTrace
();
}
catch
(
ExecutionException
e
)
{
e
.
printStackTrace
();
}
}
@Override
public
void
onPageScrolled
(
int
position
,
float
positionOffset
,
int
positionOffsetPixels
)
{
...
...
@@ -155,7 +104,6 @@ public class ImageVideoBanner extends FrameLayout implements ViewPager.OnPageCha
@Override
public
void
onPageSelected
(
int
position
)
{
autoCurrIndex
=
position
;
// Toast.makeText(context,"position="+position,Toast.LENGTH_SHORT).show();
if
(
mList
.
get
(
position
).
getType
()
==
1
)
{
//如果是视频
stopBanner
();
...
...
@@ -175,7 +123,6 @@ public class ImageVideoBanner extends FrameLayout implements ViewPager.OnPageCha
if
(
msg
.
arg1
!=
0
)
{
mViewPager
.
setCurrentItem
(
msg
.
arg1
);
}
else
{
//false 当从末页调到首页时,不显示翻页动画效果,
mViewPager
.
setCurrentItem
(
msg
.
arg1
,
false
);
}
break
;
...
...
@@ -202,7 +149,6 @@ public class ImageVideoBanner extends FrameLayout implements ViewPager.OnPageCha
}
public
void
startBanner
()
{
//如果第一页是视频 不用定时器
if
(
mList
.
get
(
0
).
getType
()
==
0
)
{
startBanner
(
period
);
}
...
...
@@ -211,7 +157,7 @@ public class ImageVideoBanner extends FrameLayout implements ViewPager.OnPageCha
public
void
startBanner
(
long
delay
)
{
stopBanner
();
timer
=
new
Timer
();
createTimerTask
();
//创建定时器
createTimerTask
();
timer
.
schedule
(
timerTask
,
delay
,
period
);
}
...
...
@@ -233,7 +179,6 @@ public class ImageVideoBanner extends FrameLayout implements ViewPager.OnPageCha
@Override
public
void
onVideoCompletion
(
MediaPlayer
mp
)
{
if
(
mList
.
size
()
==
1
)
{
//当集合中只有一个视频时,循环播放
mAdapter
.
getFragment
().
circulationPlayer
();
}
startBanner
(
0
);
...
...
@@ -279,7 +224,6 @@ public class ImageVideoBanner extends FrameLayout implements ViewPager.OnPageCha
/**
* 只有1个视频的时候循环播放
*
* @return
*/
private
boolean
getLoop
()
{
...
...
@@ -307,25 +251,21 @@ public class ImageVideoBanner extends FrameLayout implements ViewPager.OnPageCha
boolean
change
=
compareData
(
listBean
);
if
(!
change
)
{
notifyDataSetChanged
();
// startBanner();
return
;
}
if
(
null
!=
listBean
)
{
if
(
listBean
.
size
()
>
0
)
{
mList
.
clear
();
addData
(
listBean
);
//如果第一张是图片 需要开启定时器
if
(
listBean
.
get
(
0
).
getType
()
==
0
)
{
startBanner
();
}
autoCurrIndex
=
0
;
// startBanner();
}
}
/**
* 对比数据
*
* @param listBean
* @return
*/
...
...
@@ -348,6 +288,7 @@ public class ImageVideoBanner extends FrameLayout implements ViewPager.OnPageCha
public
void
addData
(
List
<
BannerBean
>
listBean
)
{
if
(
null
!=
listBean
)
{
mList
.
clear
();
mList
.
addAll
(
listBean
);
}
initBannerBitmap
();
...
...
@@ -357,7 +298,10 @@ public class ImageVideoBanner extends FrameLayout implements ViewPager.OnPageCha
public
ImageVideoFragment
getFragment
()
{
return
fragment
;
}
}
public
List
<
BannerBean
>
getBannerInfos
(){
return
mList
;
}
}
imageVideoBanner/src/main/java/com/widget/imagevideobanner/banner/ImageVideoFragment.java
View file @
d75937a0
...
...
@@ -2,7 +2,6 @@ package com.widget.imagevideobanner.banner;
import
android.graphics.Bitmap
;
import
android.graphics.Color
;
import
android.graphics.drawable.BitmapDrawable
;
import
android.graphics.drawable.Drawable
;
import
android.media.MediaPlayer
;
import
android.net.Uri
;
...
...
@@ -14,20 +13,24 @@ import android.support.annotation.Nullable;
import
android.support.constraint.ConstraintLayout
;
import
android.support.v4.app.Fragment
;
import
android.util.Log
;
import
android.util.Pair
;
import
android.view.LayoutInflater
;
import
android.view.View
;
import
android.view.ViewGroup
;
import
android.widget.ImageView
;
import
android.widget.LinearLayout
;
import
android.widget.VideoView
;
import
com.bumptech.glide.Glide
;
import
com.bumptech.glide.load.resource.drawable.GlideDrawable
;
import
com.bumptech.glide.request.RequestListener
;
import
com.bumptech.glide.request.target.Target
;
import
com.widget.imagevideobanner.R
;
import
com.widget.imagevideobanner.bean.BannerBean
;
import
com.widget.imagevideobanner.utils.BannerLogFileUtils
;
import
com.widget.imagevideobanner.cache.BitmapCache
;
import
com.widget.imagevideobanner.log.BannerLogFileUtils
;
import
com.widget.imagevideobanner.utils.BitmapUtils
;
import
com.widget.imagevideobanner.utils.ThrowableUtils
;
import
java.io.File
;
import
java.lang.ref.WeakReference
;
import
java.math.BigDecimal
;
...
...
@@ -36,9 +39,8 @@ public class ImageVideoFragment extends Fragment {
private
static
final
String
TAG
=
ImageVideoFragment
.
class
.
getSimpleName
();
private
OnVideoCompletionListener
listener
;
private
VideoView
mVideoView
;
private
My
VideoView
mVideoView
;
private
BannerBean
bannerBean
;
// 视频是否循环播放
private
boolean
loop
;
private
ImageView
ivWaitLoading
;
private
ConstraintLayout
cVideoView
;
...
...
@@ -50,21 +52,20 @@ public class ImageVideoFragment extends Fragment {
private
static
final
int
START_PLAYER
=
0x2001
;
private
static
final
int
PAUSE_PLAYER
=
0x2002
;
private
static
final
int
SET_VIDEO_URL
=
0x2003
;
private
static
final
int
PROGRESS
=
0x2004
;
private
static
final
int
DEFAULT_WIDTH
=
983
;
private
static
final
int
DEFAULT_HEIGHT
=
1080
;
/**
* 使用Handler是为了避免出现ANR异常
*/
private
Handler
handler
;
@Override
public
void
onCreate
(
@Nullable
Bundle
savedInstanceState
)
{
super
.
onCreate
(
savedInstanceState
);
Bundle
bundle
=
getArguments
();
bannerBean
=
(
BannerBean
)
bundle
.
getSerializable
(
"bannerBean"
);
loop
=
(
boolean
)
bundle
.
getBoolean
(
"loop"
);
Log
.
e
(
TAG
,
"type="
+
bannerBean
.
getType
()
+
",url="
+
bannerBean
.
getUrl
());
loop
=
bundle
.
getBoolean
(
"loop"
);
handler
=
new
InnerHandler
(
this
);
}
...
...
@@ -77,16 +78,20 @@ public class ImageVideoFragment extends Fragment {
if
(
type
==
0
)
{
view
=
LayoutInflater
.
from
(
getActivity
()).
inflate
(
R
.
layout
.
item_image_view
,
container
,
false
);
ImageView
imageView
=
view
.
findViewById
(
R
.
id
.
iv
);
File
file
=
new
File
(
bannerBean
.
getUrl
());
if
(
file
.
exists
())
{
Glide
.
with
(
container
.
getContext
()).
load
(
file
)
.
into
(
imageView
);
}
else
{
Glide
.
with
(
container
.
getContext
()).
load
(
bannerBean
.
getUrl
())
.
into
(
imageView
);
Glide
.
with
(
container
.
getContext
()).
load
(
bannerBean
.
getUrl
()).
listener
(
new
RequestListener
<
String
,
GlideDrawable
>()
{
@Override
public
boolean
onException
(
Exception
e
,
String
model
,
Target
<
GlideDrawable
>
target
,
boolean
isFirstResource
)
{
BannerLogFileUtils
.
writeLog
(
"图片加载出错 url ="
+
bannerBean
.
getUrl
()
+
"出错原因是:"
+
ThrowableUtils
.
getFullStackTrace
(
e
));
return
false
;
}
@Override
public
boolean
onResourceReady
(
GlideDrawable
resource
,
String
model
,
Target
<
GlideDrawable
>
target
,
boolean
isFromMemoryCache
,
boolean
isFirstResource
)
{
return
false
;
}
}).
error
(
R
.
drawable
.
load_fail
).
into
(
imageView
);
}
else
{
BannerLogFileUtils
.
writeLog
(
"开始加载视频 url ="
+
bannerBean
.
getUrl
());
view
=
LayoutInflater
.
from
(
getActivity
()).
inflate
(
R
.
layout
.
item_video_view
,
container
,
false
);
mVideoView
=
view
.
findViewById
(
R
.
id
.
video_view
);
ivWaitLoading
=
view
.
findViewById
(
R
.
id
.
iv_wait_loading_layout
);
...
...
@@ -102,32 +107,13 @@ public class ImageVideoFragment extends Fragment {
return
view
;
}
public
void
setVideoBackground
(){
if
(
bannerBean
.
getLoadingImage
()
==
null
)
{
Bitmap
bitmap
=
BitmapCache
.
getInstance
(
getContext
()).
getBlurBitmap
(
bannerBean
.
getUrl
());
if
(
bitmap
==
null
)
{
cVideoView
.
setBackgroundResource
(
R
.
drawable
.
guide_defaut
);
}
else
{
cVideoView
.
setBackground
(
BitmapUtils
.
bitmap2Drawable
(
BitmapUtils
.
getBitmap
(
bannerBean
.
getLoadingBlurImage
())));
}
}
/**
* 设置加载视频的等待图片
*/
public
void
setPicture
()
{
//视频未加载完成,使用菊花
if
(
bannerBean
.
getLoadingImage
()
==
null
)
{
if
(
bannerBean
.
getUrl
().
contains
(
"android.resource"
))
{
ivWaitLoading
.
setVisibility
(
View
.
VISIBLE
);
ivWaitLoading
.
setImageResource
(
R
.
drawable
.
guide_defaut
);
llWaitLoading
.
setVisibility
(
View
.
GONE
);
}
else
{
ivWaitLoading
.
setVisibility
(
View
.
GONE
);
llWaitLoading
.
setVisibility
(
View
.
VISIBLE
);
}
}
else
{
//加载完成使用首帧图片
ivWaitLoading
.
setVisibility
(
View
.
VISIBLE
);
ivWaitLoading
.
setImageBitmap
(
BitmapUtils
.
getBitmap
(
bannerBean
.
getLoadingImage
()));
llWaitLoading
.
setVisibility
(
View
.
GONE
);
cVideoView
.
setBackground
(
BitmapUtils
.
bitmap2Drawable
(
bitmap
));
}
}
...
...
@@ -140,7 +126,6 @@ public class ImageVideoFragment extends Fragment {
return
;
}
Log
.
e
(
"####"
,
"草"
+
width
+
" "
+
height
);
//设置设置尺寸
ConstraintLayout
.
LayoutParams
lp2
=
(
ConstraintLayout
.
LayoutParams
)
ivWaitLoading
.
getLayoutParams
();
lp2
.
height
=
height
;
...
...
@@ -148,7 +133,8 @@ public class ImageVideoFragment extends Fragment {
ivWaitLoading
.
setLayoutParams
(
lp2
);
//视频未加载完成,使用菊花
if
(
bannerBean
.
getLoadingImage
()
==
null
)
{
Bitmap
bitmap
=
BitmapCache
.
getInstance
(
getContext
()).
getBitmap
(
bannerBean
.
getUrl
());
if
(
bitmap
==
null
)
{
if
(
bannerBean
.
getUrl
().
contains
(
"android.resource"
))
{
ivWaitLoading
.
setVisibility
(
View
.
VISIBLE
);
ivWaitLoading
.
setImageResource
(
R
.
drawable
.
guide_defaut
);
...
...
@@ -159,27 +145,20 @@ public class ImageVideoFragment extends Fragment {
}
}
else
{
//加载完成使用首帧图片
ivWaitLoading
.
setVisibility
(
View
.
VISIBLE
);
ivWaitLoading
.
setImageBitmap
(
BitmapUtils
.
getBitmap
(
bannerBean
.
getLoadingImage
())
);
ivWaitLoading
.
setImageBitmap
(
bitmap
);
llWaitLoading
.
setVisibility
(
View
.
GONE
);
}
}
private
void
initData
()
{
if
(
null
!=
mVideoView
)
{
mVideoView
.
setAlpha
(
0
);
mVideoView
.
requestFocus
();
try
{
mVideoView
.
setVideoURI
(
Uri
.
parse
(
bannerBean
.
getUrl
()));
}
catch
(
Exception
e
)
{
}
mVideoView
.
setOnCompletionListener
(
new
MediaPlayer
.
OnCompletionListener
()
{
@Override
public
void
onCompletion
(
MediaPlayer
mp
)
{
// mVideoView.setBackgroundColor(Color.TRANSPARENT);
// mVideoView.setVisibility(View.GONE);
mVideoView
.
pause
();
if
(
null
!=
listener
)
{
listener
.
onVideoCompletion
(
mp
);
...
...
@@ -190,18 +169,13 @@ public class ImageVideoFragment extends Fragment {
mVideoView
.
setOnPreparedListener
(
new
MediaPlayer
.
OnPreparedListener
()
{
@Override
public
void
onPrepared
(
MediaPlayer
mp
)
{
//重新刷新大小
refreshPortraitScreen
(
mp
.
getVideoWidth
(),
mp
.
getVideoHeight
());
resizeVideoView
(
mp
.
getVideoWidth
(),
mp
.
getVideoHeight
());
mp
.
setLooping
(
loop
);
Log
.
e
(
TAG
,
"视频加载完成"
+
bannerBean
.
getUrl
());
mp
.
setOnInfoListener
(
new
MediaPlayer
.
OnInfoListener
()
{
@Override
public
boolean
onInfo
(
MediaPlayer
mp
,
int
what
,
int
extra
)
{
if
(
what
==
MediaPlayer
.
MEDIA_INFO_VIDEO_RENDERING_START
)
{
mVideoView
.
setAlpha
(
1
);
handler
.
sendEmptyMessage
(
PROGRESS
);
llWaitLoading
.
setVisibility
(
View
.
GONE
);
ivWaitLoading
.
setVisibility
(
View
.
GONE
);
mVideoView
.
setBackgroundColor
(
Color
.
TRANSPARENT
);
...
...
@@ -216,9 +190,7 @@ public class ImageVideoFragment extends Fragment {
mVideoView
.
setOnErrorListener
(
new
MediaPlayer
.
OnErrorListener
()
{
@Override
public
boolean
onError
(
MediaPlayer
mp
,
int
what
,
int
extra
)
{
BannerLogFileUtils
.
saveInfo2File
(
"视频播放出错了-what="
+
what
+
",extra="
+
extra
);
BannerLogFileUtils
.
writeLog
(
"视频播放出错了-what="
+
what
+
",extra="
+
extra
);
Log
.
e
(
TAG
,
"视频播放出错了-what="
+
what
+
",extra="
+
extra
);
mVideoView
.
stopPlayback
();
if
(
null
!=
listener
)
{
...
...
@@ -244,16 +216,36 @@ public class ImageVideoFragment extends Fragment {
}
public
void
refreshPortraitScreen
(
int
width
,
int
height
)
{
public
void
resizeVideoView
(
int
width
,
int
height
)
{
Pair
<
Integer
,
Integer
>
pair
=
calacScaleSize
(
width
,
height
);
width
=
pair
.
first
;
height
=
pair
.
second
;
ConstraintLayout
.
LayoutParams
lp
=
(
ConstraintLayout
.
LayoutParams
)
mVideoView
.
getLayoutParams
();
lp
.
height
=
height
;
lp
.
width
=
width
;
mVideoView
.
setLayoutParams
(
lp
);
mVideoView
.
setVideoSize
(
width
,
height
);
if
(
bannerBean
.
getWidth
()
==
0
||
bannerBean
.
getHeight
()
==
0
){
bannerBean
.
setWidth
(
width
);
bannerBean
.
setHeight
(
height
);
listener
.
updateSize
(
bannerBean
);
}
}
private
Pair
<
Integer
,
Integer
>
calacScaleSize
(
int
width
,
int
height
){
if
(
bannerBean
.
getUrl
().
contains
(
"android.resource"
))
{
width
=
DEFAULT_WIDTH
;
height
=
DEFAULT_HEIGHT
;
}
int
mHeight
=
mVideoView
.
getHeight
();
int
mWidth
=
mVideoView
.
getWidth
();
BigDecimal
viewScale
=
new
BigDecimal
(
mWidth
+
""
)
.
divide
(
new
BigDecimal
(
mHeight
+
""
),
2
,
BigDecimal
.
ROUND_HALF_UP
);
BigDecimal
videoScale
=
new
BigDecimal
(
width
+
""
)
.
divide
(
new
BigDecimal
(
height
+
""
),
2
,
BigDecimal
.
ROUND_HALF_UP
);
if
(
viewScale
.
compareTo
(
videoScale
)
>=
0
){
//按照高度缩放
BigDecimal
heightScale
=
new
BigDecimal
(
mHeight
+
""
)
...
...
@@ -267,88 +259,45 @@ public class ImageVideoFragment extends Fragment {
width
=
mWidth
;
height
=
new
BigDecimal
(
height
+
""
).
multiply
(
widthScale
).
intValue
();
}
//设置videoview 宽高
ConstraintLayout
.
LayoutParams
lp
=
(
ConstraintLayout
.
LayoutParams
)
mVideoView
.
getLayoutParams
();
lp
.
height
=
height
;
lp
.
width
=
width
;
mVideoView
.
setLayoutParams
(
lp
);
if
(
bannerBean
.
getWidth
()
==
0
||
bannerBean
.
getHeight
()
==
0
){
bannerBean
.
setWidth
(
width
);
bannerBean
.
setHeight
(
height
);
listener
.
updateSize
(
bannerBean
);
}
BannerLogFileUtils
.
writeLog
(
"重新计算屏幕宽高 width="
+
width
+
",height="
+
height
);
return
new
Pair
<>(
width
,
height
);
}
private
void
setVideoUrl
()
{
String
url
=
bannerBean
.
getUrl
();
mUrl
=
url
;
//播放本地视频
try
{
mVideoView
.
setVideoURI
(
Uri
.
parse
(
url
));
}
catch
(
Exception
e
)
{
e
.
printStackTrace
();
}
}
public
void
startPlayer
()
{
if
(
null
!=
mVideoView
)
{
// setWaitLoading();
Log
.
e
(
TAG
,
"startPlayer "
+
currentPosition
);
mVideoView
.
setBackgroundColor
(
Color
.
TRANSPARENT
);
mVideoView
.
seekTo
(
currentPosition
);
mVideoView
.
start
();
}
}
// /**
// * 截取当前进度的截图,作为等待画面
// */
// private void setWaitLoading() {
// if(currentPosition != 0){
// setPicture(currentPosition * 1000,bannerBean.getUrl());
// }
// }
public
void
circulationPlayer
()
{
/*if (null != mVideoView) {
mVideoView.setVideoPath(bannerBean.getUrl());
mVideoView.start();
}*/
sendStartVideoMsg
(
true
);
}
private
void
stopPlayer
()
{
Log
.
e
(
TAG
,
"stopPlayer"
);
if
(
null
!=
mVideoView
)
{
mVideoView
.
stopPlayback
();
//内存泄漏fix
mVideoView
.
setOnCompletionListener
(
null
);
mVideoView
.
setOnErrorListener
(
null
);
mVideoView
.
setOnPreparedListener
(
null
);
mVideoView
.
setOnInfoListener
(
null
);
handler
.
removeCallbacksAndMessages
(
null
);
}
}
public
boolean
isPlaying
()
{
if
(
null
!=
mVideoView
)
{
return
mVideoView
.
isPlaying
();
}
return
false
;
}
public
int
getCurrentPosition
()
{
if
(
null
!=
mVideoView
)
{
return
mVideoView
.
getCurrentPosition
();
}
return
0
;
}
private
void
pausePlayer
()
{
if
(
null
!=
mVideoView
)
{
Log
.
e
(
TAG
,
"pausePlayer 当前进度是"
+
mVideoView
.
getCurrentPosition
());
mVideoView
.
setBackgroundColor
(
getResources
().
getColor
(
R
.
color
.
white
));
mVideoView
.
setAlpha
(
0
);
playerPaused
=
true
;
...
...
@@ -423,7 +372,6 @@ public class ImageVideoFragment extends Fragment {
public
void
onResume
()
{
super
.
onResume
();
if
(
playerPaused
)
{
// startPlayer();
sendStartVideoMsg
();
}
}
...
...
@@ -431,22 +379,22 @@ public class ImageVideoFragment extends Fragment {
@Override
public
void
onPause
()
{
super
.
onPause
();
// pausePlayer();
sendPauseVideoMsg
();
}
@Override
public
void
onDestroyView
()
{
//2021/2/24 记录:这里要判断LoadingImage的存在,否则会回收本地Resource,造成加载错误.本地id标识的图片也会处理为BitmapDrawable
if
(
bannerBean
.
getLoadingImage
()
!=
null
&&
ivWaitLoading
!=
null
)
{
if
(
ivWaitLoading
!=
null
)
{
Drawable
drawable
=
ivWaitLoading
.
getDrawable
();
ivWaitLoading
.
setImageDrawable
(
null
);
if
(
drawable
instanceof
BitmapDrawable
)
{
Bitmap
bm
=
((
BitmapDrawable
)
drawable
).
getBitmap
();
if
(!
bm
.
isRecycled
())
{
bm
.
recycle
();
}
}
//todo bugfixed
// if (drawable instanceof BitmapDrawable) {
// Bitmap bm = ((BitmapDrawable) drawable).getBitmap();
// if (!bm.isRecycled()) {
// bm.recycle();
// }
// }
}
super
.
onDestroyView
();
}
...
...
@@ -454,9 +402,7 @@ public class ImageVideoFragment extends Fragment {
@Override
public
void
onDestroy
()
{
super
.
onDestroy
();
//stopPlayer();
sendStopVideoMsg
();
Log
.
e
(
TAG
,
"onDestroy="
+
bannerBean
.
getUrl
());
}
public
interface
OnVideoCompletionListener
{
...
...
imageVideoBanner/src/main/java/com/widget/imagevideobanner/banner/MyVideoView.java
0 → 100644
View file @
d75937a0
package
com
.
widget
.
imagevideobanner
.
banner
;
import
android.content.Context
;
import
android.util.AttributeSet
;
import
android.widget.VideoView
;
public
class
MyVideoView
extends
VideoView
{
private
int
mVideoWidth
;
private
int
mVideoHeight
;
public
MyVideoView
(
Context
context
,
AttributeSet
attrs
)
{
super
(
context
,
attrs
);
}
public
MyVideoView
(
Context
context
,
AttributeSet
attrs
,
int
defStyle
)
{
super
(
context
,
attrs
,
defStyle
);
}
public
MyVideoView
(
Context
context
)
{
super
(
context
);
}
public
void
setVideoSize
(
int
width
,
int
height
)
{
mVideoWidth
=
width
;
mVideoHeight
=
height
;
}
@Override
protected
void
onMeasure
(
int
widthMeasureSpec
,
int
heightMeasureSpec
)
{
// Log.i("@@@", "onMeasure");
int
width
=
getDefaultSize
(
mVideoWidth
,
widthMeasureSpec
);
int
height
=
getDefaultSize
(
mVideoHeight
,
heightMeasureSpec
);
if
(
mVideoWidth
>
0
&&
mVideoHeight
>
0
)
{
if
(
mVideoWidth
*
height
>
width
*
mVideoHeight
)
{
// Log.i("@@@", "image too tall, correcting");
height
=
width
*
mVideoHeight
/
mVideoWidth
;
}
else
if
(
mVideoWidth
*
height
<
width
*
mVideoHeight
)
{
// Log.i("@@@", "image too wide, correcting");
width
=
height
*
mVideoWidth
/
mVideoHeight
;
}
else
{
// Log.i("@@@", "aspect ratio is correct: " +
// width+"/"+height+"="+
// mVideoWidth+"/"+mVideoHeight);
}
}
// Log.i("@@@", "setting size: " + width + 'x' + height);
setMeasuredDimension
(
width
,
height
);
}
}
imageVideoBanner/src/main/java/com/widget/imagevideobanner/bean/BannerBean.java
View file @
d75937a0
...
...
@@ -5,10 +5,28 @@ import java.io.Serializable;
public
class
BannerBean
implements
Serializable
{
private
String
url
;
private
int
type
;
private
byte
[]
loadingImage
;
private
byte
[]
loadingBlurImage
;
private
int
width
;
private
int
height
;
//视频首帧图片key
private
String
key
;
//视频首帧图片 高斯模糊key
private
String
blurKey
;
public
String
getKey
()
{
return
key
;
}
public
void
setKey
(
String
key
)
{
this
.
key
=
key
;
}
public
String
getBlurKey
()
{
return
blurKey
;
}
public
void
setBlurKey
(
String
blurKey
)
{
this
.
blurKey
=
blurKey
;
}
public
int
getWidth
()
{
return
width
;
...
...
@@ -26,21 +44,6 @@ public class BannerBean implements Serializable {
this
.
height
=
height
;
}
public
byte
[]
getLoadingBlurImage
()
{
return
loadingBlurImage
;
}
public
void
setLoadingBlurImage
(
byte
[]
loadingBlurImage
)
{
this
.
loadingBlurImage
=
loadingBlurImage
;
}
public
byte
[]
getLoadingImage
()
{
return
loadingImage
;
}
public
void
setLoadingImage
(
byte
[]
loadingImage
)
{
this
.
loadingImage
=
loadingImage
;
}
public
String
getUrl
()
{
return
url
;
}
...
...
imageVideoBanner/src/main/java/com/widget/imagevideobanner/cache/BitmapCache.java
0 → 100644
View file @
d75937a0
package
com
.
widget
.
imagevideobanner
.
cache
;
import
android.content.Context
;
import
android.graphics.Bitmap
;
import
android.graphics.BitmapFactory
;
import
android.os.Build
;
import
android.os.Environment
;
import
android.text.TextUtils
;
import
android.util.Log
;
import
android.util.LruCache
;
import
com.widget.imagevideobanner.BuildConfig
;
import
com.widget.imagevideobanner.cache.disk.DiskLruCache
;
import
com.widget.imagevideobanner.log.BannerLogFileUtils
;
import
com.widget.imagevideobanner.utils.BitmapUtils
;
import
com.widget.imagevideobanner.utils.MD5Util
;
import
com.widget.imagevideobanner.utils.ThrowableUtils
;
import
java.io.File
;
import
java.io.IOException
;
import
java.io.InputStream
;
import
java.io.OutputStream
;
import
java.util.HashMap
;
import
java.util.Map
;
import
java.util.concurrent.ExecutorService
;
import
java.util.concurrent.Executors
;
import
wseemann.media.FFmpegMediaMetadataRetriever
;
/**
* 缓存视频的第一帧图片以及高斯模糊的背景图片
*/
public
class
BitmapCache
{
static
final
String
TAG
=
"BitmapCache####"
;
static
final
int
BYTES_PER_ARGB_8888_PIXEL
=
4
;
//经验值 最多不超过5个视频
static
final
int
MEMORY_CACHE_TARGET_SCREENS
=
10
;
static
final
int
BLUR_RADS
=
40
;
static
final
String
BITMAP_CACHE_DIR
=
Environment
.
getExternalStorageDirectory
().
getPath
()
+
File
.
separator
+
"miyaterminal"
+
File
.
separator
+
"download"
+
File
.
separator
;
private
LruCache
<
String
,
Bitmap
>
memoryCache
;
private
DiskLruCache
diskLruCache
;
private
static
BitmapCache
instance
;
private
ExecutorService
executors
;
private
Map
<
String
,
String
>
keyMaps
=
new
HashMap
<>();
public
static
BitmapCache
getInstance
(
Context
context
)
{
if
(
instance
==
null
)
{
synchronized
(
BitmapCache
.
class
)
{
if
(
instance
==
null
)
{
instance
=
new
BitmapCache
(
context
);
}
}
}
return
instance
;
}
public
BitmapCache
(
Context
context
)
{
int
screenSize
=
context
.
getResources
().
getDisplayMetrics
().
widthPixels
*
context
.
getResources
().
getDisplayMetrics
().
heightPixels
*
BYTES_PER_ARGB_8888_PIXEL
;
//todo 其实应该根据真实的视频个数来比较好
int
targetMemoryCacheSize
=
Math
.
round
(
screenSize
*
MEMORY_CACHE_TARGET_SCREENS
)/
1024
;
// int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
// int cacheSize = maxMemory / 8;
//todo 判断是小于最大可用内存
memoryCache
=
new
LruCache
<
String
,
Bitmap
>(
targetMemoryCacheSize
)
{
@Override
protected
int
sizeOf
(
String
key
,
Bitmap
bitmap
)
{
int
size
=
bitmap
.
getByteCount
()
/
1024
;
if
(
Build
.
VERSION
.
SDK_INT
>=
Build
.
VERSION_CODES
.
KITKAT
)
{
size
=
bitmap
.
getAllocationByteCount
()
/
1024
;
}
Log
.
e
(
TAG
,
"已经加入内存缓存 size ="
+
size
);
BannerLogFileUtils
.
writeLog
(
"已经加入内存缓存 size ="
+
size
);
return
size
;
}
@Override
protected
void
entryRemoved
(
boolean
evicted
,
String
key
,
Bitmap
oldValue
,
Bitmap
newValue
)
{
BannerLogFileUtils
.
writeLog
(
"内存缓存移除 key ="
+
key
);
Log
.
e
(
TAG
,
"内存缓存移除 key ="
+
key
);
//todo bitmap复用
oldValue
.
recycle
();
}
};
try
{
File
cacheFile
=
new
File
(
BITMAP_CACHE_DIR
);
if
(!
cacheFile
.
exists
()
&&
!
cacheFile
.
mkdir
())
return
;
diskLruCache
=
DiskLruCache
.
open
(
cacheFile
,
BuildConfig
.
VERSION_CODE
,
1
,
100
*
1024
*
1024
);
}
catch
(
IOException
e
)
{
e
.
printStackTrace
();
}
}
private
void
addBitmapToMemoryCache
(
String
key
,
Bitmap
bitmap
)
{
if
(
getBitmapFromMemCache
(
key
)
==
null
)
{
memoryCache
.
put
(
key
,
bitmap
);
}
}
public
Bitmap
getBitmap
(
String
key
){
if
(
TextUtils
.
isEmpty
(
key
=
keyMaps
.
get
(
key
))){
return
null
;
}
Bitmap
bitmap
=
getBitmapFromMemCache
(
key
);
if
(
bitmap
!=
null
){
return
bitmap
;
}
return
getBitmapFromDisk
(
key
);
}
/**
* 取高斯模糊的缓存图片
* @param key
* @return
*/
public
Bitmap
getBlurBitmap
(
String
key
){
if
(
TextUtils
.
isEmpty
(
key
=
keyMaps
.
get
(
key
+
"blur"
))){
return
null
;
}
Bitmap
bitmap
=
getBitmapFromMemCache
(
key
);
if
(
bitmap
!=
null
){
return
bitmap
;
}
return
getBitmapFromDisk
(
key
);
}
public
void
addCache
(
final
String
url
){
if
(
TextUtils
.
isEmpty
(
url
))
return
;
if
(
getBitmapFromMemCache
(
url
)
!=
null
){
return
;
}
if
(
null
==
executors
)
executors
=
Executors
.
newFixedThreadPool
(
3
);
executors
.
execute
(
new
Runnable
()
{
@Override
public
void
run
()
{
try
{
String
key
=
MD5Util
.
get32Md5
(
url
);
keyMaps
.
put
(
url
,
key
);
String
blurkey
=
MD5Util
.
get32Md5
(
url
+
"blur"
);
keyMaps
.
put
(
url
+
"blur"
,
blurkey
);
//截取视频首帧 TODO 横竖屏方向调整
FFmpegMediaMetadataRetriever
mmr
=
new
FFmpegMediaMetadataRetriever
();
mmr
.
setDataSource
(
url
);
mmr
.
extractMetadata
(
FFmpegMediaMetadataRetriever
.
METADATA_KEY_ALBUM
);
mmr
.
extractMetadata
(
FFmpegMediaMetadataRetriever
.
METADATA_KEY_ARTIST
);
Bitmap
b
=
mmr
.
getFrameAtTime
();
//加入到内存MemeryLrucache TODO 采样率压缩
addBitmapToMemoryCache
(
key
,
b
);
//加入到DiskLrucache TODO 质量压缩
putBitmap2Disk
(
key
,
b
);
//TODO 如果视频比例刚好相等 可以不用背景图片
Bitmap
blurBitmap
=
BitmapUtils
.
createBlurBitmap
(
b
,
BLUR_RADS
);
addBitmapToMemoryCache
(
blurkey
,
blurBitmap
);
putBitmap2Disk
(
blurkey
,
blurBitmap
);
}
catch
(
Exception
e
){
e
.
printStackTrace
();
BannerLogFileUtils
.
writeLog
(
"截取视频首帧出错 url ="
+
url
+
"出错原因是:"
+
ThrowableUtils
.
getFullStackTrace
(
e
));
}
}
});
}
/**
* 从内存缓存获取bitmap
* @param key
* @return
*/
private
Bitmap
getBitmapFromMemCache
(
String
key
)
{
return
memoryCache
.
get
(
key
);
}
/**
* 从磁盘缓存获取bitmap
* @param key
* @return
*/
private
Bitmap
getBitmapFromDisk
(
String
key
)
{
DiskLruCache
.
Snapshot
snapshot
=
null
;
Bitmap
bitmap
=
null
;
try
{
snapshot
=
diskLruCache
.
get
(
key
);
if
(
snapshot
==
null
)
{
return
null
;
}
InputStream
is
=
snapshot
.
getInputStream
(
0
);
BitmapFactory
.
Options
options
=
new
BitmapFactory
.
Options
();
bitmap
=
BitmapFactory
.
decodeStream
(
is
,
null
,
options
);
if
(
bitmap
!=
null
)
{
memoryCache
.
put
(
key
,
bitmap
);
}
}
catch
(
IOException
e
)
{
e
.
printStackTrace
();
}
finally
{
if
(
snapshot
!=
null
)
{
snapshot
.
close
();
}
}
return
bitmap
;
}
/**
* 放入磁盘缓存
*/
private
void
putBitmap2Disk
(
String
key
,
Bitmap
bitmap
)
{
DiskLruCache
.
Snapshot
snapshot
=
null
;
OutputStream
os
=
null
;
try
{
snapshot
=
diskLruCache
.
get
(
key
);
if
(
snapshot
==
null
)
{
DiskLruCache
.
Editor
edit
=
diskLruCache
.
edit
(
key
);
if
(
edit
!=
null
)
{
os
=
edit
.
newOutputStream
(
0
);
bitmap
.
compress
(
Bitmap
.
CompressFormat
.
JPEG
,
50
,
os
);
edit
.
commit
();
}
}
}
catch
(
IOException
e
)
{
e
.
printStackTrace
();
}
finally
{
if
(
snapshot
!=
null
)
{
snapshot
.
close
();
}
if
(
os
!=
null
)
{
try
{
os
.
close
();
}
catch
(
IOException
e
)
{
e
.
printStackTrace
();
}
}
}
}
}
imageVideoBanner/src/main/java/com/widget/imagevideobanner/cache/disk/DiskLruCache.java
0 → 100755
View file @
d75937a0
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package
com
.
widget
.
imagevideobanner
.
cache
.
disk
;
import
java.io.BufferedWriter
;
import
java.io.Closeable
;
import
java.io.EOFException
;
import
java.io.File
;
import
java.io.FileInputStream
;
import
java.io.FileNotFoundException
;
import
java.io.FileOutputStream
;
import
java.io.FilterOutputStream
;
import
java.io.IOException
;
import
java.io.InputStream
;
import
java.io.InputStreamReader
;
import
java.io.OutputStream
;
import
java.io.OutputStreamWriter
;
import
java.io.Writer
;
import
java.util.ArrayList
;
import
java.util.Iterator
;
import
java.util.LinkedHashMap
;
import
java.util.Map
;
import
java.util.concurrent.Callable
;
import
java.util.concurrent.LinkedBlockingQueue
;
import
java.util.concurrent.ThreadPoolExecutor
;
import
java.util.concurrent.TimeUnit
;
import
java.util.regex.Matcher
;
import
java.util.regex.Pattern
;
/**
* A cache that uses a bounded amount of space on a filesystem. Each cache
* entry has a string key and a fixed number of values. Each key must match
* the regex <strong>[a-z0-9_-]{1,120}</strong>. Values are byte sequences,
* accessible as streams or files. Each value must be between {@code 0} and
* {@code Integer.MAX_VALUE} bytes in length.
*
* <p>The cache stores its data in a directory on the filesystem. This
* directory must be exclusive to the cache; the cache may delete or overwrite
* files from its directory. It is an error for multiple processes to use the
* same cache directory at the same time.
*
* <p>This cache limits the number of bytes that it will store on the
* filesystem. When the number of stored bytes exceeds the limit, the cache will
* remove entries in the background until the limit is satisfied. The limit is
* not strict: the cache may temporarily exceed it while waiting for files to be
* deleted. The limit does not include filesystem overhead or the cache
* journal so space-sensitive applications should set a conservative limit.
*
* <p>Clients call {@link #edit} to create or update the values of an entry. An
* entry may have only one editor at one time; if a value is not available to be
* edited then {@link #edit} will return null.
* <ul>
* <li>When an entry is being <strong>created</strong> it is necessary to
* supply a full set of values; the empty value should be used as a
* placeholder if necessary.
* <li>When an entry is being <strong>edited</strong>, it is not necessary
* to supply data for every value; values default to their previous
* value.
* </ul>
* Every {@link #edit} call must be matched by a call to {@link Editor#commit}
* or {@link Editor#abort}. Committing is atomic: a read observes the full set
* of values as they were before or after the commit, but never a mix of values.
*
* <p>Clients call {@link #get} to read a snapshot of an entry. The read will
* observe the value at the time that {@link #get} was called. Updates and
* removals after the call do not impact ongoing reads.
*
* <p>This class is tolerant of some I/O errors. If files are missing from the
* filesystem, the corresponding entries will be dropped from the cache. If
* an error occurs while writing a cache value, the edit will fail silently.
* Callers should handle other problems by catching {@code IOException} and
* responding appropriately.
*/
public
final
class
DiskLruCache
implements
Closeable
{
static
final
String
JOURNAL_FILE
=
"journal"
;
static
final
String
JOURNAL_FILE_TEMP
=
"journal.tmp"
;
static
final
String
JOURNAL_FILE_BACKUP
=
"journal.bkp"
;
static
final
String
MAGIC
=
"libcore.io.DiskLruCache"
;
static
final
String
VERSION_1
=
"1"
;
static
final
long
ANY_SEQUENCE_NUMBER
=
-
1
;
static
final
String
STRING_KEY_PATTERN
=
"[a-z0-9_-]{1,120}"
;
static
final
Pattern
LEGAL_KEY_PATTERN
=
Pattern
.
compile
(
STRING_KEY_PATTERN
);
private
static
final
String
CLEAN
=
"CLEAN"
;
private
static
final
String
DIRTY
=
"DIRTY"
;
private
static
final
String
REMOVE
=
"REMOVE"
;
private
static
final
String
READ
=
"READ"
;
/*
* This cache uses a journal file named "journal". A typical journal file
* looks like this:
* libcore.io.DiskLruCache
* 1
* 100
* 2
*
* CLEAN 3400330d1dfc7f3f7f4b8d4d803dfcf6 832 21054
* DIRTY 335c4c6028171cfddfbaae1a9c313c52
* CLEAN 335c4c6028171cfddfbaae1a9c313c52 3934 2342
* REMOVE 335c4c6028171cfddfbaae1a9c313c52
* DIRTY 1ab96a171faeeee38496d8b330771a7a
* CLEAN 1ab96a171faeeee38496d8b330771a7a 1600 234
* READ 335c4c6028171cfddfbaae1a9c313c52
* READ 3400330d1dfc7f3f7f4b8d4d803dfcf6
*
* The first five lines of the journal form its header. They are the
* constant string "libcore.io.DiskLruCache", the disk cache's version,
* the application's version, the value count, and a blank line.
*
* Each of the subsequent lines in the file is a record of the state of a
* cache entry. Each line contains space-separated values: a state, a key,
* and optional state-specific values.
* o DIRTY lines track that an entry is actively being created or updated.
* Every successful DIRTY action should be followed by a CLEAN or REMOVE
* action. DIRTY lines without a matching CLEAN or REMOVE indicate that
* temporary files may need to be deleted.
* o CLEAN lines track a cache entry that has been successfully published
* and may be read. A publish line is followed by the lengths of each of
* its values.
* o READ lines track accesses for LRU.
* o REMOVE lines track entries that have been deleted.
*
* The journal file is appended to as cache operations occur. The journal may
* occasionally be compacted by dropping redundant lines. A temporary file named
* "journal.tmp" will be used during compaction; that file should be deleted if
* it exists when the cache is opened.
*/
private
final
File
directory
;
private
final
File
journalFile
;
private
final
File
journalFileTmp
;
private
final
File
journalFileBackup
;
private
final
int
appVersion
;
private
long
maxSize
;
private
final
int
valueCount
;
private
long
size
=
0
;
private
Writer
journalWriter
;
private
final
LinkedHashMap
<
String
,
Entry
>
lruEntries
=
new
LinkedHashMap
<
String
,
Entry
>(
0
,
0.75f
,
true
);
private
int
redundantOpCount
;
/**
* To differentiate between old and current snapshots, each entry is given
* a sequence number each time an edit is committed. A snapshot is stale if
* its sequence number is not equal to its entry's sequence number.
*/
private
long
nextSequenceNumber
=
0
;
/** This cache uses a single background thread to evict entries. */
final
ThreadPoolExecutor
executorService
=
new
ThreadPoolExecutor
(
0
,
1
,
60L
,
TimeUnit
.
SECONDS
,
new
LinkedBlockingQueue
<
Runnable
>());
private
final
Callable
<
Void
>
cleanupCallable
=
new
Callable
<
Void
>()
{
public
Void
call
()
throws
Exception
{
synchronized
(
DiskLruCache
.
this
)
{
if
(
journalWriter
==
null
)
{
return
null
;
// Closed.
}
trimToSize
();
if
(
journalRebuildRequired
())
{
rebuildJournal
();
redundantOpCount
=
0
;
}
}
return
null
;
}
};
private
DiskLruCache
(
File
directory
,
int
appVersion
,
int
valueCount
,
long
maxSize
)
{
this
.
directory
=
directory
;
this
.
appVersion
=
appVersion
;
this
.
journalFile
=
new
File
(
directory
,
JOURNAL_FILE
);
this
.
journalFileTmp
=
new
File
(
directory
,
JOURNAL_FILE_TEMP
);
this
.
journalFileBackup
=
new
File
(
directory
,
JOURNAL_FILE_BACKUP
);
this
.
valueCount
=
valueCount
;
this
.
maxSize
=
maxSize
;
}
/**
* Opens the cache in {@code directory}, creating a cache if none exists
* there.
*
* @param directory a writable directory
* @param valueCount the number of values per cache entry. Must be positive.
* @param maxSize the maximum number of bytes this cache should use to store
* @throws IOException if reading or writing the cache directory fails
*/
public
static
DiskLruCache
open
(
File
directory
,
int
appVersion
,
int
valueCount
,
long
maxSize
)
throws
IOException
{
if
(
maxSize
<=
0
)
{
throw
new
IllegalArgumentException
(
"maxSize <= 0"
);
}
if
(
valueCount
<=
0
)
{
throw
new
IllegalArgumentException
(
"valueCount <= 0"
);
}
// If a bkp file exists, use it instead.
File
backupFile
=
new
File
(
directory
,
JOURNAL_FILE_BACKUP
);
if
(
backupFile
.
exists
())
{
File
journalFile
=
new
File
(
directory
,
JOURNAL_FILE
);
// If journal file also exists just delete backup file.
if
(
journalFile
.
exists
())
{
backupFile
.
delete
();
}
else
{
renameTo
(
backupFile
,
journalFile
,
false
);
}
}
// Prefer to pick up where we left off.
DiskLruCache
cache
=
new
DiskLruCache
(
directory
,
appVersion
,
valueCount
,
maxSize
);
if
(
cache
.
journalFile
.
exists
())
{
try
{
cache
.
readJournal
();
cache
.
processJournal
();
return
cache
;
}
catch
(
IOException
journalIsCorrupt
)
{
System
.
out
.
println
(
"DiskLruCache "
+
directory
+
" is corrupt: "
+
journalIsCorrupt
.
getMessage
()
+
", removing"
);
cache
.
delete
();
}
}
// Create a new empty cache.
directory
.
mkdirs
();
cache
=
new
DiskLruCache
(
directory
,
appVersion
,
valueCount
,
maxSize
);
cache
.
rebuildJournal
();
return
cache
;
}
private
void
readJournal
()
throws
IOException
{
StrictLineReader
reader
=
new
StrictLineReader
(
new
FileInputStream
(
journalFile
),
Util
.
US_ASCII
);
try
{
String
magic
=
reader
.
readLine
();
String
version
=
reader
.
readLine
();
String
appVersionString
=
reader
.
readLine
();
String
valueCountString
=
reader
.
readLine
();
String
blank
=
reader
.
readLine
();
if
(!
MAGIC
.
equals
(
magic
)
||
!
VERSION_1
.
equals
(
version
)
||
!
Integer
.
toString
(
appVersion
).
equals
(
appVersionString
)
||
!
Integer
.
toString
(
valueCount
).
equals
(
valueCountString
)
||
!
""
.
equals
(
blank
))
{
throw
new
IOException
(
"unexpected journal header: ["
+
magic
+
", "
+
version
+
", "
+
valueCountString
+
", "
+
blank
+
"]"
);
}
int
lineCount
=
0
;
while
(
true
)
{
try
{
readJournalLine
(
reader
.
readLine
());
lineCount
++;
}
catch
(
EOFException
endOfJournal
)
{
break
;
}
}
redundantOpCount
=
lineCount
-
lruEntries
.
size
();
// If we ended on a truncated line, rebuild the journal before appending to it.
if
(
reader
.
hasUnterminatedLine
())
{
rebuildJournal
();
}
else
{
journalWriter
=
new
BufferedWriter
(
new
OutputStreamWriter
(
new
FileOutputStream
(
journalFile
,
true
),
Util
.
US_ASCII
));
}
}
finally
{
Util
.
closeQuietly
(
reader
);
}
}
private
void
readJournalLine
(
String
line
)
throws
IOException
{
int
firstSpace
=
line
.
indexOf
(
' '
);
if
(
firstSpace
==
-
1
)
{
throw
new
IOException
(
"unexpected journal line: "
+
line
);
}
int
keyBegin
=
firstSpace
+
1
;
int
secondSpace
=
line
.
indexOf
(
' '
,
keyBegin
);
final
String
key
;
if
(
secondSpace
==
-
1
)
{
key
=
line
.
substring
(
keyBegin
);
if
(
firstSpace
==
REMOVE
.
length
()
&&
line
.
startsWith
(
REMOVE
))
{
lruEntries
.
remove
(
key
);
return
;
}
}
else
{
key
=
line
.
substring
(
keyBegin
,
secondSpace
);
}
Entry
entry
=
lruEntries
.
get
(
key
);
if
(
entry
==
null
)
{
entry
=
new
Entry
(
key
);
lruEntries
.
put
(
key
,
entry
);
}
if
(
secondSpace
!=
-
1
&&
firstSpace
==
CLEAN
.
length
()
&&
line
.
startsWith
(
CLEAN
))
{
String
[]
parts
=
line
.
substring
(
secondSpace
+
1
).
split
(
" "
);
entry
.
readable
=
true
;
entry
.
currentEditor
=
null
;
entry
.
setLengths
(
parts
);
}
else
if
(
secondSpace
==
-
1
&&
firstSpace
==
DIRTY
.
length
()
&&
line
.
startsWith
(
DIRTY
))
{
entry
.
currentEditor
=
new
Editor
(
entry
);
}
else
if
(
secondSpace
==
-
1
&&
firstSpace
==
READ
.
length
()
&&
line
.
startsWith
(
READ
))
{
// This work was already done by calling lruEntries.get().
}
else
{
throw
new
IOException
(
"unexpected journal line: "
+
line
);
}
}
/**
* Computes the initial size and collects garbage as a part of opening the
* cache. Dirty entries are assumed to be inconsistent and will be deleted.
*/
private
void
processJournal
()
throws
IOException
{
deleteIfExists
(
journalFileTmp
);
for
(
Iterator
<
Entry
>
i
=
lruEntries
.
values
().
iterator
();
i
.
hasNext
();
)
{
Entry
entry
=
i
.
next
();
if
(
entry
.
currentEditor
==
null
)
{
for
(
int
t
=
0
;
t
<
valueCount
;
t
++)
{
size
+=
entry
.
lengths
[
t
];
}
}
else
{
entry
.
currentEditor
=
null
;
for
(
int
t
=
0
;
t
<
valueCount
;
t
++)
{
deleteIfExists
(
entry
.
getCleanFile
(
t
));
deleteIfExists
(
entry
.
getDirtyFile
(
t
));
}
i
.
remove
();
}
}
}
/**
* Creates a new journal that omits redundant information. This replaces the
* current journal if it exists.
*/
private
synchronized
void
rebuildJournal
()
throws
IOException
{
if
(
journalWriter
!=
null
)
{
journalWriter
.
close
();
}
Writer
writer
=
new
BufferedWriter
(
new
OutputStreamWriter
(
new
FileOutputStream
(
journalFileTmp
),
Util
.
US_ASCII
));
try
{
writer
.
write
(
MAGIC
);
writer
.
write
(
"\n"
);
writer
.
write
(
VERSION_1
);
writer
.
write
(
"\n"
);
writer
.
write
(
Integer
.
toString
(
appVersion
));
writer
.
write
(
"\n"
);
writer
.
write
(
Integer
.
toString
(
valueCount
));
writer
.
write
(
"\n"
);
writer
.
write
(
"\n"
);
for
(
Entry
entry
:
lruEntries
.
values
())
{
if
(
entry
.
currentEditor
!=
null
)
{
writer
.
write
(
DIRTY
+
' '
+
entry
.
key
+
'\n'
);
}
else
{
writer
.
write
(
CLEAN
+
' '
+
entry
.
key
+
entry
.
getLengths
()
+
'\n'
);
}
}
}
finally
{
writer
.
close
();
}
if
(
journalFile
.
exists
())
{
renameTo
(
journalFile
,
journalFileBackup
,
true
);
}
renameTo
(
journalFileTmp
,
journalFile
,
false
);
journalFileBackup
.
delete
();
journalWriter
=
new
BufferedWriter
(
new
OutputStreamWriter
(
new
FileOutputStream
(
journalFile
,
true
),
Util
.
US_ASCII
));
}
private
static
void
deleteIfExists
(
File
file
)
throws
IOException
{
if
(
file
.
exists
()
&&
!
file
.
delete
())
{
throw
new
IOException
();
}
}
private
static
void
renameTo
(
File
from
,
File
to
,
boolean
deleteDestination
)
throws
IOException
{
if
(
deleteDestination
)
{
deleteIfExists
(
to
);
}
if
(!
from
.
renameTo
(
to
))
{
throw
new
IOException
();
}
}
/**
* Returns a snapshot of the entry named {@code key}, or null if it doesn't
* exist is not currently readable. If a value is returned, it is moved to
* the head of the LRU queue.
*/
public
synchronized
Snapshot
get
(
String
key
)
throws
IOException
{
checkNotClosed
();
validateKey
(
key
);
Entry
entry
=
lruEntries
.
get
(
key
);
if
(
entry
==
null
)
{
return
null
;
}
if
(!
entry
.
readable
)
{
return
null
;
}
// Open all streams eagerly to guarantee that we see a single published
// snapshot. If we opened streams lazily then the streams could come
// from different edits.
InputStream
[]
ins
=
new
InputStream
[
valueCount
];
try
{
for
(
int
i
=
0
;
i
<
valueCount
;
i
++)
{
ins
[
i
]
=
new
FileInputStream
(
entry
.
getCleanFile
(
i
));
}
}
catch
(
FileNotFoundException
e
)
{
// A file must have been deleted manually!
for
(
int
i
=
0
;
i
<
valueCount
;
i
++)
{
if
(
ins
[
i
]
!=
null
)
{
Util
.
closeQuietly
(
ins
[
i
]);
}
else
{
break
;
}
}
return
null
;
}
redundantOpCount
++;
journalWriter
.
append
(
READ
+
' '
+
key
+
'\n'
);
if
(
journalRebuildRequired
())
{
executorService
.
submit
(
cleanupCallable
);
}
return
new
Snapshot
(
key
,
entry
.
sequenceNumber
,
ins
,
entry
.
lengths
);
}
/**
* Returns an editor for the entry named {@code key}, or null if another
* edit is in progress.
*/
public
Editor
edit
(
String
key
)
throws
IOException
{
return
edit
(
key
,
ANY_SEQUENCE_NUMBER
);
}
private
synchronized
Editor
edit
(
String
key
,
long
expectedSequenceNumber
)
throws
IOException
{
checkNotClosed
();
validateKey
(
key
);
Entry
entry
=
lruEntries
.
get
(
key
);
if
(
expectedSequenceNumber
!=
ANY_SEQUENCE_NUMBER
&&
(
entry
==
null
||
entry
.
sequenceNumber
!=
expectedSequenceNumber
))
{
return
null
;
// Snapshot is stale.
}
if
(
entry
==
null
)
{
entry
=
new
Entry
(
key
);
lruEntries
.
put
(
key
,
entry
);
}
else
if
(
entry
.
currentEditor
!=
null
)
{
return
null
;
// Another edit is in progress.
}
Editor
editor
=
new
Editor
(
entry
);
entry
.
currentEditor
=
editor
;
// Flush the journal before creating files to prevent file leaks.
journalWriter
.
write
(
DIRTY
+
' '
+
key
+
'\n'
);
journalWriter
.
flush
();
return
editor
;
}
/** Returns the directory where this cache stores its data. */
public
File
getDirectory
()
{
return
directory
;
}
/**
* Returns the maximum number of bytes that this cache should use to store
* its data.
*/
public
synchronized
long
getMaxSize
()
{
return
maxSize
;
}
/**
* Changes the maximum number of bytes the cache can store and queues a job
* to trim the existing store, if necessary.
*/
public
synchronized
void
setMaxSize
(
long
maxSize
)
{
this
.
maxSize
=
maxSize
;
executorService
.
submit
(
cleanupCallable
);
}
/**
* Returns the number of bytes currently being used to store the values in
* this cache. This may be greater than the max size if a background
* deletion is pending.
*/
public
synchronized
long
size
()
{
return
size
;
}
private
synchronized
void
completeEdit
(
Editor
editor
,
boolean
success
)
throws
IOException
{
Entry
entry
=
editor
.
entry
;
if
(
entry
.
currentEditor
!=
editor
)
{
throw
new
IllegalStateException
();
}
// If this edit is creating the entry for the first time, every index must have a value.
if
(
success
&&
!
entry
.
readable
)
{
for
(
int
i
=
0
;
i
<
valueCount
;
i
++)
{
if
(!
editor
.
written
[
i
])
{
editor
.
abort
();
throw
new
IllegalStateException
(
"Newly created entry didn't create value for index "
+
i
);
}
if
(!
entry
.
getDirtyFile
(
i
).
exists
())
{
editor
.
abort
();
return
;
}
}
}
for
(
int
i
=
0
;
i
<
valueCount
;
i
++)
{
File
dirty
=
entry
.
getDirtyFile
(
i
);
if
(
success
)
{
if
(
dirty
.
exists
())
{
File
clean
=
entry
.
getCleanFile
(
i
);
dirty
.
renameTo
(
clean
);
long
oldLength
=
entry
.
lengths
[
i
];
long
newLength
=
clean
.
length
();
entry
.
lengths
[
i
]
=
newLength
;
size
=
size
-
oldLength
+
newLength
;
}
}
else
{
deleteIfExists
(
dirty
);
}
}
redundantOpCount
++;
entry
.
currentEditor
=
null
;
if
(
entry
.
readable
|
success
)
{
entry
.
readable
=
true
;
journalWriter
.
write
(
CLEAN
+
' '
+
entry
.
key
+
entry
.
getLengths
()
+
'\n'
);
if
(
success
)
{
entry
.
sequenceNumber
=
nextSequenceNumber
++;
}
}
else
{
lruEntries
.
remove
(
entry
.
key
);
journalWriter
.
write
(
REMOVE
+
' '
+
entry
.
key
+
'\n'
);
}
journalWriter
.
flush
();
if
(
size
>
maxSize
||
journalRebuildRequired
())
{
executorService
.
submit
(
cleanupCallable
);
}
}
/**
* We only rebuild the journal when it will halve the size of the journal
* and eliminate at least 2000 ops.
*/
private
boolean
journalRebuildRequired
()
{
final
int
redundantOpCompactThreshold
=
2000
;
return
redundantOpCount
>=
redundantOpCompactThreshold
//
&&
redundantOpCount
>=
lruEntries
.
size
();
}
/**
* Drops the entry for {@code key} if it exists and can be removed. Entries
* actively being edited cannot be removed.
*
* @return true if an entry was removed.
*/
public
synchronized
boolean
remove
(
String
key
)
throws
IOException
{
checkNotClosed
();
validateKey
(
key
);
Entry
entry
=
lruEntries
.
get
(
key
);
if
(
entry
==
null
||
entry
.
currentEditor
!=
null
)
{
return
false
;
}
for
(
int
i
=
0
;
i
<
valueCount
;
i
++)
{
File
file
=
entry
.
getCleanFile
(
i
);
if
(
file
.
exists
()
&&
!
file
.
delete
())
{
throw
new
IOException
(
"failed to delete "
+
file
);
}
size
-=
entry
.
lengths
[
i
];
entry
.
lengths
[
i
]
=
0
;
}
redundantOpCount
++;
journalWriter
.
append
(
REMOVE
+
' '
+
key
+
'\n'
);
lruEntries
.
remove
(
key
);
if
(
journalRebuildRequired
())
{
executorService
.
submit
(
cleanupCallable
);
}
return
true
;
}
/** Returns true if this cache has been closed. */
public
synchronized
boolean
isClosed
()
{
return
journalWriter
==
null
;
}
private
void
checkNotClosed
()
{
if
(
journalWriter
==
null
)
{
throw
new
IllegalStateException
(
"cache is closed"
);
}
}
/** Force buffered operations to the filesystem. */
public
synchronized
void
flush
()
throws
IOException
{
checkNotClosed
();
trimToSize
();
journalWriter
.
flush
();
}
/** Closes this cache. Stored values will remain on the filesystem. */
public
synchronized
void
close
()
throws
IOException
{
if
(
journalWriter
==
null
)
{
return
;
// Already closed.
}
for
(
Entry
entry
:
new
ArrayList
<
Entry
>(
lruEntries
.
values
()))
{
if
(
entry
.
currentEditor
!=
null
)
{
entry
.
currentEditor
.
abort
();
}
}
trimToSize
();
journalWriter
.
close
();
journalWriter
=
null
;
}
private
void
trimToSize
()
throws
IOException
{
while
(
size
>
maxSize
)
{
Map
.
Entry
<
String
,
Entry
>
toEvict
=
lruEntries
.
entrySet
().
iterator
().
next
();
remove
(
toEvict
.
getKey
());
}
}
/**
* Closes the cache and deletes all of its stored values. This will delete
* all files in the cache directory including files that weren't created by
* the cache.
*/
public
void
delete
()
throws
IOException
{
close
();
Util
.
deleteContents
(
directory
);
}
private
void
validateKey
(
String
key
)
{
Matcher
matcher
=
LEGAL_KEY_PATTERN
.
matcher
(
key
);
if
(!
matcher
.
matches
())
{
throw
new
IllegalArgumentException
(
"keys must match regex "
+
STRING_KEY_PATTERN
+
": \""
+
key
+
"\""
);
}
}
private
static
String
inputStreamToString
(
InputStream
in
)
throws
IOException
{
return
Util
.
readFully
(
new
InputStreamReader
(
in
,
Util
.
UTF_8
));
}
/** A snapshot of the values for an entry. */
public
final
class
Snapshot
implements
Closeable
{
private
final
String
key
;
private
final
long
sequenceNumber
;
private
final
InputStream
[]
ins
;
private
final
long
[]
lengths
;
private
Snapshot
(
String
key
,
long
sequenceNumber
,
InputStream
[]
ins
,
long
[]
lengths
)
{
this
.
key
=
key
;
this
.
sequenceNumber
=
sequenceNumber
;
this
.
ins
=
ins
;
this
.
lengths
=
lengths
;
}
/**
* Returns an editor for this snapshot's entry, or null if either the
* entry has changed since this snapshot was created or if another edit
* is in progress.
*/
public
Editor
edit
()
throws
IOException
{
return
DiskLruCache
.
this
.
edit
(
key
,
sequenceNumber
);
}
/** Returns the unbuffered stream with the value for {@code index}. */
public
InputStream
getInputStream
(
int
index
)
{
return
ins
[
index
];
}
/** Returns the string value for {@code index}. */
public
String
getString
(
int
index
)
throws
IOException
{
return
inputStreamToString
(
getInputStream
(
index
));
}
/** Returns the byte length of the value for {@code index}. */
public
long
getLength
(
int
index
)
{
return
lengths
[
index
];
}
public
void
close
()
{
for
(
InputStream
in
:
ins
)
{
Util
.
closeQuietly
(
in
);
}
}
}
private
static
final
OutputStream
NULL_OUTPUT_STREAM
=
new
OutputStream
()
{
@Override
public
void
write
(
int
b
)
throws
IOException
{
// Eat all writes silently. Nom nom.
}
};
/** Edits the values for an entry. */
public
final
class
Editor
{
private
final
Entry
entry
;
private
final
boolean
[]
written
;
private
boolean
hasErrors
;
private
boolean
committed
;
private
Editor
(
Entry
entry
)
{
this
.
entry
=
entry
;
this
.
written
=
(
entry
.
readable
)
?
null
:
new
boolean
[
valueCount
];
}
/**
* Returns an unbuffered input stream to read the last committed value,
* or null if no value has been committed.
*/
public
InputStream
newInputStream
(
int
index
)
throws
IOException
{
synchronized
(
DiskLruCache
.
this
)
{
if
(
entry
.
currentEditor
!=
this
)
{
throw
new
IllegalStateException
();
}
if
(!
entry
.
readable
)
{
return
null
;
}
try
{
return
new
FileInputStream
(
entry
.
getCleanFile
(
index
));
}
catch
(
FileNotFoundException
e
)
{
return
null
;
}
}
}
/**
* Returns the last committed value as a string, or null if no value
* has been committed.
*/
public
String
getString
(
int
index
)
throws
IOException
{
InputStream
in
=
newInputStream
(
index
);
return
in
!=
null
?
inputStreamToString
(
in
)
:
null
;
}
/**
* Returns a new unbuffered output stream to write the value at
* {@code index}. If the underlying output stream encounters errors
* when writing to the filesystem, this edit will be aborted when
* {@link #commit} is called. The returned output stream does not throw
* IOExceptions.
*/
public
OutputStream
newOutputStream
(
int
index
)
throws
IOException
{
if
(
index
<
0
||
index
>=
valueCount
)
{
throw
new
IllegalArgumentException
(
"Expected index "
+
index
+
" to "
+
"be greater than 0 and less than the maximum value count "
+
"of "
+
valueCount
);
}
synchronized
(
DiskLruCache
.
this
)
{
if
(
entry
.
currentEditor
!=
this
)
{
throw
new
IllegalStateException
();
}
if
(!
entry
.
readable
)
{
written
[
index
]
=
true
;
}
File
dirtyFile
=
entry
.
getDirtyFile
(
index
);
FileOutputStream
outputStream
;
try
{
outputStream
=
new
FileOutputStream
(
dirtyFile
);
}
catch
(
FileNotFoundException
e
)
{
// Attempt to recreate the cache directory.
directory
.
mkdirs
();
try
{
outputStream
=
new
FileOutputStream
(
dirtyFile
);
}
catch
(
FileNotFoundException
e2
)
{
// We are unable to recover. Silently eat the writes.
return
NULL_OUTPUT_STREAM
;
}
}
return
new
FaultHidingOutputStream
(
outputStream
);
}
}
/** Sets the value at {@code index} to {@code value}. */
public
void
set
(
int
index
,
String
value
)
throws
IOException
{
Writer
writer
=
null
;
try
{
writer
=
new
OutputStreamWriter
(
newOutputStream
(
index
),
Util
.
UTF_8
);
writer
.
write
(
value
);
}
finally
{
Util
.
closeQuietly
(
writer
);
}
}
/**
* Commits this edit so it is visible to readers. This releases the
* edit lock so another edit may be started on the same key.
*/
public
void
commit
()
throws
IOException
{
if
(
hasErrors
)
{
completeEdit
(
this
,
false
);
remove
(
entry
.
key
);
// The previous entry is stale.
}
else
{
completeEdit
(
this
,
true
);
}
committed
=
true
;
}
/**
* Aborts this edit. This releases the edit lock so another edit may be
* started on the same key.
*/
public
void
abort
()
throws
IOException
{
completeEdit
(
this
,
false
);
}
public
void
abortUnlessCommitted
()
{
if
(!
committed
)
{
try
{
abort
();
}
catch
(
IOException
ignored
)
{
}
}
}
private
class
FaultHidingOutputStream
extends
FilterOutputStream
{
private
FaultHidingOutputStream
(
OutputStream
out
)
{
super
(
out
);
}
@Override
public
void
write
(
int
oneByte
)
{
try
{
out
.
write
(
oneByte
);
}
catch
(
IOException
e
)
{
hasErrors
=
true
;
}
}
@Override
public
void
write
(
byte
[]
buffer
,
int
offset
,
int
length
)
{
try
{
out
.
write
(
buffer
,
offset
,
length
);
}
catch
(
IOException
e
)
{
hasErrors
=
true
;
}
}
@Override
public
void
close
()
{
try
{
out
.
close
();
}
catch
(
IOException
e
)
{
hasErrors
=
true
;
}
}
@Override
public
void
flush
()
{
try
{
out
.
flush
();
}
catch
(
IOException
e
)
{
hasErrors
=
true
;
}
}
}
}
private
final
class
Entry
{
private
final
String
key
;
/** Lengths of this entry's files. */
private
final
long
[]
lengths
;
/** True if this entry has ever been published. */
private
boolean
readable
;
/** The ongoing edit or null if this entry is not being edited. */
private
Editor
currentEditor
;
/** The sequence number of the most recently committed edit to this entry. */
private
long
sequenceNumber
;
private
Entry
(
String
key
)
{
this
.
key
=
key
;
this
.
lengths
=
new
long
[
valueCount
];
}
public
String
getLengths
()
throws
IOException
{
StringBuilder
result
=
new
StringBuilder
();
for
(
long
size
:
lengths
)
{
result
.
append
(
' '
).
append
(
size
);
}
return
result
.
toString
();
}
/** Set lengths using decimal numbers like "10123". */
private
void
setLengths
(
String
[]
strings
)
throws
IOException
{
if
(
strings
.
length
!=
valueCount
)
{
throw
invalidLengths
(
strings
);
}
try
{
for
(
int
i
=
0
;
i
<
strings
.
length
;
i
++)
{
lengths
[
i
]
=
Long
.
parseLong
(
strings
[
i
]);
}
}
catch
(
NumberFormatException
e
)
{
throw
invalidLengths
(
strings
);
}
}
private
IOException
invalidLengths
(
String
[]
strings
)
throws
IOException
{
throw
new
IOException
(
"unexpected journal line: "
+
java
.
util
.
Arrays
.
toString
(
strings
));
}
public
File
getCleanFile
(
int
i
)
{
return
new
File
(
directory
,
key
+
"."
+
i
);
}
public
File
getDirtyFile
(
int
i
)
{
return
new
File
(
directory
,
key
+
"."
+
i
+
".tmp"
);
}
}
}
imageVideoBanner/src/main/java/com/widget/imagevideobanner/cache/disk/StrictLineReader.java
0 → 100755
View file @
d75937a0
/*
* Copyright (C) 2012 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package
com
.
widget
.
imagevideobanner
.
cache
.
disk
;
import
java.io.ByteArrayOutputStream
;
import
java.io.Closeable
;
import
java.io.EOFException
;
import
java.io.IOException
;
import
java.io.InputStream
;
import
java.io.UnsupportedEncodingException
;
import
java.nio.charset.Charset
;
/**
* Buffers input from an {@link InputStream} for reading lines.
*
* <p>This class is used for buffered reading of lines. For purposes of this class, a line ends
* with "\n" or "\r\n". End of input is reported by throwing {@code EOFException}. Unterminated
* line at end of input is invalid and will be ignored, the caller may use {@code
* hasUnterminatedLine()} to detect it after catching the {@code EOFException}.
*
* <p>This class is intended for reading input that strictly consists of lines, such as line-based
* cache entries or cache journal. Unlike the {@link java.io.BufferedReader} which in conjunction
* with {@link java.io.InputStreamReader} provides similar functionality, this class uses different
* end-of-input reporting and a more restrictive definition of a line.
*
* <p>This class supports only charsets that encode '\r' and '\n' as a single byte with value 13
* and 10, respectively, and the representation of no other character contains these values.
* We currently check in constructor that the charset is one of US-ASCII, UTF-8 and ISO-8859-1.
* The default charset is US_ASCII.
*/
class
StrictLineReader
implements
Closeable
{
private
static
final
byte
CR
=
(
byte
)
'\r'
;
private
static
final
byte
LF
=
(
byte
)
'\n'
;
private
final
InputStream
in
;
private
final
Charset
charset
;
/*
* Buffered data is stored in {@code buf}. As long as no exception occurs, 0 <= pos <= end
* and the data in the range [pos, end) is buffered for reading. At end of input, if there is
* an unterminated line, we set end == -1, otherwise end == pos. If the underlying
* {@code InputStream} throws an {@code IOException}, end may remain as either pos or -1.
*/
private
byte
[]
buf
;
private
int
pos
;
private
int
end
;
/**
* Constructs a new {@code LineReader} with the specified charset and the default capacity.
*
* @param in the {@code InputStream} to read data from.
* @param charset the charset used to decode data. Only US-ASCII, UTF-8 and ISO-8859-1 are
* supported.
* @throws NullPointerException if {@code in} or {@code charset} is null.
* @throws IllegalArgumentException if the specified charset is not supported.
*/
public
StrictLineReader
(
InputStream
in
,
Charset
charset
)
{
this
(
in
,
8192
,
charset
);
}
/**
* Constructs a new {@code LineReader} with the specified capacity and charset.
*
* @param in the {@code InputStream} to read data from.
* @param capacity the capacity of the buffer.
* @param charset the charset used to decode data. Only US-ASCII, UTF-8 and ISO-8859-1 are
* supported.
* @throws NullPointerException if {@code in} or {@code charset} is null.
* @throws IllegalArgumentException if {@code capacity} is negative or zero
* or the specified charset is not supported.
*/
public
StrictLineReader
(
InputStream
in
,
int
capacity
,
Charset
charset
)
{
if
(
in
==
null
||
charset
==
null
)
{
throw
new
NullPointerException
();
}
if
(
capacity
<
0
)
{
throw
new
IllegalArgumentException
(
"capacity <= 0"
);
}
if
(!(
charset
.
equals
(
Util
.
US_ASCII
)))
{
throw
new
IllegalArgumentException
(
"Unsupported encoding"
);
}
this
.
in
=
in
;
this
.
charset
=
charset
;
buf
=
new
byte
[
capacity
];
}
/**
* Closes the reader by closing the underlying {@code InputStream} and
* marking this reader as closed.
*
* @throws IOException for errors when closing the underlying {@code InputStream}.
*/
public
void
close
()
throws
IOException
{
synchronized
(
in
)
{
if
(
buf
!=
null
)
{
buf
=
null
;
in
.
close
();
}
}
}
/**
* Reads the next line. A line ends with {@code "\n"} or {@code "\r\n"},
* this end of line marker is not included in the result.
*
* @return the next line from the input.
* @throws IOException for underlying {@code InputStream} errors.
* @throws EOFException for the end of source stream.
*/
public
String
readLine
()
throws
IOException
{
synchronized
(
in
)
{
if
(
buf
==
null
)
{
throw
new
IOException
(
"LineReader is closed"
);
}
// Read more data if we are at the end of the buffered data.
// Though it's an error to read after an exception, we will let {@code fillBuf()}
// throw again if that happens; thus we need to handle end == -1 as well as end == pos.
if
(
pos
>=
end
)
{
fillBuf
();
}
// Try to find LF in the buffered data and return the line if successful.
for
(
int
i
=
pos
;
i
!=
end
;
++
i
)
{
if
(
buf
[
i
]
==
LF
)
{
int
lineEnd
=
(
i
!=
pos
&&
buf
[
i
-
1
]
==
CR
)
?
i
-
1
:
i
;
String
res
=
new
String
(
buf
,
pos
,
lineEnd
-
pos
,
charset
.
name
());
pos
=
i
+
1
;
return
res
;
}
}
// Let's anticipate up to 80 characters on top of those already read.
ByteArrayOutputStream
out
=
new
ByteArrayOutputStream
(
end
-
pos
+
80
)
{
@Override
public
String
toString
()
{
int
length
=
(
count
>
0
&&
buf
[
count
-
1
]
==
CR
)
?
count
-
1
:
count
;
try
{
return
new
String
(
buf
,
0
,
length
,
charset
.
name
());
}
catch
(
UnsupportedEncodingException
e
)
{
throw
new
AssertionError
(
e
);
// Since we control the charset this will never happen.
}
}
};
while
(
true
)
{
out
.
write
(
buf
,
pos
,
end
-
pos
);
// Mark unterminated line in case fillBuf throws EOFException or IOException.
end
=
-
1
;
fillBuf
();
// Try to find LF in the buffered data and return the line if successful.
for
(
int
i
=
pos
;
i
!=
end
;
++
i
)
{
if
(
buf
[
i
]
==
LF
)
{
if
(
i
!=
pos
)
{
out
.
write
(
buf
,
pos
,
i
-
pos
);
}
pos
=
i
+
1
;
return
out
.
toString
();
}
}
}
}
}
public
boolean
hasUnterminatedLine
()
{
return
end
==
-
1
;
}
/**
* Reads new input data into the buffer. Call only with pos == end or end == -1,
* depending on the desired outcome if the function throws.
*/
private
void
fillBuf
()
throws
IOException
{
int
result
=
in
.
read
(
buf
,
0
,
buf
.
length
);
if
(
result
==
-
1
)
{
throw
new
EOFException
();
}
pos
=
0
;
end
=
result
;
}
}
imageVideoBanner/src/main/java/com/widget/imagevideobanner/cache/disk/Util.java
0 → 100755
View file @
d75937a0
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package
com
.
widget
.
imagevideobanner
.
cache
.
disk
;
import
java.io.Closeable
;
import
java.io.File
;
import
java.io.IOException
;
import
java.io.Reader
;
import
java.io.StringWriter
;
import
java.nio.charset.Charset
;
/** Junk drawer of utility methods. */
final
class
Util
{
static
final
Charset
US_ASCII
=
Charset
.
forName
(
"US-ASCII"
);
static
final
Charset
UTF_8
=
Charset
.
forName
(
"UTF-8"
);
private
Util
()
{
}
static
String
readFully
(
Reader
reader
)
throws
IOException
{
try
{
StringWriter
writer
=
new
StringWriter
();
char
[]
buffer
=
new
char
[
1024
];
int
count
;
while
((
count
=
reader
.
read
(
buffer
))
!=
-
1
)
{
writer
.
write
(
buffer
,
0
,
count
);
}
return
writer
.
toString
();
}
finally
{
reader
.
close
();
}
}
/**
* Deletes the contents of {@code dir}. Throws an IOException if any file
* could not be deleted, or if {@code dir} is not a readable directory.
*/
static
void
deleteContents
(
File
dir
)
throws
IOException
{
File
[]
files
=
dir
.
listFiles
();
if
(
files
==
null
)
{
throw
new
IOException
(
"not a readable directory: "
+
dir
);
}
for
(
File
file
:
files
)
{
if
(
file
.
isDirectory
())
{
deleteContents
(
file
);
}
if
(!
file
.
delete
())
{
throw
new
IOException
(
"failed to delete file: "
+
file
);
}
}
}
static
void
closeQuietly
(
/*Auto*/
Closeable
closeable
)
{
if
(
closeable
!=
null
)
{
try
{
closeable
.
close
();
}
catch
(
RuntimeException
rethrown
)
{
throw
rethrown
;
}
catch
(
Exception
ignored
)
{
}
}
}
}
imageVideoBanner/src/main/java/com/widget/imagevideobanner/log/BannerLogFileUtils.java
0 → 100644
View file @
d75937a0
package
com
.
widget
.
imagevideobanner
.
log
;
import
android.os.Environment
;
import
android.text.TextUtils
;
import
java.io.File
;
import
java.io.FileOutputStream
;
import
java.io.IOException
;
import
java.io.OutputStreamWriter
;
import
java.nio.charset.Charset
;
import
java.text.SimpleDateFormat
;
import
java.util.Date
;
import
java.util.concurrent.ExecutorService
;
import
java.util.concurrent.Executors
;
public
class
BannerLogFileUtils
{
private
static
ExecutorService
sExecutor
;
private
static
SimpleDateFormat
sdf
=
new
SimpleDateFormat
(
"yyyy-MM-dd HH:mm:ss"
);
private
static
final
String
BANNER_LOG_DIR
=
Environment
.
getExternalStorageDirectory
().
getPath
()
+
File
.
separator
+
"miyaterminal"
+
File
.
separator
+
"log"
+
File
.
separator
+
"bannererr.log"
;
static
{
File
logFiles
=
new
File
(
BANNER_LOG_DIR
);
if
(!
logFiles
.
exists
())
{
logFiles
.
mkdirs
();
}
}
public
static
void
writeLog
(
final
String
input
)
{
if
(
sExecutor
==
null
)
{
sExecutor
=
Executors
.
newSingleThreadExecutor
();
}
sExecutor
.
execute
(
new
Runnable
()
{
@Override
public
void
run
()
{
writeFileLog
(
input
);
}
});
}
private
static
void
writeFileLog
(
String
input
)
{
if
(
TextUtils
.
isEmpty
(
input
))
{
return
;
}
input
=
sdf
.
format
(
new
Date
())
+
input
+
"\n"
;
FileOutputStream
fos
=
null
;
OutputStreamWriter
writer
=
null
;
try
{
File
logFile
=
new
File
(
BANNER_LOG_DIR
);
if
(!
logFile
.
exists
()
&&
!
logFile
.
createNewFile
())
return
;
fos
=
new
FileOutputStream
(
logFile
,
true
);
writer
=
new
OutputStreamWriter
(
fos
,
Charset
.
forName
(
"utf-8"
));
writer
.
write
(
input
);
writer
.
flush
();
writer
.
close
();
}
catch
(
Exception
e
)
{
e
.
printStackTrace
();
}
finally
{
if
(
null
!=
fos
)
{
try
{
fos
.
close
();
}
catch
(
IOException
e
)
{
e
.
printStackTrace
();
}
}
if
(
null
!=
writer
)
{
try
{
writer
.
close
();
}
catch
(
IOException
e
)
{
e
.
printStackTrace
();
}
}
}
}
}
imageVideoBanner/src/main/java/com/widget/imagevideobanner/utils/BannerLogFileUtils.java
deleted
100644 → 0
View file @
61e53d36
package
com
.
widget
.
imagevideobanner
.
utils
;
import
android.os.Environment
;
import
android.text.TextUtils
;
import
java.io.File
;
import
java.io.FileNotFoundException
;
import
java.io.FileOutputStream
;
import
java.io.IOException
;
public
class
BannerLogFileUtils
{
/**
* 将播放的错误信息记录到本地
* @param exInfo
* @return
*/
public
static
String
saveInfo2File
(
String
exInfo
)
{
if
(
TextUtils
.
isEmpty
(
exInfo
))
{
return
null
;
}
String
sampleDir
=
"/miyaterminal/log/"
;
String
tmpDir
=
Environment
.
getExternalStorageDirectory
().
toString
()
+
File
.
separator
+
sampleDir
;
File
file
=
new
File
(
tmpDir
);
if
(!
file
.
exists
())
{
file
.
mkdir
();
}
String
logPath
=
tmpDir
+
"bannererr.log"
;
try
{
FileOutputStream
mFileOutputStream
=
new
FileOutputStream
(
logPath
);
mFileOutputStream
.
write
(
exInfo
.
getBytes
());
mFileOutputStream
.
close
();
return
logPath
;
}
catch
(
FileNotFoundException
e
)
{
e
.
printStackTrace
();
}
catch
(
IOException
e
)
{
e
.
printStackTrace
();
}
return
null
;
}
}
imageVideoBanner/src/main/java/com/widget/imagevideobanner/utils/BitmapUtils.java
View file @
d75937a0
...
...
@@ -17,7 +17,7 @@ public class BitmapUtils {
public
static
byte
[]
getBytes
(
Bitmap
bitmap
){
//实例化字节数组输出流
ByteArrayOutputStream
baos
=
new
ByteArrayOutputStream
();
bitmap
.
compress
(
Bitmap
.
CompressFormat
.
PNG
,
0
,
baos
);
//压缩位图
bitmap
.
compress
(
Bitmap
.
CompressFormat
.
PNG
,
10
0
,
baos
);
//压缩位图
return
baos
.
toByteArray
();
//创建分配字节数组
}
...
...
imageVideoBanner/src/main/java/com/widget/imagevideobanner/utils/IMMLeaks.java
0 → 100644
View file @
d75937a0
package
com
.
widget
.
imagevideobanner
.
utils
;
import
android.app.Activity
;
import
android.app.Application
;
import
android.content.Context
;
import
android.content.ContextWrapper
;
import
android.os.Bundle
;
import
android.os.Looper
;
import
android.os.MessageQueue
;
import
android.support.annotation.RequiresApi
;
import
android.util.Log
;
import
android.view.View
;
import
android.view.ViewTreeObserver
;
import
android.view.inputmethod.InputMethodManager
;
import
java.lang.reflect.Field
;
import
java.lang.reflect.InvocationTargetException
;
import
java.lang.reflect.Method
;
import
static
android
.
content
.
Context
.
INPUT_METHOD_SERVICE
;
import
static
android
.
os
.
Build
.
VERSION
.
SDK_INT
;
import
static
android
.
os
.
Build
.
VERSION_CODES
.
KITKAT
;
public
class
IMMLeaks
{
static
class
ReferenceCleaner
implements
MessageQueue
.
IdleHandler
,
View
.
OnAttachStateChangeListener
,
ViewTreeObserver
.
OnGlobalFocusChangeListener
{
private
final
InputMethodManager
inputMethodManager
;
private
final
Field
mHField
;
private
final
Field
mServedViewField
;
private
final
Method
finishInputLockedMethod
;
ReferenceCleaner
(
InputMethodManager
inputMethodManager
,
Field
mHField
,
Field
mServedViewField
,
Method
finishInputLockedMethod
)
{
this
.
inputMethodManager
=
inputMethodManager
;
this
.
mHField
=
mHField
;
this
.
mServedViewField
=
mServedViewField
;
this
.
finishInputLockedMethod
=
finishInputLockedMethod
;
}
@Override
public
void
onGlobalFocusChanged
(
View
oldFocus
,
View
newFocus
)
{
if
(
newFocus
==
null
)
{
return
;
}
if
(
oldFocus
!=
null
)
{
oldFocus
.
removeOnAttachStateChangeListener
(
this
);
}
Looper
.
myQueue
().
removeIdleHandler
(
this
);
newFocus
.
addOnAttachStateChangeListener
(
this
);
}
@Override
public
void
onViewAttachedToWindow
(
View
v
)
{
}
@Override
public
void
onViewDetachedFromWindow
(
View
v
)
{
v
.
removeOnAttachStateChangeListener
(
this
);
Looper
.
myQueue
().
removeIdleHandler
(
this
);
Looper
.
myQueue
().
addIdleHandler
(
this
);
}
@RequiresApi
(
api
=
KITKAT
)
@Override
public
boolean
queueIdle
()
{
clearInputMethodManagerLeak
();
return
false
;
}
@RequiresApi
(
api
=
KITKAT
)
private
void
clearInputMethodManagerLeak
()
{
try
{
Object
lock
=
mHField
.
get
(
inputMethodManager
);
// This is highly dependent on the InputMethodManager implementation.
synchronized
(
lock
)
{
View
servedView
=
(
View
)
mServedViewField
.
get
(
inputMethodManager
);
if
(
servedView
!=
null
)
{
boolean
servedViewAttached
=
servedView
.
getWindowVisibility
()
!=
View
.
GONE
;
if
(
servedViewAttached
)
{
// The view held by the IMM was replaced without a global focus change. Let's make
// sure we get notified when that view detaches.
// Avoid double registration.
servedView
.
removeOnAttachStateChangeListener
(
this
);
servedView
.
addOnAttachStateChangeListener
(
this
);
}
else
{
// servedView is not attached. InputMethodManager is being stupid!
Activity
activity
=
extractActivity
(
servedView
.
getContext
());
if
(
activity
==
null
||
activity
.
getWindow
()
==
null
)
{
// Unlikely case. Let's finish the input anyways.
finishInputLockedMethod
.
invoke
(
inputMethodManager
);
}
else
{
View
decorView
=
activity
.
getWindow
().
peekDecorView
();
boolean
windowAttached
=
decorView
.
getWindowVisibility
()
!=
View
.
GONE
;
if
(!
windowAttached
)
{
finishInputLockedMethod
.
invoke
(
inputMethodManager
);
}
else
{
decorView
.
requestFocusFromTouch
();
}
}
}
}
}
}
catch
(
IllegalAccessException
|
InvocationTargetException
unexpected
)
{
Log
.
e
(
"IMMLeaks"
,
"Unexpected reflection exception"
,
unexpected
);
}
}
private
Activity
extractActivity
(
Context
context
)
{
while
(
true
)
{
if
(
context
instanceof
Application
)
{
return
null
;
}
else
if
(
context
instanceof
Activity
)
{
return
(
Activity
)
context
;
}
else
if
(
context
instanceof
ContextWrapper
)
{
Context
baseContext
=
((
ContextWrapper
)
context
).
getBaseContext
();
// Prevent Stack Overflow.
if
(
baseContext
==
context
)
{
return
null
;
}
context
=
baseContext
;
}
else
{
return
null
;
}
}
}
}
/**
* Fix for https://code.google.com/p/android/issues/detail?id=171190 .
*
* When a view that has focus gets detached, we wait for the main thread to be idle and then
* check if the InputMethodManager is leaking a view. If yes, we tell it that the decor view got
* focus, which is what happens if you press home and come back from recent apps. This replaces
* the reference to the detached view with a reference to the decor view.
* Should be called from {@link Activity#onCreate(android.os.Bundle)} )}.
*/
public
static
void
fixFocusedViewLeak
(
Application
application
)
{
// Don't know about other versions yet.
if
(
SDK_INT
<
KITKAT
||
SDK_INT
>
22
)
{
return
;
}
final
InputMethodManager
inputMethodManager
=
(
InputMethodManager
)
application
.
getSystemService
(
INPUT_METHOD_SERVICE
);
final
Field
mServedViewField
;
final
Field
mHField
;
final
Method
finishInputLockedMethod
;
final
Method
focusInMethod
;
try
{
mServedViewField
=
InputMethodManager
.
class
.
getDeclaredField
(
"mServedView"
);
mServedViewField
.
setAccessible
(
true
);
mHField
=
InputMethodManager
.
class
.
getDeclaredField
(
"mServedView"
);
mHField
.
setAccessible
(
true
);
finishInputLockedMethod
=
InputMethodManager
.
class
.
getDeclaredMethod
(
"finishInputLocked"
);
finishInputLockedMethod
.
setAccessible
(
true
);
focusInMethod
=
InputMethodManager
.
class
.
getDeclaredMethod
(
"focusIn"
,
View
.
class
);
focusInMethod
.
setAccessible
(
true
);
}
catch
(
NoSuchMethodException
|
NoSuchFieldException
unexpected
)
{
Log
.
e
(
"IMMLeaks"
,
"Unexpected reflection exception"
,
unexpected
);
return
;
}
application
.
registerActivityLifecycleCallbacks
(
new
LifecycleCallbacksAdapter
()
{
@Override
public
void
onActivityCreated
(
Activity
activity
,
Bundle
savedInstanceState
)
{
ReferenceCleaner
cleaner
=
new
ReferenceCleaner
(
inputMethodManager
,
mHField
,
mServedViewField
,
finishInputLockedMethod
);
View
rootView
=
activity
.
getWindow
().
getDecorView
().
getRootView
();
ViewTreeObserver
viewTreeObserver
=
rootView
.
getViewTreeObserver
();
viewTreeObserver
.
addOnGlobalFocusChangeListener
(
cleaner
);
}
});
}
}
\ No newline at end of file
imageVideoBanner/src/main/java/com/widget/imagevideobanner/utils/LifecycleCallbacksAdapter.java
0 → 100644
View file @
d75937a0
package
com
.
widget
.
imagevideobanner
.
utils
;
import
android.app.Activity
;
import
android.app.Application
;
import
android.os.Bundle
;
/** Helper to avoid implementing all lifecycle callback methods. */
public
class
LifecycleCallbacksAdapter
implements
Application
.
ActivityLifecycleCallbacks
{
@Override
public
void
onActivityCreated
(
Activity
activity
,
Bundle
savedInstanceState
)
{
}
@Override
public
void
onActivityStarted
(
Activity
activity
)
{
}
@Override
public
void
onActivityResumed
(
Activity
activity
)
{
}
@Override
public
void
onActivityPaused
(
Activity
activity
)
{
}
@Override
public
void
onActivityStopped
(
Activity
activity
)
{
}
@Override
public
void
onActivitySaveInstanceState
(
Activity
activity
,
Bundle
outState
)
{
}
@Override
public
void
onActivityDestroyed
(
Activity
activity
)
{
}
}
\ No newline at end of file
imageVideoBanner/src/main/java/com/widget/imagevideobanner/utils/MD5Util.java
0 → 100755
View file @
d75937a0
package
com
.
widget
.
imagevideobanner
.
utils
;
import
java.io.File
;
import
java.io.FileInputStream
;
import
java.math.BigInteger
;
import
java.security.MessageDigest
;
import
java.security.NoSuchAlgorithmException
;
public
class
MD5Util
{
public
final
static
String
MD5
(
String
s
)
{
byte
[]
btInput
=
s
.
getBytes
();
return
MD5
(
btInput
);
}
public
final
static
String
MD5
(
byte
[]
btInput
)
{
char
hexDigits
[]
=
{
'0'
,
'1'
,
'2'
,
'3'
,
'4'
,
'5'
,
'6'
,
'7'
,
'8'
,
'9'
,
'A'
,
'B'
,
'C'
,
'D'
,
'E'
,
'F'
};
try
{
MessageDigest
mdInst
=
MessageDigest
.
getInstance
(
"MD5"
);
mdInst
.
update
(
btInput
);
byte
[]
md
=
mdInst
.
digest
();
int
j
=
md
.
length
;
char
str
[]
=
new
char
[
j
*
2
];
int
k
=
0
;
for
(
int
i
=
0
;
i
<
j
;
i
++)
{
byte
byte0
=
md
[
i
];
str
[
k
++]
=
hexDigits
[
byte0
>>>
4
&
0xf
];
str
[
k
++]
=
hexDigits
[
byte0
&
0xf
];
}
return
new
String
(
str
);
}
catch
(
Exception
e
)
{
e
.
printStackTrace
();
return
null
;
}
}
private
static
final
String
TAG
=
"MD5Utils"
;
/**
* md5加密
*
* @param plainText 待加密字符串
* @return 加密后32位字符串
*/
public
static
String
get32Md5
(
String
plainText
)
{
try
{
MessageDigest
md
=
MessageDigest
.
getInstance
(
"MD5"
);
md
.
update
(
plainText
.
getBytes
());
byte
b
[]
=
md
.
digest
();
int
i
;
StringBuffer
buf
=
new
StringBuffer
(
""
);
for
(
int
offset
=
0
;
offset
<
b
.
length
;
offset
++)
{
i
=
b
[
offset
];
if
(
i
<
0
)
i
+=
256
;
if
(
i
<
16
)
buf
.
append
(
"0"
);
buf
.
append
(
Integer
.
toHexString
(
i
));
}
//32位加密
return
buf
.
toString
();
}
catch
(
NoSuchAlgorithmException
e
)
{
return
null
;
}
}
/**
* @param plainText 待加密字符串
* @return 加密后16位字符串
*/
public
static
String
get16Md5
(
String
plainText
)
{
return
get32Md5
(
plainText
).
substring
(
8
,
24
);
}
/**
* 获得文件的MD5值
*
* 注意:此方法有问题 有可能是31位
*
* @param file
* @return
*/
public
static
String
getFileMD5
(
File
file
)
{
if
(!
file
.
isFile
())
{
return
null
;
}
MessageDigest
digest
=
null
;
FileInputStream
in
=
null
;
byte
buffer
[]
=
new
byte
[
1024
];
int
len
;
try
{
digest
=
MessageDigest
.
getInstance
(
"MD5"
);
in
=
new
FileInputStream
(
file
);
while
((
len
=
in
.
read
(
buffer
,
0
,
1024
))
!=
-
1
)
{
digest
.
update
(
buffer
,
0
,
len
);
}
in
.
close
();
}
catch
(
Exception
e
)
{
e
.
printStackTrace
();
return
null
;
}
BigInteger
bigInt
=
new
BigInteger
(
1
,
digest
.
digest
());
return
bigInt
.
toString
(
16
);
}
}
imageVideoBanner/src/main/java/com/widget/imagevideobanner/utils/ThrowableUtils.java
0 → 100644
View file @
d75937a0
package
com
.
widget
.
imagevideobanner
.
utils
;
import
java.io.PrintWriter
;
import
java.io.StringWriter
;
import
java.util.ArrayList
;
import
java.util.List
;
import
java.util.StringTokenizer
;
public
class
ThrowableUtils
{
private
static
final
String
LINE_SEP
=
System
.
getProperty
(
"line.separator"
);
private
ThrowableUtils
()
{
throw
new
UnsupportedOperationException
(
"u can't instantiate me..."
);
}
public
static
String
getFullStackTrace
(
Throwable
throwable
)
{
final
List
<
Throwable
>
throwableList
=
new
ArrayList
<>();
while
(
throwable
!=
null
&&
!
throwableList
.
contains
(
throwable
))
{
throwableList
.
add
(
throwable
);
throwable
=
throwable
.
getCause
();
}
final
int
size
=
throwableList
.
size
();
final
List
<
String
>
frames
=
new
ArrayList
<>();
List
<
String
>
nextTrace
=
getStackFrameList
(
throwableList
.
get
(
size
-
1
));
for
(
int
i
=
size
;
--
i
>=
0
;
)
{
final
List
<
String
>
trace
=
nextTrace
;
if
(
i
!=
0
)
{
nextTrace
=
getStackFrameList
(
throwableList
.
get
(
i
-
1
));
removeCommonFrames
(
trace
,
nextTrace
);
}
if
(
i
==
size
-
1
)
{
frames
.
add
(
throwableList
.
get
(
i
).
toString
());
}
else
{
frames
.
add
(
" Caused by: "
+
throwableList
.
get
(
i
).
toString
());
}
frames
.
addAll
(
trace
);
}
StringBuilder
sb
=
new
StringBuilder
();
for
(
final
String
element
:
frames
)
{
sb
.
append
(
element
).
append
(
LINE_SEP
);
}
return
sb
.
toString
();
}
private
static
List
<
String
>
getStackFrameList
(
final
Throwable
throwable
)
{
final
StringWriter
sw
=
new
StringWriter
();
final
PrintWriter
pw
=
new
PrintWriter
(
sw
,
true
);
throwable
.
printStackTrace
(
pw
);
final
String
stackTrace
=
sw
.
toString
();
final
StringTokenizer
frames
=
new
StringTokenizer
(
stackTrace
,
LINE_SEP
);
final
List
<
String
>
list
=
new
ArrayList
<>();
boolean
traceStarted
=
false
;
while
(
frames
.
hasMoreTokens
())
{
final
String
token
=
frames
.
nextToken
();
// Determine if the line starts with <whitespace>at
final
int
at
=
token
.
indexOf
(
"at"
);
if
(
at
!=
-
1
&&
token
.
substring
(
0
,
at
).
trim
().
isEmpty
())
{
traceStarted
=
true
;
list
.
add
(
token
);
}
else
if
(
traceStarted
)
{
break
;
}
}
return
list
;
}
private
static
void
removeCommonFrames
(
final
List
<
String
>
causeFrames
,
final
List
<
String
>
wrapperFrames
)
{
int
causeFrameIndex
=
causeFrames
.
size
()
-
1
;
int
wrapperFrameIndex
=
wrapperFrames
.
size
()
-
1
;
while
(
causeFrameIndex
>=
0
&&
wrapperFrameIndex
>=
0
)
{
// Remove the frame from the cause trace if it is the same
// as in the wrapper trace
final
String
causeFrame
=
causeFrames
.
get
(
causeFrameIndex
);
final
String
wrapperFrame
=
wrapperFrames
.
get
(
wrapperFrameIndex
);
if
(
causeFrame
.
equals
(
wrapperFrame
))
{
causeFrames
.
remove
(
causeFrameIndex
);
}
causeFrameIndex
--;
wrapperFrameIndex
--;
}
}
}
imageVideoBanner/src/main/res/drawable-mdpi/load_fail.png
0 → 100644
View file @
d75937a0
3.7 KB
imageVideoBanner/src/main/res/layout/item_video_view.xml
View file @
d75937a0
...
...
@@ -5,7 +5,7 @@
android:layout_height=
"match_parent"
android:id=
"@+id/cVideoView"
>
<VideoView
<
com.widget.imagevideobanner.banner.My
VideoView
android:id=
"@+id/video_view"
android:layout_width=
"match_parent"
android:layout_height=
"match_parent"
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment