Commit d75937a0 authored by 委座-江's avatar 委座-江

解决部分内存泄漏

增加二级缓存
parent 61e53d36
apply plugin: 'com.android.library' apply plugin: 'com.android.application'
android { android {
compileSdkVersion 28 compileSdkVersion 28
......
...@@ -6,9 +6,9 @@ ...@@ -6,9 +6,9 @@
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<application <application
android:allowBackup="true" android:allowBackup="true"
android:name=".BaseApplication"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:label="@string/app_name" android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round" android:roundIcon="@mipmap/ic_launcher_round"
...@@ -28,7 +28,6 @@ ...@@ -28,7 +28,6 @@
</activity> </activity>
</application> </application>
</manifest> </manifest>
\ No newline at end of file
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;
}
}
...@@ -6,6 +6,7 @@ import android.support.v7.app.AppCompatActivity; ...@@ -6,6 +6,7 @@ import android.support.v7.app.AppCompatActivity;
import com.widget.imagevideobanner.banner.ImageVideoBanner; import com.widget.imagevideobanner.banner.ImageVideoBanner;
import com.widget.imagevideobanner.bean.BannerBean; import com.widget.imagevideobanner.bean.BannerBean;
import com.widget.imagevideobanner.utils.IMMLeaks;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
...@@ -22,17 +23,20 @@ public class MainActivity extends AppCompatActivity { ...@@ -22,17 +23,20 @@ public class MainActivity extends AppCompatActivity {
setDatas(); setDatas();
banner.addData(list); banner.addData(list);
banner.startBanner(); banner.startBanner();
//修复inputManager引发的内存泄漏
IMMLeaks.fixFocusedViewLeak(BaseApplication.getApplication());
} }
private void setDatas() { private void setDatas() {
for (int i = 0; i < 1; i++) { for (int i = 0; i < 2; i++) {
BannerBean listBean = new BannerBean(); BannerBean listBean = new BannerBean();
if (i == 0) { if (i == 0) {
String url = Environment.getExternalStorageDirectory() + "/Video_Compressed_converseBanner_2021_01_05_03_05_11_0.mp4"; String uri = "android.resource://" + getPackageName() + "/" + R.raw.default_guide;
listBean.setUrl(url); listBean.setUrl(uri);
listBean.setType(1); listBean.setType(1);
list.add(listBean); list.add(listBean);
} else if (i == 1) { } else {
String url = Environment.getExternalStorageDirectory() + "/vertical_test.mp4"; String url = Environment.getExternalStorageDirectory() + "/vertical_test.mp4";
listBean.setUrl(url); listBean.setUrl(url);
listBean.setType(1); listBean.setType(1);
......
...@@ -2,7 +2,6 @@ package com.widget.imagevideobanner.banner; ...@@ -2,7 +2,6 @@ package com.widget.imagevideobanner.banner;
import android.content.Context; import android.content.Context;
import android.content.res.TypedArray; import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.media.MediaPlayer; import android.media.MediaPlayer;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
...@@ -13,28 +12,24 @@ import android.support.v4.app.Fragment; ...@@ -13,28 +12,24 @@ import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity; import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentStatePagerAdapter; import android.support.v4.app.FragmentStatePagerAdapter;
import android.text.TextUtils;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.MotionEvent; import android.view.MotionEvent;
import android.view.View; import android.view.View;
import android.widget.FrameLayout; import android.widget.FrameLayout;
import com.bumptech.glide.util.Util;
import com.widget.imagevideobanner.R; import com.widget.imagevideobanner.R;
import com.widget.imagevideobanner.bean.BannerBean; 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.ArrayList;
import java.util.List; import java.util.List;
import java.util.Timer; import java.util.Timer;
import java.util.TimerTask; 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 java.util.concurrent.ScheduledExecutorService;
import wseemann.media.FFmpegMediaMetadataRetriever;
public class ImageVideoBanner extends FrameLayout implements ViewPager.OnPageChangeListener, com.widget.imagevideobanner.banner.ImageVideoFragment.OnVideoCompletionListener { public class ImageVideoBanner extends FrameLayout implements ViewPager.OnPageChangeListener, com.widget.imagevideobanner.banner.ImageVideoFragment.OnVideoCompletionListener {
private static final int UPTATE_VIEWPAGER = 0; private static final int UPTATE_VIEWPAGER = 0;
...@@ -77,8 +72,6 @@ public class ImageVideoBanner extends FrameLayout implements ViewPager.OnPageCha ...@@ -77,8 +72,6 @@ public class ImageVideoBanner extends FrameLayout implements ViewPager.OnPageCha
return true; return true;
} }
}); });
} }
public void replaceData(List<com.widget.imagevideobanner.bean.BannerBean> listBean) { public void replaceData(List<com.widget.imagevideobanner.bean.BannerBean> listBean) {
...@@ -97,56 +90,12 @@ public class ImageVideoBanner extends FrameLayout implements ViewPager.OnPageCha ...@@ -97,56 +90,12 @@ public class ImageVideoBanner extends FrameLayout implements ViewPager.OnPageCha
return; return;
} }
for (BannerBean bannerBean : mList) { for (BannerBean bannerBean : mList) {
if (bannerBean.getType() == 1 && null == bannerBean.getLoadingImage()) { if (bannerBean.getType() == 1) {
setLoadingImages(bannerBean); 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 @Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
...@@ -155,7 +104,6 @@ public class ImageVideoBanner extends FrameLayout implements ViewPager.OnPageCha ...@@ -155,7 +104,6 @@ public class ImageVideoBanner extends FrameLayout implements ViewPager.OnPageCha
@Override @Override
public void onPageSelected(int position) { public void onPageSelected(int position) {
autoCurrIndex = position; autoCurrIndex = position;
// Toast.makeText(context,"position="+position,Toast.LENGTH_SHORT).show();
if (mList.get(position).getType() == 1) { if (mList.get(position).getType() == 1) {
//如果是视频 //如果是视频
stopBanner(); stopBanner();
...@@ -175,7 +123,6 @@ public class ImageVideoBanner extends FrameLayout implements ViewPager.OnPageCha ...@@ -175,7 +123,6 @@ public class ImageVideoBanner extends FrameLayout implements ViewPager.OnPageCha
if (msg.arg1 != 0) { if (msg.arg1 != 0) {
mViewPager.setCurrentItem(msg.arg1); mViewPager.setCurrentItem(msg.arg1);
} else { } else {
//false 当从末页调到首页时,不显示翻页动画效果,
mViewPager.setCurrentItem(msg.arg1, false); mViewPager.setCurrentItem(msg.arg1, false);
} }
break; break;
...@@ -202,7 +149,6 @@ public class ImageVideoBanner extends FrameLayout implements ViewPager.OnPageCha ...@@ -202,7 +149,6 @@ public class ImageVideoBanner extends FrameLayout implements ViewPager.OnPageCha
} }
public void startBanner() { public void startBanner() {
//如果第一页是视频 不用定时器
if (mList.get(0).getType() == 0) { if (mList.get(0).getType() == 0) {
startBanner(period); startBanner(period);
} }
...@@ -211,7 +157,7 @@ public class ImageVideoBanner extends FrameLayout implements ViewPager.OnPageCha ...@@ -211,7 +157,7 @@ public class ImageVideoBanner extends FrameLayout implements ViewPager.OnPageCha
public void startBanner(long delay) { public void startBanner(long delay) {
stopBanner(); stopBanner();
timer = new Timer(); timer = new Timer();
createTimerTask();//创建定时器 createTimerTask();
timer.schedule(timerTask, delay, period); timer.schedule(timerTask, delay, period);
} }
...@@ -233,7 +179,6 @@ public class ImageVideoBanner extends FrameLayout implements ViewPager.OnPageCha ...@@ -233,7 +179,6 @@ public class ImageVideoBanner extends FrameLayout implements ViewPager.OnPageCha
@Override @Override
public void onVideoCompletion(MediaPlayer mp) { public void onVideoCompletion(MediaPlayer mp) {
if (mList.size() == 1) { if (mList.size() == 1) {
//当集合中只有一个视频时,循环播放
mAdapter.getFragment().circulationPlayer(); mAdapter.getFragment().circulationPlayer();
} }
startBanner(0); startBanner(0);
...@@ -279,7 +224,6 @@ public class ImageVideoBanner extends FrameLayout implements ViewPager.OnPageCha ...@@ -279,7 +224,6 @@ public class ImageVideoBanner extends FrameLayout implements ViewPager.OnPageCha
/** /**
* 只有1个视频的时候循环播放 * 只有1个视频的时候循环播放
*
* @return * @return
*/ */
private boolean getLoop() { private boolean getLoop() {
...@@ -307,25 +251,21 @@ public class ImageVideoBanner extends FrameLayout implements ViewPager.OnPageCha ...@@ -307,25 +251,21 @@ public class ImageVideoBanner extends FrameLayout implements ViewPager.OnPageCha
boolean change = compareData(listBean); boolean change = compareData(listBean);
if (!change) { if (!change) {
notifyDataSetChanged(); notifyDataSetChanged();
// startBanner();
return; return;
} }
if (null != listBean) { if (listBean.size() > 0) {
mList.clear(); mList.clear();
addData(listBean); addData(listBean);
//如果第一张是图片 需要开启定时器
if (listBean.get(0).getType() == 0) { if (listBean.get(0).getType() == 0) {
startBanner(); startBanner();
} }
autoCurrIndex = 0; autoCurrIndex = 0;
// startBanner();
} }
} }
/** /**
* 对比数据 * 对比数据
*
* @param listBean * @param listBean
* @return * @return
*/ */
...@@ -348,6 +288,7 @@ public class ImageVideoBanner extends FrameLayout implements ViewPager.OnPageCha ...@@ -348,6 +288,7 @@ public class ImageVideoBanner extends FrameLayout implements ViewPager.OnPageCha
public void addData(List<BannerBean> listBean) { public void addData(List<BannerBean> listBean) {
if (null != listBean) { if (null != listBean) {
mList.clear();
mList.addAll(listBean); mList.addAll(listBean);
} }
initBannerBitmap(); initBannerBitmap();
...@@ -357,7 +298,10 @@ public class ImageVideoBanner extends FrameLayout implements ViewPager.OnPageCha ...@@ -357,7 +298,10 @@ public class ImageVideoBanner extends FrameLayout implements ViewPager.OnPageCha
public ImageVideoFragment getFragment() { public ImageVideoFragment getFragment() {
return fragment; return fragment;
} }
}
public List<BannerBean> getBannerInfos(){
return mList;
} }
} }
...@@ -2,7 +2,6 @@ package com.widget.imagevideobanner.banner; ...@@ -2,7 +2,6 @@ package com.widget.imagevideobanner.banner;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.Color; import android.graphics.Color;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.media.MediaPlayer; import android.media.MediaPlayer;
import android.net.Uri; import android.net.Uri;
...@@ -14,20 +13,24 @@ import android.support.annotation.Nullable; ...@@ -14,20 +13,24 @@ import android.support.annotation.Nullable;
import android.support.constraint.ConstraintLayout; import android.support.constraint.ConstraintLayout;
import android.support.v4.app.Fragment; import android.support.v4.app.Fragment;
import android.util.Log; import android.util.Log;
import android.util.Pair;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.LinearLayout; import android.widget.LinearLayout;
import android.widget.VideoView;
import com.bumptech.glide.Glide; 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.R;
import com.widget.imagevideobanner.bean.BannerBean; 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.BitmapUtils;
import com.widget.imagevideobanner.utils.ThrowableUtils;
import java.io.File;
import java.lang.ref.WeakReference; import java.lang.ref.WeakReference;
import java.math.BigDecimal; import java.math.BigDecimal;
...@@ -36,9 +39,8 @@ public class ImageVideoFragment extends Fragment { ...@@ -36,9 +39,8 @@ public class ImageVideoFragment extends Fragment {
private static final String TAG = ImageVideoFragment.class.getSimpleName(); private static final String TAG = ImageVideoFragment.class.getSimpleName();
private OnVideoCompletionListener listener; private OnVideoCompletionListener listener;
private VideoView mVideoView; private MyVideoView mVideoView;
private BannerBean bannerBean; private BannerBean bannerBean;
// 视频是否循环播放
private boolean loop; private boolean loop;
private ImageView ivWaitLoading; private ImageView ivWaitLoading;
private ConstraintLayout cVideoView; private ConstraintLayout cVideoView;
...@@ -50,21 +52,20 @@ public class ImageVideoFragment extends Fragment { ...@@ -50,21 +52,20 @@ public class ImageVideoFragment extends Fragment {
private static final int START_PLAYER = 0x2001; private static final int START_PLAYER = 0x2001;
private static final int PAUSE_PLAYER = 0x2002; private static final int PAUSE_PLAYER = 0x2002;
private static final int SET_VIDEO_URL = 0x2003; 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异常 * 使用Handler是为了避免出现ANR异常
*/ */
private Handler handler; private Handler handler;
@Override @Override
public void onCreate(@Nullable Bundle savedInstanceState) { public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
Bundle bundle = getArguments(); Bundle bundle = getArguments();
bannerBean = (BannerBean) bundle.getSerializable("bannerBean"); bannerBean = (BannerBean) bundle.getSerializable("bannerBean");
loop = (boolean) bundle.getBoolean("loop"); loop = bundle.getBoolean("loop");
Log.e(TAG, "type=" + bannerBean.getType() + ",url=" + bannerBean.getUrl());
handler = new InnerHandler(this); handler = new InnerHandler(this);
} }
...@@ -77,16 +78,20 @@ public class ImageVideoFragment extends Fragment { ...@@ -77,16 +78,20 @@ public class ImageVideoFragment extends Fragment {
if (type == 0) { if (type == 0) {
view = LayoutInflater.from(getActivity()).inflate(R.layout.item_image_view, container, false); view = LayoutInflater.from(getActivity()).inflate(R.layout.item_image_view, container, false);
ImageView imageView = view.findViewById(R.id.iv); ImageView imageView = view.findViewById(R.id.iv);
File file = new File(bannerBean.getUrl()); Glide.with(container.getContext()).load(bannerBean.getUrl()).listener(new RequestListener<String, GlideDrawable>() {
if (file.exists()) { @Override
Glide.with(container.getContext()).load(file) public boolean onException(Exception e, String model, Target<GlideDrawable> target, boolean isFirstResource) {
.into(imageView); BannerLogFileUtils.writeLog("图片加载出错 url ="+bannerBean.getUrl() + "出错原因是:"+ ThrowableUtils.getFullStackTrace(e));
} else { return false;
Glide.with(container.getContext()).load(bannerBean.getUrl())
.into(imageView);
} }
@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 { } else {
BannerLogFileUtils.writeLog("开始加载视频 url ="+bannerBean.getUrl());
view = LayoutInflater.from(getActivity()).inflate(R.layout.item_video_view, container, false); view = LayoutInflater.from(getActivity()).inflate(R.layout.item_video_view, container, false);
mVideoView = view.findViewById(R.id.video_view); mVideoView = view.findViewById(R.id.video_view);
ivWaitLoading = view.findViewById(R.id.iv_wait_loading_layout); ivWaitLoading = view.findViewById(R.id.iv_wait_loading_layout);
...@@ -102,32 +107,13 @@ public class ImageVideoFragment extends Fragment { ...@@ -102,32 +107,13 @@ public class ImageVideoFragment extends Fragment {
return view; return view;
} }
public void setVideoBackground(){ public void setVideoBackground(){
if (bannerBean.getLoadingImage() == null) { Bitmap bitmap = BitmapCache.getInstance(getContext()).getBlurBitmap(bannerBean.getUrl());
if (bitmap == null ) {
cVideoView.setBackgroundResource(R.drawable.guide_defaut); cVideoView.setBackgroundResource(R.drawable.guide_defaut);
} else { } else {
cVideoView.setBackground(BitmapUtils.bitmap2Drawable(BitmapUtils.getBitmap(bannerBean.getLoadingBlurImage()))); cVideoView.setBackground(BitmapUtils.bitmap2Drawable(bitmap));
}
}
/**
* 设置加载视频的等待图片
*/
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);
} }
} }
...@@ -140,7 +126,6 @@ public class ImageVideoFragment extends Fragment { ...@@ -140,7 +126,6 @@ public class ImageVideoFragment extends Fragment {
return; return;
} }
Log.e("####","草"+width+" "+height);
//设置设置尺寸 //设置设置尺寸
ConstraintLayout.LayoutParams lp2 = (ConstraintLayout.LayoutParams) ivWaitLoading.getLayoutParams(); ConstraintLayout.LayoutParams lp2 = (ConstraintLayout.LayoutParams) ivWaitLoading.getLayoutParams();
lp2.height = height; lp2.height = height;
...@@ -148,7 +133,8 @@ public class ImageVideoFragment extends Fragment { ...@@ -148,7 +133,8 @@ public class ImageVideoFragment extends Fragment {
ivWaitLoading.setLayoutParams(lp2); ivWaitLoading.setLayoutParams(lp2);
//视频未加载完成,使用菊花 //视频未加载完成,使用菊花
if (bannerBean.getLoadingImage() == null) { Bitmap bitmap = BitmapCache.getInstance(getContext()).getBitmap(bannerBean.getUrl());
if (bitmap == null) {
if (bannerBean.getUrl().contains("android.resource")) { if (bannerBean.getUrl().contains("android.resource")) {
ivWaitLoading.setVisibility(View.VISIBLE); ivWaitLoading.setVisibility(View.VISIBLE);
ivWaitLoading.setImageResource(R.drawable.guide_defaut); ivWaitLoading.setImageResource(R.drawable.guide_defaut);
...@@ -159,27 +145,20 @@ public class ImageVideoFragment extends Fragment { ...@@ -159,27 +145,20 @@ public class ImageVideoFragment extends Fragment {
} }
} else {//加载完成使用首帧图片 } else {//加载完成使用首帧图片
ivWaitLoading.setVisibility(View.VISIBLE); ivWaitLoading.setVisibility(View.VISIBLE);
ivWaitLoading.setImageBitmap(BitmapUtils.getBitmap(bannerBean.getLoadingImage())); ivWaitLoading.setImageBitmap(bitmap);
llWaitLoading.setVisibility(View.GONE); llWaitLoading.setVisibility(View.GONE);
} }
} }
private void initData() { private void initData() {
if (null != mVideoView) { if (null != mVideoView) {
mVideoView.setAlpha(0); mVideoView.setAlpha(0);
mVideoView.requestFocus(); mVideoView.requestFocus();
try {
mVideoView.setVideoURI(Uri.parse(bannerBean.getUrl())); mVideoView.setVideoURI(Uri.parse(bannerBean.getUrl()));
} catch (Exception e) {
}
mVideoView.setOnCompletionListener(new MediaPlayer.OnCompletionListener() { mVideoView.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
@Override @Override
public void onCompletion(MediaPlayer mp) { public void onCompletion(MediaPlayer mp) {
// mVideoView.setBackgroundColor(Color.TRANSPARENT);
// mVideoView.setVisibility(View.GONE);
mVideoView.pause(); mVideoView.pause();
if (null != listener) { if (null != listener) {
listener.onVideoCompletion(mp); listener.onVideoCompletion(mp);
...@@ -190,18 +169,13 @@ public class ImageVideoFragment extends Fragment { ...@@ -190,18 +169,13 @@ public class ImageVideoFragment extends Fragment {
mVideoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() { mVideoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
@Override @Override
public void onPrepared(MediaPlayer mp) { public void onPrepared(MediaPlayer mp) {
resizeVideoView(mp.getVideoWidth(),mp.getVideoHeight());
//重新刷新大小
refreshPortraitScreen(mp.getVideoWidth(),mp.getVideoHeight());
mp.setLooping(loop); mp.setLooping(loop);
Log.e(TAG, "视频加载完成" + bannerBean.getUrl());
mp.setOnInfoListener(new MediaPlayer.OnInfoListener() { mp.setOnInfoListener(new MediaPlayer.OnInfoListener() {
@Override @Override
public boolean onInfo(MediaPlayer mp, int what, int extra) { public boolean onInfo(MediaPlayer mp, int what, int extra) {
if (what == MediaPlayer.MEDIA_INFO_VIDEO_RENDERING_START) { if (what == MediaPlayer.MEDIA_INFO_VIDEO_RENDERING_START) {
mVideoView.setAlpha(1); mVideoView.setAlpha(1);
handler.sendEmptyMessage(PROGRESS);
llWaitLoading.setVisibility(View.GONE); llWaitLoading.setVisibility(View.GONE);
ivWaitLoading.setVisibility(View.GONE); ivWaitLoading.setVisibility(View.GONE);
mVideoView.setBackgroundColor(Color.TRANSPARENT); mVideoView.setBackgroundColor(Color.TRANSPARENT);
...@@ -216,9 +190,7 @@ public class ImageVideoFragment extends Fragment { ...@@ -216,9 +190,7 @@ public class ImageVideoFragment extends Fragment {
mVideoView.setOnErrorListener(new MediaPlayer.OnErrorListener() { mVideoView.setOnErrorListener(new MediaPlayer.OnErrorListener() {
@Override @Override
public boolean onError(MediaPlayer mp, int what, int extra) { public boolean onError(MediaPlayer mp, int what, int extra) {
BannerLogFileUtils.writeLog("视频播放出错了-what=" + what + ",extra=" + extra);
BannerLogFileUtils.saveInfo2File("视频播放出错了-what=" + what + ",extra=" + extra);
Log.e(TAG, "视频播放出错了-what=" + what + ",extra=" + extra); Log.e(TAG, "视频播放出错了-what=" + what + ",extra=" + extra);
mVideoView.stopPlayback(); mVideoView.stopPlayback();
if (null != listener) { if (null != listener) {
...@@ -244,16 +216,36 @@ public class ImageVideoFragment extends Fragment { ...@@ -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 mHeight = mVideoView.getHeight();
int mWidth = mVideoView.getWidth(); int mWidth = mVideoView.getWidth();
BigDecimal viewScale = new BigDecimal(mWidth+"") BigDecimal viewScale = new BigDecimal(mWidth+"")
.divide(new BigDecimal(mHeight+""),2, BigDecimal.ROUND_HALF_UP); .divide(new BigDecimal(mHeight+""),2, BigDecimal.ROUND_HALF_UP);
BigDecimal videoScale = new BigDecimal(width+"") BigDecimal videoScale = new BigDecimal(width+"")
.divide(new BigDecimal(height+""),2, BigDecimal.ROUND_HALF_UP); .divide(new BigDecimal(height+""),2, BigDecimal.ROUND_HALF_UP);
if(viewScale.compareTo(videoScale) >= 0){ if(viewScale.compareTo(videoScale) >= 0){
//按照高度缩放 //按照高度缩放
BigDecimal heightScale = new BigDecimal(mHeight+"") BigDecimal heightScale = new BigDecimal(mHeight+"")
...@@ -267,88 +259,45 @@ public class ImageVideoFragment extends Fragment { ...@@ -267,88 +259,45 @@ public class ImageVideoFragment extends Fragment {
width = mWidth; width = mWidth;
height = new BigDecimal(height+"").multiply(widthScale).intValue(); height = new BigDecimal(height+"").multiply(widthScale).intValue();
} }
BannerLogFileUtils.writeLog("重新计算屏幕宽高 width=" + width + ",height=" + height);
//设置videoview 宽高 return new Pair<>(width,height);
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);
}
} }
private void setVideoUrl() { private void setVideoUrl() {
String url = bannerBean.getUrl(); String url = bannerBean.getUrl();
mUrl = url; mUrl = url;
//播放本地视频
try {
mVideoView.setVideoURI(Uri.parse(url)); mVideoView.setVideoURI(Uri.parse(url));
} catch (Exception e) {
e.printStackTrace();
}
} }
public void startPlayer() { public void startPlayer() {
if (null != mVideoView) { if (null != mVideoView) {
// setWaitLoading();
Log.e(TAG, "startPlayer " + currentPosition);
mVideoView.setBackgroundColor(Color.TRANSPARENT); mVideoView.setBackgroundColor(Color.TRANSPARENT);
mVideoView.seekTo(currentPosition); mVideoView.seekTo(currentPosition);
mVideoView.start(); mVideoView.start();
} }
} }
// /**
// * 截取当前进度的截图,作为等待画面
// */
// private void setWaitLoading() {
// if(currentPosition != 0){
// setPicture(currentPosition * 1000,bannerBean.getUrl());
// }
// }
public void circulationPlayer() { public void circulationPlayer() {
/*if (null != mVideoView) {
mVideoView.setVideoPath(bannerBean.getUrl());
mVideoView.start();
}*/
sendStartVideoMsg(true); sendStartVideoMsg(true);
} }
private void stopPlayer() { private void stopPlayer() {
Log.e(TAG, "stopPlayer");
if (null != mVideoView) { if (null != mVideoView) {
mVideoView.stopPlayback(); mVideoView.stopPlayback();
//内存泄漏fix
mVideoView.setOnCompletionListener(null);
mVideoView.setOnErrorListener(null);
mVideoView.setOnPreparedListener(null);
mVideoView.setOnInfoListener(null);
handler.removeCallbacksAndMessages(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() { private void pausePlayer() {
if (null != mVideoView) { if (null != mVideoView) {
Log.e(TAG, "pausePlayer 当前进度是" + mVideoView.getCurrentPosition());
mVideoView.setBackgroundColor(getResources().getColor(R.color.white)); mVideoView.setBackgroundColor(getResources().getColor(R.color.white));
mVideoView.setAlpha(0); mVideoView.setAlpha(0);
playerPaused = true; playerPaused = true;
...@@ -423,7 +372,6 @@ public class ImageVideoFragment extends Fragment { ...@@ -423,7 +372,6 @@ public class ImageVideoFragment extends Fragment {
public void onResume() { public void onResume() {
super.onResume(); super.onResume();
if (playerPaused) { if (playerPaused) {
// startPlayer();
sendStartVideoMsg(); sendStartVideoMsg();
} }
} }
...@@ -431,22 +379,22 @@ public class ImageVideoFragment extends Fragment { ...@@ -431,22 +379,22 @@ public class ImageVideoFragment extends Fragment {
@Override @Override
public void onPause() { public void onPause() {
super.onPause(); super.onPause();
// pausePlayer();
sendPauseVideoMsg(); sendPauseVideoMsg();
} }
@Override @Override
public void onDestroyView() { public void onDestroyView() {
//2021/2/24 记录:这里要判断LoadingImage的存在,否则会回收本地Resource,造成加载错误.本地id标识的图片也会处理为BitmapDrawable //2021/2/24 记录:这里要判断LoadingImage的存在,否则会回收本地Resource,造成加载错误.本地id标识的图片也会处理为BitmapDrawable
if (bannerBean.getLoadingImage() != null && ivWaitLoading != null) { if ( ivWaitLoading != null) {
Drawable drawable = ivWaitLoading.getDrawable(); Drawable drawable = ivWaitLoading.getDrawable();
ivWaitLoading.setImageDrawable(null); ivWaitLoading.setImageDrawable(null);
if (drawable instanceof BitmapDrawable) { //todo bugfixed
Bitmap bm = ((BitmapDrawable) drawable).getBitmap(); // if (drawable instanceof BitmapDrawable) {
if (!bm.isRecycled()) { // Bitmap bm = ((BitmapDrawable) drawable).getBitmap();
bm.recycle(); // if (!bm.isRecycled()) {
} // bm.recycle();
} // }
// }
} }
super.onDestroyView(); super.onDestroyView();
} }
...@@ -454,9 +402,7 @@ public class ImageVideoFragment extends Fragment { ...@@ -454,9 +402,7 @@ public class ImageVideoFragment extends Fragment {
@Override @Override
public void onDestroy() { public void onDestroy() {
super.onDestroy(); super.onDestroy();
//stopPlayer();
sendStopVideoMsg(); sendStopVideoMsg();
Log.e(TAG, "onDestroy=" + bannerBean.getUrl());
} }
public interface OnVideoCompletionListener { public interface OnVideoCompletionListener {
......
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);
}
}
...@@ -5,10 +5,28 @@ import java.io.Serializable; ...@@ -5,10 +5,28 @@ import java.io.Serializable;
public class BannerBean implements Serializable { public class BannerBean implements Serializable {
private String url; private String url;
private int type; private int type;
private byte[] loadingImage;
private byte[] loadingBlurImage;
private int width; private int width;
private int height; 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() { public int getWidth() {
return width; return width;
...@@ -26,21 +44,6 @@ public class BannerBean implements Serializable { ...@@ -26,21 +44,6 @@ public class BannerBean implements Serializable {
this.height = height; 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() { public String getUrl() {
return url; return url;
} }
......
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();
}
}
}
}
}
/*
* 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");
}
}
}
/*
* 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;
}
}
/*
* 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) {
}
}
}
}
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();
}
}
}
}
}
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;
}
}
...@@ -17,7 +17,7 @@ public class BitmapUtils { ...@@ -17,7 +17,7 @@ public class BitmapUtils {
public static byte[] getBytes(Bitmap bitmap){ public static byte[] getBytes(Bitmap bitmap){
//实例化字节数组输出流 //实例化字节数组输出流
ByteArrayOutputStream baos = new ByteArrayOutputStream(); ByteArrayOutputStream baos = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.PNG, 0, baos);//压缩位图 bitmap.compress(Bitmap.CompressFormat.PNG, 100, baos);//压缩位图
return baos.toByteArray();//创建分配字节数组 return baos.toByteArray();//创建分配字节数组
} }
......
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
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
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);
}
}
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--;
}
}
}
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
android:layout_height="match_parent" android:layout_height="match_parent"
android:id="@+id/cVideoView"> android:id="@+id/cVideoView">
<VideoView <com.widget.imagevideobanner.banner.MyVideoView
android:id="@+id/video_view" android:id="@+id/video_view"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment