[转载]基于node.js实现一个小小的爬虫 - imwtr - 博客园

mikel阅读(1094)

[转载]基于node.js实现一个小小的爬虫 – imwtr – 博客园.

以前一直听说有爬虫这种东西,稍微看了看资料,貌似不是太复杂。

正好了解过node.js,那就基于它来个简单的爬虫。

 

1.本次爬虫目标:

从拉钩招聘网站中找出“前端开发”这一类岗位的信息,并作相应页面分析,提取出特定的几个部分如岗位名称、岗位薪资、岗位所属公司、岗位发布日期等。并将抓取到的这些信息,展现出来。

 

初始拉钩网站上界面信息如下:

 

2.设计方案:

爬虫,实际上就是通过相应的技术,抓取页面上特定的信息。

这里主要抓取上图所示岗位列表部分相关的具体岗位信息。

首先,抓取,就得先有地址url:

http://www.lagou.com/jobs/list_%E5%89%8D%E7%AB%AF%E5%BC%80%E5%8F%91?kd=%E5%89%8D%E7%AB%AF%E5%BC%80%E5%8F%91&spc=1&pl=&gj=&xl=&yx=&gx=&st=&labelWords=label&lc=&workAddress=&city=%E5%85%A8%E5%9B%BD&requestId=&pn=1

这个链接就是岗位列表的第一页的网页地址。

我们通过对地址的参数部分进行分析,先不管其他选择的参数,只看最后的参数值:pn=1

我们的目的是通过page来各个抓取,所以设置为pn = page;

其次,爬虫要获取特定信息,就需要特定代表的标识符。

这里采用分析页面代码标签值、class值、id值来考虑。

通过Firebug对这一小部分审查元素

分析得出将要获取哪些信息则需要对特定的标识符进行处理。

 

3.代码编写:

按照预定的方案,考虑到node.js的使用情况,通过其内置http模块进行页面信息的获取,另外再通过cheerio.js模块对DOM的分析,进而转化为json格式的数据,控制台直接输出或者再次将json数据传送回浏览器端显示出来。

(cheerio.js这东西的用法很简单,详情可以自行搜索一下。其中最主要的也就下边这份代码了,其余的跟JQuery的用法差不多。

就是先将页面的数据load进来形成一个特定的数据格式,然后通过类似jq的语法,对数据进行解析处理)

var cheerio = require('cheerio'),
$ = cheerio.load('
<h2 class="title">Hello world</h2>
');

$('h2.title').text('Hello there!');
$('h2').addClass('welcome');

$.html();
//=&gt;
<h2 class="title welcome">Hello there!</h2>

采用express模块化开发,按要求建立好项目后。进入项目目录,执行npm install安装所需依赖包。如果还不了解express的可以  到这里看看

爬虫需要cheerio.js 所以另外require进来, 所以要另外  npm install cheerio

项目文件很多,为了简单处理,就只修改了其中三个文件。(index.ejs  index.js   style.css )

(1)直接修改routes路由中的index.js文件,这也是最核心的部分。

还是看代码吧,有足够的注释

var express = require('express');
var router = express.Router();
var http = require('http');
var cheerio = require('cheerio');

/* GET home page. */
router.get('/', function(req, res, next) {
res.render('index', { title: '简单nodejs爬虫' });
});
router.get('/getJobs', function(req, res, next) { // 浏览器端发来get请求
var page = req.param('page'); //获取get请求中的参数 page
console.log("page: "+page);
var Res = res; //保存,防止下边的修改
//url 获取信息的页面部分地址
var url = 'http://www.lagou.com/jobs/list_%E5%89%8D%E7%AB%AF%E5%BC%80%E5%8F%91?kd=%E5%89%8D%E7%AB%AF%E5%BC%80%E5%8F%91&amp;spc=1&amp;pl=&amp;gj=&amp;xl=&amp;yx=&amp;gx=&amp;st=&amp;labelWords=label&amp;lc=&amp;workAddress=&amp;city=%E5%85%A8%E5%9B%BD&amp;requestId=&amp;pn=';

http.get(url+page,function(res){ //通过get方法获取对应地址中的页面信息
var chunks = [];
var size = 0;
res.on('data',function(chunk){ //监听事件 传输
chunks.push(chunk);
size += chunk.length;
});
res.on('end',function(){ //数据传输完
var data = Buffer.concat(chunks,size);
var html = data.toString();
// console.log(html);
var $ = cheerio.load(html); //cheerio模块开始处理 DOM处理
var jobs = [];

var jobs_list = $(".hot_pos li");
$(".hot_pos&gt;li").each(function(){ //对页面岗位栏信息进行处理 每个岗位对应一个 li ,各标识符到页面进行分析得出
var job = {};
job.company = $(this).find(".hot_pos_r div").eq(1).find("a").html(); //公司名
job.period = $(this).find(".hot_pos_r span").eq(1).html(); //阶段
job.scale = $(this).find(".hot_pos_r span").eq(2).html(); //规模

job.name = $(this).find(".hot_pos_l a").attr("title"); //岗位名
job.src = $(this).find(".hot_pos_l a").attr("href"); //岗位链接
job.city = $(this).find(".hot_pos_l .c9").html(); //岗位所在城市
job.salary = $(this).find(".hot_pos_l span").eq(1).html(); //薪资
job.exp = $(this).find(".hot_pos_l span").eq(2).html(); //岗位所需经验
job.time = $(this).find(".hot_pos_l span").eq(5).html(); //发布时间

console.log(job.name); //控制台输出岗位名
jobs.push(job);
});
Res.json({ //返回json格式数据给浏览器端
jobs:jobs
});
});
});

});

module.exports = router;

(2)node.js抓取的核心代码就是上面的部分了。

下一步就是将抓取到的数据展示出来,所以需要另一个页面,将views中的index.ejs模板修改一下


&lt;%= title %&gt;
<h3>【nodejs爬虫】 获取拉勾网招聘岗位--前端开发</h3>
初始化完成 ...

<button id="btn0" class="btn">点击开始抓取第一页</button>
<div class="container">

<!--
<div class="jobs"></div>
-->

</div>
<div class="footer">
<p class="fetching">数据抓取中 ... 请稍后</p>
<button id="btn1" class="btn">抓取上一页</button>
<button id="btn2" class="btn">抓取下一页</button>

</div>
<script src="javascripts/jquery.min.js" type="text/javascript"></script><script type="text/javascript">// <![CDATA[
function getData(str){   //获取到的数据有杂乱..需要把前面部分去掉,只需要data(<em>......<em>  data)
        if(str){
        return str.slice(str.lastIndexOf(">")+1);
    }
}

document.getElementById("btn1").style.visibility = "hidden";
document.getElementById("btn2").style.visibility = "hidden";
var currentPage = 0;  //page初始0

function cheerFetch(_page){  //抓取数据处理函数
    if(_page == 1){ 
        currentPage  = 1;      //开始抓取则更改page
    }
    $(document).ajaxSend(function(event, xhr, settings) {  //抓取中...
        $(".fetching").css("display","block");
    });
    $(document).ajaxSuccess(function(event, xhr, settings) {  //抓取成功
        $(".fetching").css("display","none");
    });
    $.ajax({   //开始发送ajax请求至路径 /getJobs  进而作页面抓取处理
        data:{page:_page},  //参数 page = _page
        dataType: "json",
        type: "get",
        url: "/getJobs",
        success: function(data){   //收到返回的json数据
            console.log(data);
            var html = "";
            $(".container").empty();
            if(data.jobs.length == 0){ 
                alert("Error2: 未找到数据..");
                return;
            }
            for(var i=0;i<data.jobs.length;i++){   //遍历数据并提取处理
                var job = data.jobs[i];
                html += "



<div class='jobs'>

<span >岗位序号:</span>  "+((i+1)+15*(currentPage-1))+"

"+
                "

岗位名称:<a href='"+job.src+"'target='_blank'>"+job.name+"</a>

"+
                "

<span >岗位所在公司:</span>  "+job.company+"

"+
                "

<span>公司阶段:</span>  "+getData(job.period)+"

"+
                "

<span>岗位规模:</span>  "+getData(job.scale)+"

"+
                "

<span>岗位所在城市:</span>  "+job.city+"

"+
                "

<span>岗位薪资:</span>  "+getData(job.salary)+"

"+
                "

<span>岗位最低经验要求:</span>  "+getData(job.exp)+"

"+
                "

<span>岗位发布时间:</span>  "+getData(job.time)+"

"+
                "</div>




"
            }

            $(".container").append(html);  //展现至页面
            if(_page == 1){ 
                document.getElementById("btn1").style.visibility = "hidden";
                document.getElementById("btn2").style.visibility = "visible";
            }else if(_page > 1){ 
                document.getElementById("btn1").style.visibility = "visible";
                document.getElementById("btn2").style.visibility = "visible";
            }
        },
        error: function(){ 
            alert("Error1: 未找到数据..");
        }
    });
}
// ]]></script>

(3)当然了,也少不了样式部分的简单修改  public文件下的 style.css

body {
padding: 20px 50px;
font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;
}
a {
color: #00B7FF;
cursor: pointer;
}
.container{position: relative;width: 1100px;overflow: hidden;zoom:1;}
.jobs{margin: 30px; float: left;}
.jobs span{ color: green; font-weight: bold;}
.btn{cursor: pointer;}
.fetching{display: none;color: red;}
.footer{clear: both;}

基本改动的也就这三个文件了。

所以,如果要测试一下的话,可以新建项目后,直接修改对应的那三个文件。

修改成功后,就可以测试一下了。

 

3.测试结果

1) 首先在控制台中执行 npm start

2) 接下来在浏览器输入http://localhost:3000/开始访问

3) 点击开始抓取(这里每次抓取15条,也就是原网址对应的15条)

4) 再抓取下一页也还是可以的~

 

5) 再来看看控制台的输出

 

 

看看看看…多简单的小爬虫呀..

简单归简单,最重要的是,知道了最基本的处理形式。

 

小尾巴一摆就是一个季节!

互联网公司的9种死法

mikel阅读(1306)

原文《那些互联网公司失败的原因,不外乎这九点,后来者请绕行!》摘要:

  1、战略不清,一头撞死

有些公司,在建立之后就陷入到纷繁的争斗中,不管是在股权上还是战略上,屡次调整,错失好局,眼睁睁看着大好的事业毁于一旦。比如,8848这 家号称中国最早的电子商务网站,在当时的股市,只要与之关联的都曾经创造奇迹,可是在公司业务发展上多次调整,从B2CB2C到系统集成,结果郁郁而 终。

  2、遭遇微创,山寨居上

腾讯的历史息息相关的都是很多当年很多耳熟能详的业务,比如OICQ,比如联众,甚至也包括MSN,最后都在不断被模仿中倒下了。腾讯并不是一 家只会抄袭的公司,在很多业务的细节方面都有独到之处,每一次的细小的创新都深深的刻在中国人的心坎上。不过,到了2010年之后,腾讯就再也没有干掉过 一家公司的创新业务,即便自己后来居上做到了最大,但其初始的创新者依然能生存,甚至有些还活的很好。

  3、外来和尚,本土替代

中国的大多数互联网业务都不是本土首创,几乎全部都有其美国化的创始者,但那些最早到中国来的美国互联网业务却没有一家能够在中国扎下根。最典型的就是雅虎,等到了中国化的新浪、搜狐、网易等崛起之后,那些西方门户就都离开了中国人的视野。

  4、技术突变,无奈退场

互联网是典型的技术推动型产业,任何一次技术上的大变革都会创造出新的领军企业和应用,也会让原来的弄潮儿溺水而亡。最典型的,莫过于运营商当 年雄心勃勃的WAP,还有依托WAP兴起的移动端网站,当智能手机和3G/4G网络到来的时候,他们都已经或正在成为历史遗迹。

  5、商业模式,彻底颠覆

互联网的商业模式一直在被摸索,一旦有强人或者强公司杀入,每次都会掀起一轮风波大战,而低价或者免费的模式却总是会占据上风,倒下的总会是那些收费的。360的免费杀毒让杀毒软件们几乎在一年之内就隐退江湖,而淘宝的免费更是让拥有美国后台的易趣识趣的选择了退出。

  6、弹尽粮绝,当场阵亡

互联网是需要烧钱的,剩者为王几乎成了最近几年生存的潜规则。很多网站在火红的时候就因为缺钱而被收购,剩下的往往会逐渐沉寂,特别是互联网泡沫破裂的时候更是如此。团购网站经历过一轮,2000年的时候更是一大批倒下,最近的案例自然是除了打车软件。

  7、竞争失利,战略放弃

互联网的应用在初期往往是万马奔腾,只要是被大家普遍看到的,大公司都要做,很多在后期会逐渐的选择放弃,也是公司战略收缩的一种方式。比如博客、微博等等都是,最早是新浪搜狐腾讯等等都在做,后来逐渐就剩下了新浪一家,电商也大体经历了类似的路线。

  8、大兵压境,被人吞并

互联网的初期还是草莽时代,大大小小的公司并力,很难有谁能彻底的吃掉谁,可随着行业集中度的提高,大公司越来越强大,资本的力量也越来越强 悍。很多小公司在一个领域发展起来,一旦被大公司看好,就会被强行收购,或者也会被大公司出一个类似的产品将其干掉。这样的例子在中国不胜枚举。当然,很 多创业者将手头的公司卖掉,也并非失败。

  9、非正常死亡

有一种互联网公司的离开比较特殊,属于突然死亡法。当然,这种里面也分为两种,一是被拿下的,还有一种是不堪凌辱而自我离开的。如果是因作恶而被干掉,属于咎由自取,如果是没作恶却也被干掉,就有些凄凉。不管怎样,这种非正常死亡都令人痛心,还是越少越好。

观点:互联网公司对比线下的公司死亡方式更加鄙夷所思,这上面总结的9种死法只是其中的小部分,可见主要原因还是互联网的发展变化太大,红海的惨烈竞争以及大鱼吃小鱼的境遇,让多少互联网公司纷纷成了某个行业的炮灰或者大佬的口食,尽管互联网依然是遍地黄金的热门风投聚集的英雄地,但是又有多少英雄从大浪淘沙的竞争中脱颖而出呢?

[转载]安卓开发笔记——关于Handler的一些总结(上) - Balla_兔子 - 博客园

mikel阅读(1007)

[转载]安卓开发笔记——关于Handler的一些总结(上) – Balla_兔子 – 博客园.今天来讲下在安卓开发里”重中之重”的另一个异步操作类Handler。

今天打算先讲下关于Handler的一些基本定义和使用方式

还是以一个下载图片为例,先看下实例效果:

 

好了,先来看下关于Handler的定义:

以上是官方对于Hanler类的描述,大致意思是说:Handler主要用于异步消息的处理:当发出一个消息之后,首先进入一个消息队列,发送消息 的函数即刻返回,而另外一个部分在消息队列中逐一将消息取出,然后对消息进行处理,也就是发送消息和接收消息不是同步的处理。 这种机制通常用来处理相对耗时比较长的操作。

1、Handler是一套更新UI的机制,在子线程中进行耗时操作,然后通过它来通知主线程对UI进行更新。

2、Handler也是一套消息处理机制,可以用它来发送消息和处理消息。

 

关于Handler的用途:

Handler可以分发Message对象和Runnable对象到主线程中,每个Handler的实例都会绑定到创建他的线程中(一般是位于主线程)

用途:1、安排消息或Runnable 在某个主线程中某个地方执行  2、安排一个动作在不同的线程中执行

 

接下来讲讲Handler的基本使用方式,今天先不讲太复杂,不引入线程和消息队列的概念,等下篇文章来说。

先来个教科书版本的Handler使用方式:(注释很全)

package com.example.handlertest_01;

import java.io.IOException;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.util.EntityUtils;

import android.app.Activity;
import android.app.ProgressDialog;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.ImageView;

public class MainActivity extends Activity {
//声明所使用控件
private ImageView imageView;
private Button button;
private ProgressDialog progressDialog;
private String path = "http://pic.baomihua.com/photos/201110/m_6_634545730007187500_16585344.jpg";//下载图片的资源地址

//创建一个Handler对象
private Handler handler = new Handler() {
public void handleMessage(android.os.Message msg) {
byte[] data=(byte[]) msg.obj;//直接obj对象
Bitmap bitmap=BitmapFactory.decodeByteArray(data, 0, data.length);
progressDialog.dismiss();
imageView.setImageBitmap(bitmap);
};
};

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//对控件进行实例化,并设置相对应属性
imageView = (ImageView) findViewById(R.id.imageView);
button = (Button) findViewById(R.id.bt);
progressDialog=new ProgressDialog(this);
progressDialog.setTitle("当前任务");
progressDialog.setMessage("正在下载图片,请稍后..");

//给按钮绑定监听
button.setOnClickListener(new OnClickListener() {

@Override
public void onClick(View v) {
progressDialog.show();
new Thread(new MyThread()).start();//开辟一条子线程,执行该线程操作
}
});

}

// 继承Runnable接口,开辟新线程访问网络资源
public class MyThread implements Runnable {
@Override
public void run() {
HttpClient httpClient = new DefaultHttpClient();
HttpGet httpGet = new HttpGet(path);
try {
HttpResponse httpResponse = httpClient.execute(httpGet);
if (httpResponse.getStatusLine().getStatusCode() == 200) {
// 访问成功
HttpEntity entity = httpResponse.getEntity();
// 阿帕奇提供的工具类EntityUtils可以很方便的把实体对象转换成字节码数组
byte[] data = EntityUtils.toByteArray(entity);

Message message = handler.obtainMessage();//取得Message消息对象
message.obj = data;
handler.sendMessage(message);//利用handler发送消息
}
} catch (ClientProtocolException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}

}

}

这里的Message就是上文所提到的消息,这边有个特别需要主要的是如何得到一个Message的实例对象,官方不提倡我们直接new Message(),它提供了很多种方法让我们去获取,具体我们可以看下API文档:

Handler类:

Message类:

为什么不直接去new Message()对象,查看过源码的朋友可以发现,其实Message.obtain()或者是Handler.obtainMesssage()在源 码里我们可以发现,Android给我们提供了一个消息池,这个消息池的大小为10而且加锁了,在我们调用这些方法的时候,系统会先去消息池里去取消息对 象,如果不存在那么它才会去new出一个新的Message消息对象。

以上API这里再列举一个:

1                     Message message=Message.obtain(handler);
2                     message.obj=data;
3                     message.sendToTarget();

都很简单,大家参照着API给的规范去做就可以了。

 

 

Message对象,里面除了给我们提供了obj这个存储对象之外,它还帮我们提供了一些其他类型的存储变量,比如:

这几个变量都是Android给我们提供的轻消耗的变量,我们可以拿来用,例如arg1,arg2我们可以用来存放简单的整型变量,what我们可 以用来存放消息的标识符,然后在handMessgae(Message msg)里用一个switch去判断要执行那些操作等等,用法和上面给的代码一致。

具体大家查看下API结合我刚给的注释,其实都很简单,这里就不一一举例了。

 

最后再看看下几个发送消息的API:

说几个易混淆的吧,其他的大家可以自己尝试玩玩

比如sendMessageDelayed是延迟发送回消息,后面跟着long类型的时间,以毫秒为单位。

还有sendMessAtTime是定时发送消息,时间由uptimeMills()传递

这两句是等效的,都是延时1秒将消息加入列队:

1 handler.sendMessageAtTime(msg, SystemClock.uptimeMillis()+1000);
2 handler.sendMessageDelayed(msg, 1000);

 

好了,先介绍这么多吧,其他的等下篇文章再说。

[转载]安卓开发笔记——关于AsyncTask的使用 - Balla_兔子 - 博客园

mikel阅读(1246)

[转载]安卓开发笔记——关于AsyncTask的使用 – Balla_兔子 – 博客园.

在安卓开发中,我们经常要进行一些耗时操作,比如数据库操作,获取网络资源,读取内存文件等等,当我们在处理这些耗时操作的时候,如果我们直接在 UI主线程进行,那么可能会导致阻塞UI主线程,使得UI界面卡顿,带来很不好的用户体验,因此安卓也给我们提供了2个异步操作的类,Handler和 AsyncTask。

今天先讲AsyncTask,AsyncTask类是Andorid提供给我们的一个轻量级异步类,算是一个框架,它对线程之间的通讯进行了封装,并且提供了简易的编程操作,使得我们可以很轻松的实现后台线程和UI主线程之间的通讯。

我写了一个异步获取网络图片的小例子,先来看实现效果:

 

以上是官网对AsyncTask的一些描述,大致意思是:

AsyncTask是一个易操作线程使用类,它可以帮助我们把后台线程处理程序的结果发送给UI主线程,使UI线程得到更新。

AsyncTask类提供了3个泛型参数(Params,Progress,Result)和4个执行步骤(下文会具体提及)

 

先来看下3个泛型参数

1、Params:这是一个任务参数,一般我们会定义成String类型的,例如本例子中要获取网络资源的URL地址

2、Progress:任务执行的刻度,一般我们会定义成Integer类型

3、Result:返回结果类型,例如本例中是对网络图片进行获取,那么它的返回类型应该是BitMap

 

再来看下4个步骤:

当我们的类去实现AsyncTask类的时候至少需要实现doInBackground(Params…)方法,这里作为学习,我把每一个的具体工作任务也说说

它的执行顺序是这样的 onPreExecute–>doInBackground–>onProgressUpdate–>onPostExecute

1、onPreExecute:这是一个预处理方法,在任务开始的时候执行,我们可以在这里进行一些控件的实例化,设置属性等。(非必须)

2、doInBackground:这是一个任务操作方法,也是最重要的一个方法,所有的耗时操作都应该在这里执行。(必须)

3、onProgressUpdate:这是一个进度即时更新方法,在这里我们可以即时更新任务滚动条的进度。(非必须,当在doInBackground里调用publishProgress时触发)

4、onPostExecute:这是一个任务结果处理方法,在doInBackground里执行完任务,会将结果通知给这个类,在这类中我们可以对UI进行更新操作(非必须)

上面的1、3、4是UI主线程触发调用的,所以可以对UI进行更新操作,而第2步是个异步操作,不能在里面进行UI的更新操作。

 

关于AsyncTask的调用,其实非常简单,我们在AsyncTask类被继承实现的时候,在主线程直接对其对象调用execute(Params..)方法即可。

 

好了,文字介绍到此结束,上代码:

1、布局文件


&nbsp;

&nbsp;

2、AsyncTask的实现类

package com.example.asynctasktest;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;

import android.app.ProgressDialog;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.AsyncTask;
import android.widget.ImageView;

public class MyAsyncTask extends AsyncTask&lt;String, Integer, Bitmap&gt; {

private ImageView imageView;
private ProgressDialog progressDialog;

public MyAsyncTask(ImageView imageView, ProgressDialog progressDialog) {
this.imageView = imageView;
this.progressDialog = progressDialog;
}

/**
* 执行第一步 这里为预处理操作,被UI线程所调用(可以在这里完成进度条的属性设置)
*/
@Override
protected void onPreExecute() {
super.onPreExecute();
progressDialog.setTitle("当前任务");
progressDialog.setMessage("正在下载图片,请稍后...");
progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);//设置进度条样式,横项
progressDialog.show();
}

/**
* 执行第二步 这里为异步线程,在这里处理耗时任务操作(比如:下载,读取文件)
* 通过调用publishProgress方法(传递即时任务进度)可以触发onProgressUpdate的执行
*/
@Override
protected Bitmap doInBackground(String... params) {
String path=params[0];
Bitmap bitmap=null;
HttpClient httpClient=new DefaultHttpClient();
HttpGet httpGet=new HttpGet(path);
InputStream inputStream=null;
try {
HttpResponse httpResponse=httpClient.execute(httpGet);
if(httpResponse.getStatusLine().getStatusCode()==200){
//连接成功
// HttpEntity entity=httpResponse.getEntity();
// byte[] data=EntityUtils.toByteArray(entity);
// bitmap=BitmapFactory.decodeByteArray(data, 0, data.length);

inputStream=httpResponse.getEntity().getContent();
ByteArrayOutputStream outputStream=new ByteArrayOutputStream();
long fileSize=httpResponse.getEntity().getContentLength();//文件总大小
byte[] data=new byte[1024];//每次读取的大小
int len=0;//本次读取的大小
int total=0;//累计读取的大小
while((len=inputStream.read(data))!=-1){
total+=len;//累计读取的大小
int values=(int) ((total/(float)fileSize)*100);//得到当前任务进行百分比
publishProgress(values);//触发onProgressUpdate更新即时进度
outputStream.write(data, 0, len);
}
byte[] result=outputStream.toByteArray();//转换为字节数组
bitmap=BitmapFactory.decodeByteArray(result, 0, result.length);
}
} catch (ClientProtocolException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally{
if(inputStream!=null){
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}

return bitmap;

}

/**
* 执行第三步 这里为实时UI更新操作,被UI线程所调用 在这里可以即时更新(如进度条进度)
*/
@Override
protected void onProgressUpdate(Integer... values) {
super.onProgressUpdate(values);
progressDialog.setProgress(values[0]);
}

/**
* 执行第四步 在这里会返回doInBackground的操作结果,被UI线程调用,更新最后UI结果
*/
@Override
protected void onPostExecute(Bitmap result) {
super.onPostExecute(result);
progressDialog.dismiss();
imageView.setImageBitmap(result);
}

}

3、主类

package com.example.asynctasktest;

import android.app.ProgressDialog;
import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.ImageView;

public class MainActivity extends ActionBarActivity {

private ImageView imageView;
private Button bt_download;
private ProgressDialog progressDialog;//进度对话框
private String path="http://img.pconline.com.cn/images/photoblog/5/3/7/5/5375781/20096/6/1244302842840.jpg";//下载图片路径

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

imageView=(ImageView) findViewById(R.id.imageView);
bt_download=(Button) findViewById(R.id.bt_download);
progressDialog=new ProgressDialog(this);

bt_download.setOnClickListener(new OnClickListener() {

@Override
public void onClick(View v) {
MyAsyncTask myAsyncTask=new MyAsyncTask(imageView,progressDialog);
myAsyncTask.execute(path);
}
});

}

}

 

代码到此结束,注释很详细应该很好理解。

使用AsyncTask类,以下是几条必须遵守的准则:

  • Task的实例必须在UI主线程中创建。
  • execute方法必须在UI主线程中调用。
  • 不要手动的调用onPreExecute(),onPostExecute(Result),doInBackground(Params…), onProgressUpdate(Progress…)这几个方法。
  • 该task只能被执行一次,否则多次调用时将会出现异常。

当然AsyncTask类不止这些东西,还有线程池的概念,今天先不讲,过几天连同Handler一起说。

对于简单的异步操作,掌握这些已经够了。

 

项目代码:http://pan.baidu.com/s/1kTkTgm7

 

作者:Balla_兔子
出处:http://www.cnblogs.com/lichenwei/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。
正在看本人博客的这位童鞋,我看你气度不凡,谈吐间隐隐有王者之气,日后必有一番作为!旁边有“推荐”二字,你就顺手把它点了吧,相得准,我分文不收;相不准,你也好回来找我!

[转载]WebRTC手记之WebRtcVideoEngine2模块 - 孤竹君 - 博客园

mikel阅读(929)

[转载]WebRTC手记之WebRtcVideoEngine2模块 – 孤竹君 – 博客园.

转载请注明出处:http://www.cnblogs.com/fangkm/p/4401143.html 

终于讲到视频 数据的编码发送模块了,不容易。总体来说也看了不少时间WebRTC的源码了,最大的感触就是各个模块在开发的时候非常独立,每个模块都定义了自己的一套 接口,最后串起来的时候添加各种适配对象来转接。这给我们这些刚开始源码阅读的人带来非常大的苦恼,不过WebRTC的模块内的结构设计还是很不错的,不 然我也没有看下去的动力。

注意命 名,WebRtcVideoEngine2带了个2字,不用想,这肯定是个升级版本的VideoEngine,还有个WebRtcVideoEngine 类。从目前我的理解来看,WebRtcVideoEngine2比WebRtcVideoEngine改进之处在于将视频流一分为二:发送流 (WebRtcVideoSendStream)和接收流(WebRtcVideoReceiveStream),从而结构上更合理,源码更清晰。这个部 分等下会细说。在介绍WebRtcVideoEngine2之前,先简单地分析一下WebRTC的Media Engine结构,说实话,我真不会表达Engine是个怎样的概念,但既然这样命名,核心模块肯定是错不了的。结构很简单:

 

  • MediaEngineInterface:抽象Media Engine的逻辑接口,负责创建用于视频传输的VideoMediaChannel、用于音频传输的VoiceMediaChannel、注册音频数据处理接口等。
  • CompositeMediaEngine: 实现MediaEngineInterface接口,本身也是个模板类,两个模板参数分别是视频Engine和音频Engine。其派生类 WebRtcMediaEngine依赖的模板参数是WebRtcVoiceEngine和WebRtcVideoEngine,而用于Chromium 的WebRtcMediaEngine2则依赖WebRtcVoiceEngine和WebRtcVideoEngine2。

WebRtcVideoEngine2主要作用在于创建视频channel对象WebRtcVideoChannel2。结构如下:

当调用WebRtcVideoChannel2的AddSendStream方法时,会创建一个WebRtcVideoSendStream对象,同样,调用AddRecvStream成员方法,会创建一个WebRtcVideoReceiveStream对象。

当 外部调用WebRtcVideoChannel2的SetCapturer方法时,会转给WebRtcVideoSendStream来响 应,WebRtcVideoSendStream内部将InputFrame成员方法挂接VideoCapturer的SignalVideoFrame 信号来接收视频采集器传输过来的视频帧数据。

WebRtcVideoChannel2的AddSendStream和SetCapturer的调用时机这里暂时不考虑,这些涉及到网络连接,等每个节点的内容分析完后,再探讨整个流程。

如图所 示,WebRtcVideoSendStream和WebRtcVideoReceiveStream也只是个包装类,内部依赖Call接口创建对应的 VideoSendStream接口实现类和VideoReceiveStream接口实现类。在internal命名空间内,分别有一个Call类、 VideoSendStream类、VideoReceiveStream类来实现这三个接口,Call类创建关键的VideoEngine对象来管理视 频数据发送过程中的一系列处理逻辑。从代码结构上看,VideoEngine是一个相对独立的模块,它封装视频数据采集后的处理、编码等逻辑,下面仔细分 析一下VideoEngine的结构:

 

VideoEngine模块里有ViEBase、ViECodec、ViECapture、ViEImageProcess、ViENetwork、ViERender、ViERTP_RTCP、ViEExternalCodec接口,注意,这些都是功能性的 接口,它们相应的实现分别对应于上图中的XXXImpl类,VideoEngineImpl类从所有的XXXImpl接口派生,因此外部有了 VideoEngine接口,都可以通过强转的方式获取ViEBase、ViECapture等之类的接口(根据VideoEngine强转成相应的接口 的逻辑封装在目标接口的GetInterface静态方法中),外界可以通过这些接口来完成视频数据做相应的设置,而这些设置最终都反映到一个名叫 ViESharedData的类对象里。该对象由ViEBaseImpl创建并在各接口的实现之间共享,XXXImpl可以通过ViEBaseImpl的 shared_data方法来访问,用于共享的数据有三类:ViEInputManager、ViEChannelManager和 ViERenderManager。下面分别介绍一下这关键的三个对象。

  • ViEInputManager:封装了视频采集/输入逻辑(哈哈,又是一套视频输入逻辑),结构:

ViEInputManager 为每个通道分配一个ViECapturer对象来做为视频源,ViECapturer可以传入也可以自己创建一个VideoCaptureModule视 频采集模块,并通过VideoCaptureDataCallback接口从其接收数据,也可以直接通过ViEExternalCapture接口接收从 外部直接传入的视频帧数据(调用ViEExternalCapture接口的IncomingFrame方法)。VideoSendStream就是通过 ViEInputManager创建一个ViEExternalCapture对象来传入外界传来的视频帧数据(通过 WebRtcVideoSendStream的InputFrame传来)。这里要注意,ViEInputManager为创建的ViECapturer 对象分配一个capture_id,外界可以通过这个capture_id来操作其对应的ViECapturer。视频源传入逻辑已经明了,接下来分析一 下视频是怎么传出去的。无论通过哪种视频数据接收方法,ViECapturer都不会立即将数据传递出去,因为它内部需要对这些视频数据做相关的处理。数 据处理必然耗时,如果采用同步的方式,必将阻塞视频传入的流程。因此,在创建ViECapturer的时候,会启动一采集线程,该线程调用 ViECaptureProcess处理函数,在该处理函数里,先调用VideoProcessingModule对视频数据进行处理(灯光加亮、去闪 烁),如果在ViEImageProcessImpl里注册了ViEEffectFilter处理对象,这里也会调用该对象来处理视频帧数据,最后通过 DeliverFrame方法分发到注册进来的所有ViEFrameCallback接口。

  • ViEChannelManager:封装了视频编码和传输逻辑,这块结构比较复杂,总体如下:

ViEChannelManager 维护了ViEEncoder和ViEChannel对象,ViEEncoder实现了ViEFrameCallback接口从ViECapturer对象 中接收视频帧数据,ViEEncoder对接收到的视频帧数据进行编码,然后将编码后的数据传给ViEChannel(通过两者之间共享的 PayloadRouter对象),ViEChannel将编码后的视频数据通过RTP/RTCP协议发送出去。下面分别分析一下ViEEncoder和 ViEChannel。

    1) ViEEncoder类:封装了视频编码流程。

视 频编码由VideoCodingModule模块统一管理,视频帧传入接口是通过VideoCodingModule的的AddVideoFrame方 法,编码后的视频传出接口是借助VCMPacketizationCallback接口来回调。具体选取哪种视频编码的逻辑位于 VCMCodecDataBase类,当前支持VP8编码、VP9编码和视频格式到I420格式的转换。

2)ViEChannel类:封装了编码后的视频数据发送逻辑和视频数据接收解码逻辑。

视 频数据发送逻辑是通过PayloadRouter对象委托给RtpRtcp模块做RTP协议的封装,具体的网络发送操作还是回托给ViESender做数 据的网络发送操作。ViESender的逻辑相对简单,限于篇幅,图中无法做详细的标注。ViESender的发送操作依赖外部设置给它的 Transport接口(通过VideoEngine模块的ViENetwork接口来完成设置)。

当 WebRtcVideoChannel2接收到网路数据包后(通过OnPacketReceived或OnRtcpReceived方法响应),会在 VideoReceiveStream对象中通过VideoEngine模块暴露出去的ViENetwork接口来响应数据包处理,最终触发到 ViEChannel的ReceivedRTPPacket或ReceivedRTCPPacket方法。ViEChannel中将接收并解码网络视频数 据的任务分配给ViEReceiver对象。ViEReceiver先调用RTP/RTCP模块做协议的解析(图中限于篇幅未标注出来),解析完成后调用 VideoCodingModule模块进行数据的解码操作(参见ViEReceiver的OnReceivedPayloadData方 法),VideoCodingModule模块内部维护了一个与VideoSender对应的VideoReceiver来完成解码逻辑,这块与 VideoSender的编码逻辑完全对称,这里不再表述。

  • ViERenderManager:这个类封装了视频渲染逻辑,结构如下:

当 ViEChannel接收到网络数据解包并解码后,就会开启触发渲染流程(参见FrameToRender方法),ViEChannel会调用向其注册的 ViEFrameCallback接口来派发视频帧数据。ViERenderManager维护了一个ViERenderer对象来实现 ViEFrameCallback接口,它将数据进一步派发,最终通过ExternalRenderer接口派发给 WebRtcVideoChannel2的VideoReceiveStream对象。VideoReceiveStream通过VideoSource 设置进来的VideoRenderer接口将数据派发给VideoTrack,用户可以挂接VideoRendererInterface接口来接收视频 帧数据。真够绕的,而且那么多命名的相似性(比如VideoRender/VideoRenderer),感觉各模块开发期间,都实现了自己的一套接口规 范,最后强行串在一起了。

[转载]关于iOS开发证书的一些总结(很有用) - 金辰九零三 - 博客园

mikel阅读(739)

[转载]关于iOS开发证书的一些总结(很有用) – 金辰九零三 – 博客园.

今天出了个问题,具体是这样的,我把本地的钥匙传里面的各种东西全部清空了,结果出现了各种不可预料到问题。花了一下午的时间反复的测试,终于把证书的一些问题理顺,然后在这里做一些总结。

先看张图片:

其中,从上往下四个栏目,分别是证书,AppID,设备,描述文件,基础的我就不说了,网上教程很多,就总结一下问题所在。

我们要创建一个生产证书,首先我们会创建一个Development,在创建这个证书要选择一个CSR文件,这个文件就是开发机器账号之间的关系,可以理 解成是一个数字签名(或者是秘钥),然后我们会创建一个AppID,正常的来创建就行了,我现在举例子,我们在AppID创建的时候把推送给选中,下一 步,我们要创建一个推送用的证书,就是根据这个AppID来的,最后我们根据AppID和Development来创建一个描述文件,同理要创建生产证 书,也是这样的步骤。

那么现在就牵扯到几个名词,Development证书,aps_Development证书(推送证书),测试描述文件,AppID,同理也就有Distribution证书,aps_Distribution证书,开发描述文件。

两个推送证书其实只是给推送服务器的证书(要导出pem,或者p12文件),真正的开发和生产不需要这两个证书,在公司里,如果做账号管理,我们要把开发 证书和生产证书(Development和Distribution)导成p12文件(加密),给我们同事,一般开发证书是不给的(统一管理),通常是这 样的,开发证书的p12证书和描述文件给开发人员,生产证书的p12备份存在云端。

为什么我今天会出现这个问题呢,是因为我没有备份p12文件,然后把钥匙串中得秘钥全部删掉了,我从网站下载新的证书,原先的证书是我没删除之前的秘钥,现在删除之后,是肯定没有秘钥的,所以是运行不起来的,也就是说,证书没有秘钥是肯定运行不起来的。

然后延伸出一个问题,假如我申请的电脑丢失了,岂不是一切都完了,其实这是这样的,我们只要有原证书导出的p12文件之后就不必用原来的电脑发布和生产 了,所以说为了保险起见,我们需要把所有的证书配置完成之后,生成p12文件再加上描述文件,要在云端全部存起来,这样就可以避免这个问题。那又来了一个 问题,那么我开发的时候没有上传,又删了怎么办?那只有一个办法,将里面的证书全不删除,重新创建,就OK了!!!

好了现在就写到这里吧,有问题,或者有不同见解的话,可以给我留言。O(∩_∩)O哈哈~

[转载]Android单元测试Junit (一) - 米蓝 - 博客园

mikel阅读(918)

[转载]Android单元测试Junit (一) – 米蓝 – 博客园.

1、在eclips中建立一个Android工程,具体信息如下:

2、配置单元测试环境,打开AndroidManifest.xml,具体代码如下所示:

<!--?xml version="1.0" encoding="utf-8"?-->

<!--使用单元测试库-->

<!-- 声明Android框架和目标测试包-->

说明:

      1、<uses-library Android:name=”android.test.runner”/> 这句,放在application内部。

2、android:targetPackage 这个属性,一般为应用程序的包名,必须和xml中manifest节点的package同名。

3、创建一个包com.milan.service,这个包下面再创建一个类 Person,用于测试的时候调用。

package com.milan.service;
public class Person {
public int getAge(String age){
return new Integer(age);
}
}

4、在com.milan.junit包下面,建一个PersonTest类,代码如下:

package com.milan.junit;
import com.milan.service.Person;
import android.test.AndroidTestCase;
public class PersonTest extends AndroidTestCase {
///用例1,传入非数字
public void test01(){
Person person = new Person();
person.getAge("milan");
}
//用例2,传入数字
public void test02(){
Person person = new Person();
person.getAge("1");
}
}

 5、选中PersonTest.java,右键选择Run As 选择Android Junit Test 运行单元测试,如图:

从上图可以看到,test01的测试结果为失败。失败原因为:java.lang.NumberFormatException: Invalid int: “milan”,

test02的测试结果为通过。

如果没有Junit运行结果,可以在eclips的Window菜单下,选择Show View–Other–Java–Junit 中打开。

6、运行单个测试用例

eclips的Window菜单下,选择Show View–Other–General–Outline打开大纲视图,右键测试用例,即可运行单个测试用例。如图:

同理,如果需要执行整个项目的测试用例,可选中整个项目,用Android Junit Test运行即可。

7、断言

当我们获得程序的运行结果,我们希望Junit能够自动判断 将实际结果和期望结果是否一致。这个时候我们就可以用到Junit自带的断言。

修改PersonTest类,为以下代码:

package com.milan.junit;
import com.milan.service.Person;
import android.test.AndroidTestCase;
public class PersonTest extends AndroidTestCase {
///用例1,传入非数字
public void test01(){
Person person = new Person();
int i= person.getAge("milan");
assertEquals(1, i);
}
//用例2,传入数字,断言有message
public void test02(){
Person person = new Person();
int i= person.getAge("1");
assertEquals("传入数字",2,i);
}
//用例2,传入数字,断言无message
public void test03(){
Person person = new Person();
int i= person.getAge("1");
assertEquals(1,i);
}
}

然后运行结果为:

 

如果传入了message,一旦用例不通过,会在右边的错误详细里,列出message。

8、其他断言

JUnit提供丰富的断言,来满足我们不同的比对需求。按住Ctrl键,点击代码中的assertEquals,可以看到Assert类里面多支持的断言函数的参数类型:

大家有空可以多试试。这里就不一一讲解了。

其他信息

如出现Test run failed:Unable to find instrumentation target package错误

是因为 android:targetPackage=“com.milan.junit” 必须和

<manifest xmlns:android=“http://schemas.android.com/apk/res/android”
      package=“com.milan.junit”
package相同的包名

[转载]MyEclipse 2015 CI 12 无限试用 - gcg0036 - 博客园

mikel阅读(967)

[转载]MyEclipse 2015 CI 12 无限试用 – gcg0036 – 博客园.

官方报价 2499人民币一年

http://www.myeclipsecn.com/buy/

 

 

为什么是CI 12 ?因为这个版本有重大更新:

 

1.基于Eclipse Luna SR2 (4.4.2),

2.Js性能可调整,Ternjs可关闭

3.Ternjs已经升到0.9

基于Eclipse的两大IDE(MyXX和Jboss Studio)的共同选择

wpsED12.tmp 

 

wpsED13.tmp 

https://github.com/angelozerr/tern.java/wiki/Installation-Update-Site

 

4.集成了Emmet

快速编写Html和Css的神器

wpsED14.tmp 

 

wpsED24.tmp 

http://docs.emmet.io/cheat-sheet/

 

 

CI 8 是最后一个可以破解的,之后的版本只能试用

 

 

 

 

 

经过上百次重装,终于搞出了“无限试用版”,并总结出下面几条规律:

 

1.如果提示过期了,删什么都没用,要不就注册,要不就重装

2.但并不用备份,所有的配置和项目都会保存在原来的workspace里,千万不要全删workspace

3.导出的配置文件会包含所有信息, 包括过期时间

 

然后开始路漫漫中(一)

单纯卸载重装 = wpsED25.tmp

卸载,删除我的文档\.myeclipse\,新建workspace = wpsED26.tmp

卸载,新建workspace = wpsED27.tmp

 

简单的“破解”这样就可以了

但如果原来的workspace必须要用,那就继续(二)

卸载,原来workspace删除.metadata\.plugins\ = wpsED28.tmp

卸载,原来workspace删除\.metadata\.plugins\org.eclipse.core.runtime\.settings\org.eclipse.core.resources.prefs = wpsED29.tmp

 

比较完美的“破解”这样就可以了

但实际使用中定期备份配置文件也必不可少,那么怎么才能倒入呢(三)

 

倒入,退出,当前workspace删除\.metadata\.plugins\org.eclipse.core.runtime\.settings\org.eclipse.core.resources.prefs

MyEclipse2015删除\configuration\.settings\org.eclipse.ui.ide.prefs

 

 

大功告成,安心睡觉吧

 

 

相关文件下载:

http://download.csdn.net/detail/gcg0036/8572505

40岁的微软要开源?

mikel阅读(1329)

原文《四十不惑,微软真的要开源了吗?》摘要:

为什么Windows就不能开源呢?在谷歌和苹果的压制下,微软早就应该寻求更大胆的革新来应对新的市场发展了,尤其是移动互联网市场的发展需要更多 的转换思路。当初谷歌Android的开源帮助其斩获了更多的市场机会和份额,苹果封闭的iOS帮助其获得了那么大的收益率,这对微软来说又是一种刺激。

不过需要关注的是,苹果的成功是不可复制的,微软继续封闭对于开拓新市场并没有更多的优势,相反只能不断地拉开其与谷歌和苹果的差距,如果微软真的可以 把Windows开源了,那么或许真的可以形成绝地反击,带来意想不到的效果。毕竟Windows早已经深入人心,对于更多的开发者来说上手更加简单和便 捷。当然一切都取决于微软的决心和对未来的格局掌控的高瞻远瞩。

观点:

微软赖以生存的授权盈利模式一直被争议着,这次在移动互联网的大潮下,微软还在拼授权显得力不从心,因为从移动互联网一开始微软就迟迟落后于谷歌和苹果,结果匆匆上马的winPhone也无力回天,大局已定的移动互联网操作系统的天下更是没了地位,Android从诞生就是开源的,苹果人家从基础硬件到操作系统全套封闭,微软恰恰游离在之间,很尴尬。

如果微软开源了是否意味着微软的授权免费?同样面临的就是Android的乱象,开发者们会推出各种自己开发的版本,那么硬件厂商会怎么看?!怎么兼容这些版本,涉及到的一系列兼容性问题会更多,消费者目前是在免费的驱使下投奔谷歌,那么微软在这个时候开源能不能挽回市场,还是像微软所说的靠卖服务来提高盈利?一系列的问题摆在微软这个40不惑的企业面前,下一步如何走关系到未来操作系统的格局问题,风险和机遇并存!

[转载]Android Metro风格的Launcher开发系列第三篇 - wuhao_blog - 博客园

mikel阅读(1402)

[转载]Android Metro风格的Launcher开发系列第三篇 – wuhao_blog – 博客园.

前言:

各位小伙伴,又到了每周更新文章了时候了,本来是周日能发出来呢,这不是赶上清明节吗,女王大人发话了,清明节前两天半陪她玩,只留给我周一下午半天时间写博客快哭了,哪里有女王哪里就有压迫呀有木有!好了闲话少说,上一篇博客(Android Metro风格的Launcher开发系列第二篇)说到Launcher主体框架用ViewPager来实现,这一篇博客咱们来说说每一个page的具体实现。

PagerAdapter:

Launcher主体ViewPager实现就引出了PagerAdapter,PagerAdapter是Android.support.v4包中的 类,它的子类有FragmentPagerAdapter, FragmentStatePagerAdapter,这两个adapter都是Fragment的适配器,这里因为没有用到Fragment所以这里不 讲,我只讲PagerAdapter。关于PageAapter的描述,Google官网原文是这样的:Base class providing the adapter to populate pages inside of a ViewPager.  You will most likely want to use a more specific implementation of this, such as FragmentPagerAdapter or FragmentStatePagerAdapter,大致就是说PagerAdapter是ViewPager提供的一个适配器,方便我们对 ViewPager的每一个View进行控制。我的PagerAdapter是这样实现的:

public class LauncherAdapter extends PagerAdapter {
private ArrayList mViews;

public LauncherAdapter(ArrayList views) {
mViews = views;
}

@Override
public void destroyItem(View arg0, int arg1, Object arg2) {
((ViewPager) arg0).removeView(mViews.get(arg1));
}

@Override
public void finishUpdate(View arg0) {
}

@Override
public int getCount() {
if (mViews != null) {
return mViews.size();
}
return 0;
}

public View getCurrentView(int currentID) {
return mViews.get(currentID);
}

@Override
public Object instantiateItem(View arg0, int arg1) {
((ViewPager) arg0).addView(mViews.get(arg1));
return mViews.get(arg1);
}

@Override
public boolean isViewFromObject(View arg0, Object arg1) {
return (arg0 == arg1);
}

@Override
public void restoreState(Parcelable arg0, ClassLoader arg1) {
}

@Override
public Parcelable saveState() {
return null;
}

}

PageViewItem:

PagerAdapter的getCurrentView方法返回的每一个view都是自定义View,为什么要自定义呢?因为在每一个图标获取焦点放大的时候会与旁边的图标有重叠部分,ViewPager每一页view都是一个FrameLayout,在绘制view的时候是按照一定的顺序绘制的,就会遇到焦点view放大后显示的效果是被旁边的view压了一部分,如果不改变view绘制顺序就不能避免这个问题。

如上图所示,图一显示效果就是焦点view放大,改变绘制顺序的实现效果。改变绘制顺序其实就是重写ViewGroup的getChildDrawingOrder(int childCount, int i)方法,每一次绘制时,最后返回focusview所在的viewgroup中的index就行了。

CellView:

如上图所示,每一个正方形的view我在这里叫做CellView,它也是一个自定义的view,自定义主要是为了实现:

1、获取焦点时放大和丢掉焦点时缩小效果,这里是应用了属性动画,ViewPropertyAnimator可以通过View的animate()方法获取的,具体动画实现如下:

mPropertyAnimator.scaleX((width + mScaleX) / width)
.scaleY((height + mScaleY) / height).setDuration(duration)
.setInterpolator(new DecelerateInterpolator())
.start();

2、在xml文件灵活配置一些CellView的属性,比如点击打开的应用,呈现的ICON获取地址,焦点x、y的放大值等,CellView对应的属性定义attrs.xml文件如下:

<!--?xml version="1.0" encoding="utf-8"?-->

&nbsp;

3、实现在用遥控器移动焦点时不会焦点错乱,在开发遥控器应用时一个很大的问题就是焦点在移动时焦点错乱,基本 上应用UI bug至少有一半时焦点bug,这个应用我为了防止焦点错乱定义了CellView的边界属性,上面的xml文件中isXXEdge就是,这样在焦点移动 到边界时可以进行Page之间的切换和其他处理,防止焦点在进入每一个page时出现错乱。

 

下面来看一下实现的具体效果:

 

 

总结:以上就是Metro风格Launcher实现,我用了三篇博客来讲解这个应用,所有效果的实现都是自己摸索的,应该还有更好的实现方法,大家可以多多交流提出自己的看法,也可以关注我的微信号coder_online,以上谢谢!

 

 

  第一时间获得博客更新提醒,以及更多技术信息分享,欢迎关注个人微信公众平台:程序员互动联盟(coder_online),扫一扫下方二维码或搜索微信号coder_online即可关注,我们可以在线交流。