安卓TV开发(八) 移动智能终端多媒体爬虫技术 获取加载网页视频源
转载请标明出处:http://blog.csdn.net/sk719887916/article/details/40049137,作者:skay
从上一篇学习中,学习了多媒体技术中的怎么去用josup加载一个网页并解析html标签的用法,今天就接着前篇 【安卓TV开发(七) 移动智能终端多媒体之在线解析网页视频源】 的学习。同时也了解下避免安卓内存溢出解决方式和安卓常用的几种UI更新的方式。
一 准备异步加载工具
1 新建 VideoLoaderTask 用来获取视频列表
/** * Represents an asynchronous loaderVideoInfos task used to authenticate the * user. */ public class VideoLoaderTask extends AsyncTask<TvModle, String, List<TvTaiModel>> { @SuppressWarnings("unchecked") @Override protected List<TvTaiModel> doInBackground(TvModle... params) { // TODO Auto-generated method stub return lists = DataVideoManager.getData(params[0]); } @Override protected void onPostExecute(final List<TvTaiModel> resList) { mAuthTask = null; showProgress(false); if (resList != null && resList.size() > 0) { // Log.e(TAG ,success +"--"); adapter = new VideoWallAdapter(VideoInfoActivity.this, 0, resList, mPhotoWall); mPhotoWall.setAdapter(adapter); adapter.notifyDataSetChanged(); } else { Toast.makeText(VideoInfoActivity.this, "失败", Toast.LENGTH_SHORT).show(); } } @Override protected void onCancelled() { mAuthTask = null; showProgress(false); }
此类设计到安卓AsyncTask的用法,需要大家了解此Api,具体原理是利用Thead+ handler机制实现,实际开发中我们更新UI也可以用安卓自带的UI线程runOnUiThread 代码可以如下,具体执行动作在run()实现,不管是用哪种的方式更新UI,必须注意的是主线程不能执行网络耗时操作任务,容易出现ANR,(安卓4.0rom以后
主线程直接不能访问网络)。UI也必须由主线程来更新,子线程无UI操作权限。
1) 利用UI线程
this.runOnUiThread(new Runnable() { @Override public void run() { // TODO Auto-generated method stub } });
2 ) 利用handler发送Message
mHandler = new Handler() { @Override public void handleMessage(Message msg) { super.handleMessage(msg); } };
3) 利用view的post方法
<span style="color: rgb(85, 85, 85); font-family: 'microsoft yahei'; font-size: 14.7368421554565px; line-height: 35px;"> View.post(</span>new Runnable() { @Override public void run() { // TODO Auto-generated method stub } }
)
4 )AsyncTask
AsyncTask.start() 一般用此类获取网络数据,但是本线程不能重复调用,不然会出异常,只有在task没有运行的情况下才能调用start()方法,多个线程同是调用其start() 也会出现线程安全问题。
2 因为网络任务还耗时的因此给Task加一个过渡loading
/** * Shows the progress UI and hides the geimainUI form. */ @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR2) private void showProgress(final boolean show) { // On Honeycomb MR2 we have the ViewPropertyAnimator APIs, which allow // for very easy animations. If available, use these APIs to fade-in // the progress spinner. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) { int shortAnimTime = getResources().getInteger( android.R.integer.config_shortAnimTime); mLoginStatusView.setVisibility(View.VISIBLE); mLoginStatusView.animate().setDuration(shortAnimTime) .alpha(show ? 1 : 0) .setListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { mLoginStatusView.setVisibility(show ? View.VISIBLE : View.GONE); } }); mPhotoWall.setVisibility(View.VISIBLE); mPhotoWall.animate().setDuration(shortAnimTime).alpha(show ? 0 : 1) .setListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { mPhotoWall.setVisibility(show ? View.GONE : View.VISIBLE); } }); } else { // The ViewPropertyAnimator APIs are not available, so simply show // and hide the relevant UI components. mLoginStatusView.setVisibility(show ? View.VISIBLE : View.GONE); mPhotoWall.setVisibility(show ? View.GONE : View.VISIBLE); } }
3 获取到网络数据必定含有图片 为了防止OOM,新建ImageLoader
当然目前已经有开源的图片缓存框架,但是我们必须懂期原理,为了防止图片内存溢出,我们必须合理利用安卓内存,熟悉安卓回收机制的朋友也非常了解安卓强引用和弱引用,所谓的一级缓存和二级缓存,开发中常用的缓存技术,一般是采用合理分配内存和适当过渡承载实现,具体可以获取手机当前的内存大小,合理设置本次图片请求的最大的负载内存,超过了缓存大小 移除使用频率较低的图片键值,一般是去sd卡读取资源,然后再内存,最后才去请求网络数据。当然请求的图片实际尺寸过大会导致oom。因此必要时还需要根据当前屏幕上所要展示ImageView的大小去缩放bitmap,本人平时喜欢绘制bitmap带自自定义控件上,不喜欢用安卓原生的图片控件。其利弊以后我再慢慢道来。
1 ), 具体缓存逻辑如下,
public class ImageLoader { /** * 图片缓存加载器 */ private static LruCache<String, Bitmap> mMemoryCache; /** * ImageLoader。 */ private static ImageLoader mImageLoader; @SuppressLint("NewApi") private ImageLoader() { // 获取应用程序最大可用内存 int maxMemory = (int) Runtime.getRuntime().maxMemory(); int cacheSize = maxMemory / 8; // 设置图片缓存大小为程序最大可用内存的1/8 mMemoryCache = new LruCache<String, Bitmap>(cacheSize) { @SuppressLint("NewApi") @Override protected int sizeOf(String key, Bitmap bitmap) { return bitmap.getByteCount(); } }; } /** * 获取ImageLoader的实例。 * * @return ImageLoader的实例。 */ public static ImageLoader getInstance() { if (mImageLoader == null) { mImageLoader = new ImageLoader(); } return mImageLoader; } /** * 将一张图片存储到LruCache中。 * * @param key * LruCache的键,这里传入图片的URL地址。 * @param bitmap * LruCache的键,这里传入从网络上下载的Bitmap对象。 */ @SuppressLint("NewApi") public void addBitmapToMemoryCache(String key, Bitmap bitmap) { if (getBitmapFromMemoryCache(key) == null) { mMemoryCache.put(key, bitmap); } } /** * 从LruCache中获取一张图片,如果不存在就返回null。 * * @param key * LruCache的键,这里传入图片的URL地址。 * @return 对应传入键的Bitmap对象,或者null。 */ @SuppressLint("NewApi") public Bitmap getBitmapFromMemoryCache(String key) { return mMemoryCache.get(key); } public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth) { // 源图片的宽度 final int width = options.outWidth; int inSampleSize = 1; if (width > reqWidth) { // 计算出实际宽度和目标宽度的比率 final int widthRatio = Math.round((float) width / (float) reqWidth); inSampleSize = widthRatio; } return inSampleSize; } public static Bitmap decodeSampledBitmapFromResource(String pathName, int reqWidth) { // 第一次解析将inJustDecodeBounds设置为true,来获取图片大小 final BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeFile(pathName, options); // 调用上面定义的方法计算inSampleSize值 options.inSampleSize = calculateInSampleSize(options, reqWidth); // 使用获取到的inSampleSize值再次解析图片 options.inJustDecodeBounds = false; return BitmapFactory.decodeFile(pathName, options); } }
4 新建请求图片的异步加载任务
/** * 异步下载图片的任务。 * * @author guolin */ class BitmapWorkerTask extends AsyncTask<String, Void, Bitmap> { /** * 图片的URL地址 */ private String imageUrl; @Override protected Bitmap doInBackground(String... params) { imageUrl = params[0]; // 在后台开始下载图片 Bitmap bitmap = downloadBitmap(params[0]); if (bitmap != null) { // 图片下载完成后缓存到LrcCache中 addBitmapToMemoryCache(params[0], bitmap); } return bitmap; } @Override protected void onPostExecute(Bitmap bitmap) { super.onPostExecute(bitmap); // 根据Tag找到相应的ImageView控件,将下载好的图片显示出来。 ImageView imageView = (ImageView) mPhotoWall.findViewWithTag(imageUrl); if (imageView != null && bitmap != null) { imageView.setImageBitmap(bitmap); } taskCollection.remove(this); } /** * 建立HTTP请求,并获取Bitmap对象。 * * @param imageUrl * 图片的URL地址 * @return 解析后的Bitmap对象 */ private Bitmap downloadBitmap(String imageUrl) { Bitmap bitmap = null; HttpURLConnection con = null; try { URL url = new URL(imageUrl); con = (HttpURLConnection) url.openConnection(); con.setConnectTimeout(5 * 1000); con.setReadTimeout(10 * 1000); bitmap = BitmapFactory.decodeStream(con.getInputStream()); } catch (Exception e) { e.printStackTrace(); } finally { if (con != null) { con.disconnect(); } } return bitmap; } }
二 新建视图 activity
我们可以利用第五篇中实现的UI界面加以利用 点击去请求音悦台MV视频数据。
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_video_info); mContext = getBaseContext(); mImageThumbSize = getResources().getDimensionPixelSize( R.dimen.image_thumbnail_size); mImageThumbSpacing = getResources().getDimensionPixelSize( R.dimen.image_thumbnail_spacing); mLoginStatusView = this.findViewById(R.id.login_status); mLoginStatusMessageView = (TextView) this .findViewById(R.id.login_status_message); mPhotoWall = (GridView) findViewById(R.id.video_info); mPhotoWall.setOnItemClickListener(this); if (getIntent().getExtras() != null) { modle = (TvModle) getIntent().getExtras().getSerializable( "TvModle"); if (modle != null) { mUrl = modle.getUrl(); } } mPhotoWall.getViewTreeObserver().addOnGlobalLayoutListener( new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { final int numColumns = (int) Math.floor(mPhotoWall .getWidth() / (mImageThumbSize + mImageThumbSpacing)); if (numColumns > 0) { int columnWidth = (mPhotoWall.getWidth() / numColumns) - mImageThumbSpacing; //mAdapter.setItemHeight(columnWidth); mPhotoWall.getViewTreeObserver() .removeGlobalOnLayoutListener(this); } } }); /*mUrl = Constant.QQ_TAI_URL;*/ attemptLoader(); } @Override protected void onDestroy() { super.onDestroy(); adapter.cancelAllTasks(); } /** * Attempts to sign in or register the account specified by the login form. * If there are form errors (invalid email, missing fields, etc.), the * errors are presented and no actual login attempt is made. */ public void attemptLoader() { if (mAuthTask != null) { return; } boolean cancel = false; if (cancel) { } else { // Show a progress spinner, and kick off a background task to // perform the user login attempt. showProgress(true); mAuthTask = new VideoLoaderTask(); mAuthTask.execute(modle); } }
三 增加控制逻辑
创建gridView适配器 VideoWallAdapter
public class VideoWallAdapter extends ArrayAdapter<TvTaiModel> implements OnScrollListener { /** * 记录所有正在下载或等待下载的任务。 */ private Set<BitmapWorkerTask> taskCollection; /** * 图片缓存技术的核心类,用于缓存所有下载好的图片,在程序内存达到设定值时会将最少最近使用的图片移除掉。 */ private LruCache<String, Bitmap> mMemoryCache; /** * GridView的实例 */ private GridView mPhotoWall; /** * 第一张可见图片的下标 */ private int mFirstVisibleItem; /** * 一屏有多少张图片可见 */ private int mVisibleItemCount; /** * 记录是否刚打开程序,用于解决进入程序不滚动屏幕,不会下载图片的问题。 */ private boolean isFirstEnter = true; private List< TvTaiModel> lists = null; @SuppressLint("NewApi") public VideoWallAdapter(Context context, int textViewResourceId, List<TvTaiModel> taiModels, GridView photoWall) { super(context, textViewResourceId, taiModels); //super(context, textViewResourceId); lists = taiModels; mPhotoWall = photoWall; taskCollection = new HashSet<BitmapWorkerTask>(); // 获取应用程序最大可用内存 int maxMemory = (int) Runtime.getRuntime().maxMemory(); int cacheSize = maxMemory / 8; // 设置图片缓存大小为程序最大可用内存的1/8 mMemoryCache = new LruCache<String, Bitmap>(cacheSize) { @Override protected int sizeOf(String key, Bitmap bitmap) { return bitmap.getByteCount(); } }; mPhotoWall.setOnScrollListener(this); } @Override public View getView(int position, View convertView, ViewGroup parent) { final String url = lists.get(position).getImg(); final String name = lists.get(position).getTitle(); Log.e("VideoWallAdapter", url); View view; if (convertView == null) { view = LayoutInflater.from(getContext()).inflate(R.layout.vedio_item, null); } else { view = convertView; } final ImageView photo = (ImageView) view.findViewById(R.id.photo); final TextView title = (TextView) view.findViewById(R.id.title); // 给ImageView设置一个Tag,保证异步加载图片时不会乱序 photo.setTag(url); setImageView(url, photo); title.setText(name); return view; }
此时的我们的基础工具类已完成, 接下来 给我们的FocusView所在的acitity注册处理点击事件,用于获取当前TV的视频Url
@Override public void onItemClick(FocusView mFocusView, View focusView, FocusItemModle<TvModle> focusItem, int Postion, int row, int col, long id) { Intent intent = new Intent(); Bundle bundle = new Bundle(); if (focusItem != null && focusItem.getModle() != null) { bundle.putSerializable("TvModle", focusItem.getModle()); } intent.putExtras(bundle); intent.setClass(FocusUIActivity.this, values[0]); startActivity(intent); }
最后运行 效果如下
这样就把音悦台的MV资源给解析出来并展现到我们自己的APP上,下一篇会继续结合本篇的逻辑,实现点击具体视频获取真实地址 播放网络视频功能,欢迎大家阅读,如需转载请标明出处 http://blog.csdn.net/sk719887916/article/details/40049137,欢迎交流分享。
安卓TV开发(八) 移动智能终端多媒体爬虫技术 获取加载网页视频源的更多相关文章
- 安卓TV开发(七) 移动智能终端多媒体之在线解析网页视频源
载请标明出处:http://blog.csdn.net/sk719887916/article/details/40049137,作者:skay 结束了所有UI绘制的学习,智能设备常用的应用音视频类, ...
- 安卓TV开发(十) 智能电视开发之在线视频直播
转载注明出处:http://blog.csdn.net/sk719887916/article/details/46582987 从<安卓TV开发(八) 移动智能终端多媒体之在线加载网页视频源& ...
- 安卓TV开发(五) 移动智能终端UI之实现主流TV焦点可控UI
载请标明出处:http://blog.csdn.net/sk719887916,作者:skay 由于其他网站收录,导致你无法查看本系列原创文章请点击此处 安卓TV开发(四)实现主流智能T ...
- 安卓TV开发(六) 移动智能终端UI之实现类似GridView的焦点控制FocusView框架
转载请标明出处:http://blog.csdn.net/sk719887916/article/details/40045089,作者:skay 前言 安卓TV开发(五) 移动智能终端UI之实现主流 ...
- 安卓Tv开发(一)移动智能电视之焦点控制(触控事件)
前言:移动智能设备的发展,推动了安卓另一个领域,包括智能电视和智能家居,以及可穿戴设备的大量使用,但是这些设备上的开发并不是和传统手机开发一样,特别是焦点控制和用户操作体验风格上有很大的区别,本系列博 ...
- 安卓TV开发(四) 实现主流智能TV视频播放器UI
前言:移动智能设备的发展,推动了安卓另一个领域,包括智能电视和智能家居,以及可穿戴设备的大量使用,但是这些设备上的开发并不是和传统手机开发一样,特别是焦点控制和用户操作体验上有很大的区别,本系列博文主 ...
- 安卓TV开发(三) 移动智能设备之实现主流TV电视盒子焦点可控UI
前言:移动智能设备的发展,推动了安卓另一个领域,包括智能电视和智能家居,以及可穿戴设备的大量使用,但是这些设备上的开发并不是和传统手机开发一样,特别是焦点控制和用户操作体验上有很大的区别,本系列博文主 ...
- 安卓Tv开发(二)移动智能电视之焦点控制(按键事件)
原文:http://blog.csdn.net/sk719887916/article/details/44781475 skay 前言:移动智能设备的发展,推动了安卓另一个领域,包括智能电视和智能家 ...
- 安卓TV开发(概述) 智能电视之视觉设计和体验分析
转载说明出处 :http://blog.csdn.net/sk719887916, 作者:skay 前言:移动智能设备的发展,推动了安卓另一个领域,包括智能电视和智能家居,以及可穿戴设备的大 ...
随机推荐
- MTK8127编译sdk出错解决方法
1.按照源码中sdk目录下的howto_build_SDK.txt参考文档的编译方式 $ cd ~/my-android-git $ . build/envsetup.sh $ lunch sd ...
- linux下的环境变量
环境变量有时候要查找,但是经常忘记有哪些文件,现在做一个总结: /etc/profile 此文件为系统的每个用户设置环境信息,当用户第一次登录时,该文件被执行.并从/e ...
- Android简易实战教程--第十九话《手把手教您监听EditText文本变化,实现抖动和震动的效果》
昨晚写博客太仓促,代码结构有问题,早上测试发现没法监听文本变化!今日更改一下.真心见谅啦,哈哈!主活动的代码已经改好了,看截图这次的确实现了文本监听变化情况. 监听文本输入情况,仅仅限于土司略显low ...
- 【Netty源码解析】NioEventLoop
上一篇博客[Netty源码学习]EventLoopGroup中我们介绍了EventLoopGroup,实际说来EventLoopGroup是EventLoop的一个集合,EventLoop是一个单线程 ...
- 福利:工作经常用到的Mac软件整理(全)
每日更新关注:http://weibo.com/hanjunqiang 新浪微博!iOS开发者交流QQ群: 446310206 前言 这是我个人在工作中会用到的Mac软件,其中包括办公.开发.视频等 ...
- 【移动开发】SharedPreferences的兼容版本
public class SharedPreferencesCompat { private static final String TAG = SharedPreferencesCompat.cla ...
- Editorial Board 、co-editor、ediitor、editor-in-chief的区别
昨天更新掘金APP-IOS之后发现一个比较严重的Bug,联系管理者报告了Bug,中途发现掘金的发布功能需要申请成为co-editor才行. 那么这里科普一下这几个名词: Editorial Board ...
- Android开发学习之路--RxAndroid之操作符
学习了RxAndroid的一些基本知识,上篇文章也试过了RxAndroid的map操作符,接着来学习更多的操作符的功能吧. 操作符就是为了解决对Observable对象的变换的问题,操作符用于 ...
- Java进阶(三十九)Java集合类的排序,查找,替换操作
Java进阶(三十九)Java集合类的排序,查找,替换操作 前言 在Java方向校招过程中,经常会遇到将输入转换为数组的情况,而我们通常使用ArrayList来表示动态数组.获取到ArrayList对 ...
- Android开发学习之路--UI之初体验
之前都是学习Activity,对于布局都没有做过学习,这里就简单学习下吧.下面看下Android Studio下有哪些控件: 这里分为Widgets,Text Fields,Containers,Da ...