CI 3.1.3 源码分析(一)

仅是自己分析源码时的笔记参考 并不是教程之类的文章

  1. 入口文件index.php

    主要定义了报错类型以及设置各种路径常量 

  2. CodeIgniter.php

    1. load application文件夹下的常量文件(constants .php)

    load system  文件夹下的common.php

    自定义加载目录下的class,默认加载libraries目录下的class,首先加载core/下的,然后加载application/下的

    默认用“CI_”这个prefix,其次使用自定义prefix 例如“MY_”

    function &load_class($class, $directory = 'libraries', $param = NULL)
    {
       static $_classes = array();
    
       // Does the class exist? If so, we're done...
       if (isset($_classes[$class]))
       {
          return $_classes[$class];
       }
    
       $name = FALSE;
    
       // Look for the class first in the local application/libraries folder
       // then in the native system/libraries folder
       foreach (array(APPPATH, BASEPATH) as $path)
       {
          if (file_exists($path.$directory.'/'.$class.'.php'))
          {
             $name = 'CI_'.$class;
    
             if (class_exists($name, FALSE) === FALSE)
             {
                require_once($path.$directory.'/'.$class.'.php');
             }
    
             break;
          }
       }
    
       // Is the request a class extension? If so we load it too
       if (file_exists(APPPATH.$directory.'/'.config_item('subclass_prefix').$class.'.php'))
       {
          $name = config_item('subclass_prefix').$class;
    
          if (class_exists($name, FALSE) === FALSE)
          {
             require_once(APPPATH.$directory.'/'.$name.'.php');
          }
       }
    
       // Did we find the class?
       if ($name === FALSE)
       {
          // Note: We use exit() rather than show_error() in order to avoid a
          // self-referencing loop with the Exceptions class
          set_status_header(503);
          echo 'Unable to locate the specified class: '.$class.'.php';
          exit(5); // EXIT_UNK_CLASS
       }
    
       // Keep track of what we just loaded
       is_loaded($class);
    
       $_classes[$class] = isset($param)
          ? new $name($param)
          : new $name();
       return $_classes[$class];
    }

      然后加载config文件,默认加载Application目录下config/config.php,找不到才会检查config.php是否再自定义的路径里

function &get_config(Array $replace = array())
{
   static $config;

   if (empty($config))
   {
      $file_path = APPPATH.'config/config.php';
      $found = FALSE;
      if (file_exists($file_path))
      {
         $found = TRUE;
         require($file_path);
      }

      // Is the config file in the environment folder?
      if (file_exists($file_path = APPPATH.'config/'.ENVIRONMENT.'/config.php'))
      {
         require($file_path);
      }
      elseif ( ! $found)
      {
         set_status_header(503);
         echo 'The configuration file does not exist.';
         exit(3); // EXIT_CONFIG
      }

      // Does the $config array exist in the file?
      if ( ! isset($config) OR ! is_array($config))
      {
         set_status_header(503);
         echo 'Your config file does not appear to be formatted correctly.';
         exit(3); // EXIT_CONFIG
      }
   }

   // Are any values being dynamically added or replaced?
   foreach ($replace as $key => $val)
   {
      $config[$key] = $val;
   }

   return $config;
}

基本上其他的function 都是一些基础function简单过一下就看完了

下面要看的是core/libraries文件夹下的这些类库了

php中引用&的真正理解-变量引用、函数引用、对象引用

5.3以后已经不赞成使用引用来传递参数了貌似

只是再分析CI3的源码的时候还是能看到函数引用

总之再复习一下吧

总没有坏处

====================================================================

    php的引用(就是在变量或者函数、对象等前面加上&符号) //最重要就是 删除引用的变量 ,只是引用的变量访问不了,但是内容并没有销毁 在PHP 中引用的意思是:不同的名字访问同一个变量内容.

变量的引用

        PHP 的引用允许你用两个变量来指向同一个内容 

<?php
$a="ABC"; 
$b =&$a; 
echo $a;//这里输出:ABC 
echo $b;//这里输出:ABC 
$b="EFG"; 
echo $a;//这里$a的值变为EFG 所以输出EFG echo $b;//这里输出EFG 
?>

  函数的传址调用 传址调用我就不多说了 下面直接给出代码

<?php
function test(&$a){ 
    $a=$a+100; 
} 
$b=1; 
echo $b;//输出1 test($b);   //这里$b传递给函数的其实是$b的变量内容所处的内存地址,通过在函数里改变$a的值 就可以改变$b的值了 echo "<br>"; echo $b;//输出101
?>

 要注意的是,在这里test(1);的话就会出错,原因是:PHP规定传递的引用不能为常量(可以看错误提示)。

<?php
function &test(){ 
    static $b=0;//申明一个静态变量 
    $b=$b+1; 
    echo $b; 
    return $b; }
}
$a=test();//这条语句会输出 $b的值 为1 
$a=5; $a=test();//这条语句会输出 $b的值 为2
$a=&test();//这条语句会输出 $b的值 为3 
$a=5; $a=test();//这条语句会输出 $b的值 为6
?>

下面解释下:  通过这种方式$a=test();得到的其实不是函数的引用返回,这跟普通的函数调用没有区别 至于原因: 这是PHP的规定 PHP规定通过$a=&test(); 方式得到的才是函数的引用返回 至于什么是引用返回呢(PHP手册上说:引用返回用在当想用函数找到引用应该被绑定在哪一个变量上面时。) 这句狗屁话 害我半天没看懂

       用上面的例子来解释就是 $a=test()方式调用函数,只是将函数的值赋给$a而已, 而$a做任何改变,都不会影响到函数中的$b,而通过$a=&test()方式调用函数呢, 他的作用是 将return $b中的 $b变量的内存地址与$a变量的内存地址 指向了同一个地方 即产生了相当于这样的效果($a=&b;) 所以改变$a的值 也同时改变了$b的值 所以在执行了 $a=&test(); $a=5; 以后,$b的值变为了5

这里是为了让大家理解函数的引用返回才使用静态变量的,其实函数的引用返回多用在对象中

对象的引用 

<?php
class a{
    var $abc="ABC";
} 
$b=new a; 
$c=$b; 
echo $b->abc;//这里输出ABC 
echo $c->abc;//这里输出ABC $b->abc="DEF"; 
echo $c->abc;//这里输出DEF
?>

       以上代码是在PHP5中的运行效果 在PHP5中 对象的复制是通过引用来实现的。上列中$b=new a; $c=$b; 其实等效于$b=new a; $c=&$b; PHP5中默认就是通过引用来调用对象, 但有时你可能想建立一个对象的副本,并希望原来的对象的改变不影响到副本 . 为了这样的目的,PHP定义了一个特殊的方法,称为__clone.

引用的作用 

       如果程序比较大,引用同一个对象的变量比较多,并且希望用完该对象后手工清除它,个人建议用 "&" 方式,然后用$var=null的方式清除. 其它时候还是用php5的默认方式吧. 另外, php5中对于大数组的传递,建议用 "&" 方式, 毕竟节省内存空间使用。

取消引用 当你 unset 一个引用,只是断开了变量名和变量内容之间的绑定。这并不意味着变量内容被销毁了。例如:

<?php $a = 1; $b =& $a; unset ($a); ?>

不会 unset $b,只是 $a。 function quoteTest(){ global $var ; //相当于 $var = &$GLOBALS['var']; unset($var); //删除只是删除引用,而引用的内容还存在,同上这并不意味着变量内容被销毁了}$var=1;quoteTest();echo $var; //  结果 1

———————————————————————————————-

不会 unset $b,只是 $a。

function quoteTest(){ global $var ; //相当于 $var = &$GLOBALS['var']; $var = 5; //因为他们都指向 同一内存内容}$var=1;quoteTest();echo $var; //结果 5———————————————————————————————-

'&' 这就是引用

23111813-8cf28c728bcd4002b1bbee0ef6b99d4a.jpg

global 引用 当用 global $var 声明一个变量时实际上建立了一个到全局变量的引用。也就是说和这样做是相同的:

<?php $var =& $GLOBALS["var"]; ?>

这意味着,例如,unset $var 不会 unset 全局变量。

$this 在一个对象的方法中,$this 永远是调用它的对象的引用。

//下面再来个小插曲 php中对于地址的指向(类似指针)功能不是由用户自己来实现的,是由Zend核心实现的,php中引用采用的是“写时拷贝”的原理,就是除非发生写操作,指向同一个地址的变量或者对象是不会被拷贝的。

通俗的讲 1:如果有下面的代码 [php] $a="ABC"; $b=$a; [/php] 其实此时 $a与$b都是指向同一内存地址 而并不是$a与$b占用不同的内存

2:如果在上面的代码基础上再加上如下代码 [php] $a="EFG"; [/php] 由于$a与$b所指向的内存的数据要重新写一次了,此时Zend核心会自动判断 自动为$b生产一个$a的数据拷贝,重新申请一块内存进行存储

23111848-f447888b753a40748f27c64c222c8ea9.jpg

PHP __call拦截器 实现委托

__call方法可能是最有用的拦截器方法。

当客户端代码要调用类中未定义的方法时,__call会被调用。

__call()接受2个参数,一个是方法的名称,另一个是传递给要调用方法的所有参数(数组)。

__call()方法返回的任何值都会返回给客户,就好像调用一个真实存在的方法一样。

__call()方法对于实现委托也很有用。委托是指一个对象转发或者委托一个请求给另一个对象,被委托的一方替原先对象处理请求。

这类似于继承,和在子类中调用父类的方法有点相似。

但再继承时,父类与子类的关系是固定的,而使用委托则可以再代码运行时改变使用的对象,这意味着委托比继承具有更大的灵活性。

//将Person类信息格式化并输出
class personWriter{
	function writeName(Person $p){
		print $p->getName();
	}

	function writeAge(Person $p){
			print $p->getAge();
	}

}


//当然我们可以通过集成PersonWrite类以不同的方式输出Person类的信息。
//下面的的代码结合使用__call()方法和PersonWriter对象来实现Person类:
class Person{
	private $writer;

	function __construct(PersonWriter $write){
		$this->write = $write;
	}

	function __call($methodname, $args){
		if(method_exists($this->writer, $methodname)){
			return $this->writer->$methodname($this);
		}
	}

	function getName(){ return "LeoKim"; };
	function getAge(){ return 31; }
}

代码中Person类接受一个PersonWriter对象作为构造方法的参数,并将它存储再属性变量$writer中。

在__call()方法中,我们使用参数$methodname,检查PersonWriter对象中是否存在同名的方法。

如果相应的方法存在,我们就委托PersonWriter对象来处理对方法的调用,把当前类(Person)的实例作为参数传递给PersonWriter对象(使用$this伪变量)。

因此,可以这样调用Person类:

$person = new Person(new PersonWriter());
$person->writeName();

PHP 闭包

<?php
/** 
 * 下面提到的代码在PHP5.3以上版本运行通过. 
 */  
function callback($callback) {  
    $callback();  
}  

//输出: This is a anonymous function.<br />/n  
//这里是直接定义一个匿名函数进行传递, 在以往的版本中, 这是不可用的.  
//现在, 这种语法非常舒服, 和JavaScript语法基本一致, 之所以说基本呢, 需要继续向下看  
//结论: 一个舒服的语法必然会受欢迎的.  
callback(function() {  
    print "This is a anonymous function.<br />/n";  
});  



//输出: This is a closure use string value, msg is: Hello, everyone.<br />/n  
//这里首先定义了一个闭包, 这次户口本上有名字了...  
//use, 一个新鲜的家伙...  
//众所周知, 闭包: 内部函数使用了外部函数中定义的变量.  
//在PHP新开放的闭包语法中, 我们就是用use来使用闭包外部定义的变量的.  
//这里我们使用了外部变量$msg, 定义完之后, 又对其值进行了改变, 闭包被执行后输出的是原始值  
//结论: 以传值方式传递的基础类型参数, 闭包use的值在闭包创建是就确定了.  
$msg = "Hello, everyone";  
$callback = function () use ($msg) {  
    print "This is a closure use string value, msg is: $msg. <br />/n";  
};  
$msg = "Hello, everybody";  
callback($callback);  


//输出: This is a closure use string value lazy bind, msg is: Hello, everybody.<br />/n  
//换一种引用方式, 我们使用引用的方式来use  
//可以发现这次输出是闭包定义后的值...  
//这个其实不难理解, 我们以引用方式use, 那闭包use的是$msg这个变量的地址  
//当后面对$msg这个地址上的值进行了改变之后, 闭包内再输出这个地址的值时, 自然改变了.  
$msg = "Hello, everyone";  
$callback = function () use (&$msg) {  
    print "This is a closure use string value lazy bind, msg is: $msg. <br />/n";  
};  
$msg = "Hello, everybody";  
callback($callback);  


//输出: This is a closure use object, msg is: Hello, everyone.<br />/n  
//闭包中输出的是之前被拷贝的值为Hello, everyone的对象, 后面是对$obj这个名字的一个重新赋值.  
//可以这样考虑  
//1. obj是对象Hello, everyone的名字  
//2. 对象Hello, everyone被闭包use, 闭包产生了一个对Hello, everyone对象的引用  
//3. obj被修改为Hello, everybody这个对象的名字  
//4. 注意, 是名字obj代表的实体变了, 而不是Hello, everyone对象, 那自然闭包的输出还是前面的Hello, everyone  
$obj = (object) "Hello, everyone";  
$callback = function () use ($obj) {  
    print "This is a closure use object, msg is: {$obj->scalar}. <br />/n";  
};  
$obj = (object) "Hello, everybody";  
callback($callback);  


//输出: This is a closure use object, msg is: Hello, everybody.<br />/n  
//还是按照上面的步骤, 按部就班的来吧:  
//1. obj名字指向Hello, everyone对象  
//2. 闭包产生一个引用指向Hello, everyone对象  
//3. 修改obj名字指向的对象(即Hello, everyone对象)的scalar值  
//4. 执行闭包, 输出的自然是Hello, everybody, 因为其实只有一个真正的对象  
$obj = (object) "Hello, everyone";  
$callback = function () use ($obj) {  
    print "This is a closure use object, msg is: {$obj->scalar}. <br />/n";  
};  
$obj->scalar = "Hello, everybody";  
callback($callback);  


//输出: This is a closure use object lazy bind, msg is: Hello, everybody.<br />/n  
//闭包引用的是什么呢? &$obj, 闭包产生的引用指向$obj这个名字所指向的地址.  
//因此, 无论obj怎么变化, 都是逃不脱的....  
//所以, 输出的就是改变后的值  
$obj = (object) "Hello, everyone";  
$callback = function () use (&$obj) {  
    print "This is a closure use object lazy bind, msg is: {$obj->scalar}. <br />/n";  
};  
$obj = (object) "Hello, everybody";  
callback($callback);  

/** 
 * 一个利用闭包的计数器产生器 
 * 这里其实借鉴的是Python中介绍闭包时的例子... 
 * 我们可以这样考虑: 
 *      1. counter函数每次调用, 创建一个局部变量$counter, 初始化为1. 
 *      2. 然后创建一个闭包, 闭包产生了对局部变量$counter的引用. 
 *      3. 函数counter返回创建的闭包, 并销毁局部变量, 但此时有闭包对$counter的引用,  
 *          它并不会被回收, 因此, 我们可以这样理解, 被函数counter返回的闭包, 携带了一个游离态的 
 *          变量. 
 *      4. 由于每次调用counter都会创建独立的$counter和闭包, 因此返回的闭包相互之间是独立的. 
 *      5. 执行被返回的闭包, 对其携带的游离态变量自增并返回, 得到的就是一个计数器. 
 * 结论: 此函数可以用来生成相互独立的计数器. 
 */  
function counter() {  
    $counter = 1;  
    return function() use(&$counter) {return $counter ++;};  
}  
$counter1 = counter();  
$counter2 = counter();  
echo "counter1: " . $counter1() . "<br />/n";  
echo "counter1: " . $counter1() . "<br />/n";  
echo "counter1: " . $counter1() . "<br />/n";  
echo "counter1: " . $counter1() . "<br />/n";  
echo "counter2: " . $counter2() . "<br />/n";  
echo "counter2: " . $counter2() . "<br />/n";  
echo "counter2: " . $counter2() . "<br />/n";  
echo "counter2: " . $counter2() . "<br />/n";  
?>

php匿名函数和闭包

<?php

class Product{
	public $name;
	public $price;

	function __construct($name, $price){
		$this->name = $name;
		$this->price = $price;
	}
}

class ProcessSale{
	private $callbacks;

	function registerCallback($callback){
		if(!is_callable($callback)){
			throw new Exception("allback not callable");
		}

		$this->callbacks[] = $callback;
	}	

	function sale($product){
		print "{$product->name}:processing \n";
		foreach ($this->callbacks as $callback) {
			call_user_func($callback, $product);
		}
	}
}

class Mailer{
	function doMail($product){
		print "mailing({$product->name})<br/>";
	}
}

class Totalizer{
	static function warnAmount($amt){
		$count = 0;
		// return function ($product){
		// 	if($product->price > 5){
		// 		print "reached high price: {$product->price}<br />";
		// 	}
		// };
		return function ($product) use ($amt, &$count){
			$count += $product->price;
			print "count: $count <br />";
			if($count > $amt){
				print "high price reached:{$count} <br>";
			}
		};
	}
}





// $logger = create_function('$product',
// 						  'print "logging({$product->name})\n";' );

// $logger2 = function($product){
// 	print "logging ({$product->name})<br/>";
// };

$processor = new ProcessSale();
// $processor->registerCallback($logger2);
// $processor->registerCallback(array( new Mailer(), "doMail"));
$processor->registerCallback(Totalizer::warnAmount(8));

$processor->sale( new Product("shose", 6));
print "<br>";
$processor->sale( new Product("coffee", 6));


?>

PHP Static延迟静态绑定,后期静态绑定

这个文章看着表述也是不太明确的

我看到现在只是觉得如果不用延迟静态绑定 那么子类没有重写的function 会调用父类内的“资源”

如果用了延迟静态绑定 那么这个执行结果也就是子类应该有的正确的结果了

可能我表述的也不是那么通俗 233333333

==============================================================

本文实例讲述了PHP Static延迟静态绑定用法。分享给大家供大家参考,具体如下:

PHP5.3以后引入了延迟静态绑定static,它是为了解决什么问题呢?php的继承模型中有一个存在已久的问题,那就是在父类中引用扩展类的最终状态比较困难。来看一个例子。

class A 
{ 
  public static function echoClass(){ 
    echo __CLASS__; 
  }
  public static function test(){ 
    self::echoClass();    
  }
}
class B extends A 
{    
  public static function echoClass() 
  { 
     echo __CLASS__; 
  } 
} 
B::test(); //输出A

在PHP5.3中加入了一个新特性:延迟静态绑定,就是把本来在定义阶段固定下来的表达式或变量,改在执行阶段才决定,比如当一个子类继承了父类的静态表达式的时候,它的值并不能被改变,有时不希望看到这种情况。

下面的例子解决了上面提出的问题:

class A 
{ 
  public static function echoClass(){ 
    echo __CLASS__; 
  } 
  public static function test() 
  { 
    static::echoClass();    
  } 
} 
class B extends A 
{    
  public static function echoClass(){ 
     echo __CLASS__; 
  } 
} 
B::test(); //输出B

第8行的static::echoClass();定义了一个静态延迟绑定方法,直到B调用test的时候才执行原本定义的时候执行的方法。

后期静态绑定的用法

http://php.net/manual/zh/language.oop5.late-static-bindings.php

PHP 设计模式 – 静态方法不用实例化调用

<?php
class ShopProduct {
    private $title;
    private $producerMainName;
    private $producerFirstName;
    protected $price;
    private $discount = 0; 
    private $id = 0;
    
    public function __construct(   $title, $firstName, 
                            $mainName, $price ) { 
        $this->title             = $title;
        $this->producerFirstName = $firstName;
        $this->producerMainName  = $mainName;
        $this->price             = $price;
    }

    public function setID( $id ) {
        $this->id = $id;
    }

    public function getProducerFirstName() {
        return $this->producerFirstName;
    }

    public function getProducerMainName() {
        return $this->producerMainName;
    }

    public function setDiscount( $num ) {
        $this->discount=$num;
    }

    public function getDiscount() {
        return $this->discount;
    }
    
    public function getTitle() {
        return $this->title;
    }

    public function getPrice() {
        return ($this->price - $this->discount);
    }

    public function getProducer() {
        return "{$this->producerFirstName}".
               " {$this->producerMainName}";
    }

    function getSummaryLine() {
        $base  = "$this->title ( $this->producerMainName, ";
        $base .= "$this->producerFirstName )"; 
        return $base;
    }

    public static function getInstance( $id, PDO $pdo ) {
        $query = "select * from products where id='$id'";
        $stmt = $pdo->prepare("select * from products where id=?");
        $result = $stmt->execute( array( $id ) );
        $row = $stmt->fetch( );
        if ( empty( $row ) ) { return null; }

        if ( $row['type'] == "book" ) {
            $product = new BookProduct( 
                                    $row['title'], 
                                    $row['firstname'], $row['mainname'], 
                                    $row['price'], $row['numpages'] ); 
        } else if ( $row['type'] == "cd" ) {
            $product = new CdProduct(
                                    $row['title'], 
                                    $row['firstname'], $row['mainname'], 
                                    $row['price'], $row['playlength'] ); 
        } else {
            $product = new ShopProduct(     
                                    $row['title'], 
                                    $row['firstname'], $row['mainname'], 
                                    $row['price'] ); 
        }
        $product->setId(            $row['id'] );
        $product->setDiscount(      $row['discount'] );
        return $product;
    }
}

class CdProduct extends ShopProduct {
    private $playLength = 0;

    public function __construct(   $title, $firstName, 
                            $mainName, $price, $playLength ) { 
        parent::__construct(    $title, $firstName, 
                                $mainName, $price );
        $this->playLength = $playLength;
    }

    public function getPlayLength() {
        return $this->playLength;
    }

    function getSummaryLine() {
        $base = parent::getSummaryLine();
        $base .= ": playing time - $this->playLength";
        return $base;
    }
 
}

class BookProduct extends ShopProduct {
    private $numPages = 0;

    public function __construct(   $title, $firstName, 
                            $mainName, $price, $numPages ) { 
        parent::__construct(    $title, $firstName, 
                                $mainName, $price );
        $this->numPages = $numPages;
    }

    public function getNumberOfPages() {
        return $this->numPages;
    }
   
    function getSummaryLine() {
        $base = parent::getSummaryLine();
        $base .= ": page count - $this->numPages";
        return $base;
    }

    public function getPrice() {
        return $this->price;
    }
}

require_once("generate_product_pdo.php");
$pdo = getPDO();
$obj = ShopProduct::getInstance( 1, $pdo );
print_r( $obj );
$obj = ShopProduct::getInstance( 2, $pdo );
print_r( $obj );
$obj = ShopProduct::getInstance( 3, $pdo );
print_r( $obj );
?>

这个方法再类中会比在对象中更有用。

我们可以轻松地将原始数据转换为一个对象,而不需要一开始就使用ShopProduct对象。

这个方法并没有使用任何实例属性或方法吗所以没有理由不把他定义为static。

只要有一个有效的PDO对象,我们就可以再程序的任何地方(应该要先把class include进来吧)调用这个方法:

$dsn = "xxxxxxxxxxxxx";
$pdo = new PDO($dns,null,null);
$pdo->getAttribute(xxxxxxxxxxxxxxxxx, xxxxxxxxxxxxx);
$bjg = ShopProduct::getInstance(1, $pdo);

如果这个类的父类已经有了数据库连接的实例应该不用传这个pdo实例就可以直接返回数据了。

ViewConfiguration

blob.png

主要是因为getScaledTouchSlop这个功能才了解这个类的

看起来来挺有用的


getScaledTouchSlop是一个距离,表示滑动的时候,手的移动要大于这个距离才开始移动控件。如果小于这个距离就不触发移动控件,如viewpager就是用这个距离来判断用户是否翻页

ViewConfiguration滑动参数设置类:


/**    
  * 包含了方法和标准的常量用来设置UI的超时、大小和距离    
  */
public class ViewConfiguration {
    // 设定水平滚动条的宽度和垂直滚动条的高度,单位是像素px     
    private static final int SCROLL_BAR_SIZE = 10;

    //定义滚动条逐渐消失的时间,单位是毫秒     
    private static final int SCROLL_BAR_FADE_DURATION = 250;

    // 默认的滚动条多少秒之后消失,单位是毫秒     
    private static final int SCROLL_BAR_DEFAULT_DELAY = 300;

    // 定义边缘地方褪色的长度     
    private static final int FADING_EDGE_LENGTH = 12;

    //定义子控件按下状态的持续事件     
    private static final int PRESSED_STATE_DURATION = 125;

    //定义一个按下状态转变成长按状态的转变时间     
    private static final int LONG_PRESS_TIMEOUT = 500;

    //定义用户在按住适当按钮,弹出全局的对话框的持续时间     
    private static final int GLOBAL_ACTIONS_KEY_TIMEOUT = 500;

    //定义一个touch事件中是点击事件还是一个滑动事件所需的时间,如果用户在这个时间之内滑动,那么就认为是一个点击事件     
    private static final int TAP_TIMEOUT = 115;

    /**    
      * Defines the duration in milliseconds we will wait to see if a touch event     
      * is a jump tap. If the user does not complete the jump tap within this interval, it is    
      * considered to be a tap.     
      */
    //定义一个touch事件时候是一个点击事件。如果用户在这个时间内没有完成这个点击,那么就认为是一个点击事件     
    private static final int JUMP_TAP_TIMEOUT = 500;

    //定义双击事件的间隔时间     
    private static final int DOUBLE_TAP_TIMEOUT = 300;

    //定义一个缩放控制反馈到用户界面的时间     
    private static final int ZOOM_CONTROLS_TIMEOUT = 3000;

    /**    
      * Inset in pixels to look for touchable content when the user touches the edge of the screen    
      */
    private static final int EDGE_SLOP = 12;

    /**    
      * Distance a touch can wander before we think the user is scrolling in pixels    
      */
    private static final int TOUCH_SLOP = 16;

    /**    
      * Distance a touch can wander before we think the user is attempting a paged scroll    
      * (in dips)    
      */
    private static final int PAGING_TOUCH_SLOP = TOUCH_SLOP * 2;

    /**    
      * Distance between the first touch and second touch to still be considered a double tap    
      */
    private static final int DOUBLE_TAP_SLOP = 100;

    /**    
      * Distance a touch needs to be outside of a window's bounds for it to    
      * count as outside for purposes of dismissing the window.    
      */
    private static final int WINDOW_TOUCH_SLOP = 16;

    //用来初始化fling的最小速度,单位是每秒多少像素     
    private static final int MINIMUM_FLING_VELOCITY = 50;

    //用来初始化fling的最大速度,单位是每秒多少像素     
    private static final int MAXIMUM_FLING_VELOCITY = 4000;

    //视图绘图缓存的最大尺寸,以字节表示。在ARGB888格式下,这个尺寸应至少等于屏幕的大小     
    @Deprecated private static final int MAXIMUM_DRAWING_CACHE_SIZE = 320 * 480 * 4; // HVGA screen, ARGB8888     
    //flings和scrolls摩擦力度大小的系数     
    private static float SCROLL_FRICTION = 0.015f;

    /**    
      * Max distance to over scroll for edge effects    
      */
    private static final int OVERSCROLL_DISTANCE = 0;

    /**    
      * Max distance to over fling for edge effects    
      */
    private static final int OVERFLING_DISTANCE = 4;

}

Java RandomAccessFile用法

RandomAccessFile RandomAccessFile是用来访问那些保存数据记录的文件的,你就可以用seek()方法来访问记录,并进行读写了。这些记录的大小不必相同;但是其大小和位置必须是可知的。但是该类仅限于操作文件。

RandomAccessFile不属于InputStream和OutputStream类系的。实际上,除了实现DataInput和DataOutput接口之外 (DataInputStream和DataOutputStream也实现了这两个接口),它和这两个类系毫不相干,甚至不使用InputStream和OutputStream类中已经存在的任何功能;它是一个完全独立的类,所有方法 (绝大多数都只属于它自己)都是从零开始写的。这可能是因为RandomAccessFile能在文件里面前后移动,所以它的行为与其它的I / O类有些根本性的不同。总而言之,它是一个直接继承Object的,独立的类。

基本上,RandomAccessFile的工作方式是,把DataInputStream和DataOutputStream结合起来,再加上它自己的一些方法,比如定位用的getFilePointer(),在文件里移动用的seek(),以及判断文件大小的length()、skipBytes()跳过多少字节数。此外,它的构造函数还要一个表示以只读方式 ("r"),还是以读写方式 ("rw")打开文件的参数 (和C的fopen()一模一样)。它不支持只写文件。

只有RandomAccessFile才有seek搜寻方法,而这个方法也只适用于文件。BufferedInputStream有一个mark()方法,你可以用它来设定标记 (把结果保存在一个内部变量里),然后再调用reset()返回这个位置,但是它的功能太弱了,而且也不怎么实用。

RandomAccessFile的绝大多数功能,但不是全部,已经被JDK 1.4的nio的"内存映射文件(memory-mapped files)"给取代了,你该考虑一下是不是用"内存映射文件"来代替RandomAccessFile了。

import java.io.IOException;
import java.io.RandomAccessFile;
public class TestRandomAccessFile {
    public static void main(String[] args) throws IOException {
        RandomAccessFile rf = new RandomAccessFile("rtest.dat", "rw");
        for (int i = 0; i < 10; i++) {
            //写入基本类型double数据  
            rf.writeDouble(i * 1.414);
        }
        rf.close();
        rf = new RandomAccessFile("rtest.dat", "rw");
        //直接将文件指针移到第5个double数据后面  
        rf.seek(5 * 8);
        //覆盖第6个double数据  
        rf.writeDouble(47.0001);
        rf.close();
        rf = new RandomAccessFile("rtest.dat", "r");
        for (int i = 0; i < 10; i++) {
            System.out.println("Value " + i + ": " + rf.readDouble());
        }
        rf.close();
    }
}

内存映射文件内存映射文件能让你创建和修改那些因为太大而无法放入内存的文件。有了内存映射文件,你就可以认为文件已经全部读进了内存,然后把它当成一个非常大的数组来访问。这种解决办法能大大简化修改文件的代码。fileChannel.map(FileChannel.MapMode mode, long position, long size)将此通道的文件区域直接映射到内存中。注意,你必须指明,它是从文件的哪个位置开始映射的,映射的范围又有多大;也就是说,它还可以映射一个大文件的某个小片断。

MappedByteBuffer是ByteBuffer的子类,因此它具备了ByteBuffer的所有方法,但新添了force()将缓冲区的内容强制刷新到存储设备中去、load()将存储设备中的数据加载到内存中、isLoaded()位置内存中的数据是否与存储设置上同步。这里只简单地演示了一下put()和get()方法,除此之外,你还可以使用asCharBuffer()之类的方法得到相应基本类型数据的缓冲视图后,可以方便的读写基本类型数据。

import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
public class LargeMappedFiles {
    static int length = 0x8000000; // 128 Mb  
    public static void main(String[] args) throws Exception {
        // 为了以可读可写的方式打开文件,这里使用RandomAccessFile来创建文件。  
        FileChannel fc = new RandomAccessFile("test.dat", "rw").getChannel();
        //注意,文件通道的可读可写要建立在文件流本身可读写的基础之上  
        MappedByteBuffer out = fc.map(FileChannel.MapMode.READ_WRITE, 0, length);
        //写128M的内容  
        for (int i = 0; i < length; i++) {
            out.put((byte)'x');
        }
        System.out.println("Finished writing");
        //读取文件中间6个字节内容  
        for (int i = length / 2; i < length / 2 + 6; i++) {
            System.out.print((char) out.get(i));
        }
        fc.close();
    }
}

尽管映射写似乎要用到FileOutputStream,但是映射文件中的所有输出必须使用RandomAccessFile,但如果只需要读时可以使用FileInputStream,写映射文件时一定要使用随机访问文件,可能写时要读的原因吧。

该程序创建了一个128Mb的文件,如果一次性读到内存可能导致内存溢出,但这里访问好像只是一瞬间的事,这是因为,真正调入内存的只是其中的一小部分,其余部分则被放在交换文件上。这样你就可以很方便地修改超大型的文件了 (最大可以到2 GB)。注意,Java是调用操作系统的"文件映射机制"来提升性能的。

RandomAccessFile类的应用:

/* 
 * 程序功能:演示了RandomAccessFile类的操作,同时实现了一个文件复制操作。 
 */
package com.lwj.demo;
import java.io. * ;
public class RandomAccessFileDemo {
    public static void main(String[] args) throws Exception {
        RandomAccessFile file = new RandomAccessFile("file", "rw");
        // 以下向file文件中写数据  
        file.writeInt(20); // 占4个字节  
        file.writeDouble(8.236598); // 占8个字节  
        file.writeUTF("这是一个UTF字符串"); // 这个长度写在当前文件指针的前两个字节处,可用readShort()读取  
        file.writeBoolean(true); // 占1个字节  
        file.writeShort(395); // 占2个字节  
        file.writeLong(2325451l); // 占8个字节  
        file.writeUTF("又是一个UTF字符串");
        file.writeFloat(35.5f); // 占4个字节  
        file.writeChar('a'); // 占2个字节  
        file.seek(0); // 把文件指针位置设置到文件起始处  
        // 以下从file文件中读数据,要注意文件指针的位置  
        System.out.println("——————从file文件指定位置读数据——————");
        System.out.println(file.readInt());
        System.out.println(file.readDouble());
        System.out.println(file.readUTF());
        file.skipBytes(3); // 将文件指针跳过3个字节,本例中即跳过了一个boolean值和short值。  
        System.out.println(file.readLong());
        file.skipBytes(file.readShort()); // 跳过文件中“又是一个UTF字符串”所占字节,注意readShort()方法会移动文件指针,所以不用加2。  
        System.out.println(file.readFloat());
        //以下演示文件复制操作  
        System.out.println("——————文件复制(从file到fileCopy)——————");
        file.seek(0);
        RandomAccessFile fileCopy = new RandomAccessFile("fileCopy", "rw");
        int len = (int) file.length(); //取得文件长度(字节数)  
        byte[] b = new byte[len];
        file.readFully(b);
        fileCopy.write(b);
        System.out.println("复制完成!");
    }
}

RandomAccessFile插入写示例: 

/** 
 *  
 * @param skip 跳过多少过字节进行插入数据 
 * @param str 要插入的字符串 
 * @param fileName 文件路径 
 */
public static void beiju(long skip, String str, String fileName) {
    try {
        RandomAccessFile raf = new RandomAccessFile(fileName, "rw");
        if (skip < 0 || skip > raf.length()) {
            System.out.println("跳过字节数无效");
            return;
        }
        byte[] b = str.getBytes();
        raf.setLength(raf.length() + b.length);
        for (long i = raf.length() - 1; i > b.length + skip - 1; i--) {
            raf.seek(i - b.length);
            byte temp = raf.readByte();
            raf.seek(i);
            raf.writeByte(temp);
        }
        raf.seek(skip);
        raf.write(b);
        raf.close();
    } catch(Exception e) {
        e.printStackTrace();
    }
}

利用RandomAccessFile实现文件的多线程下载,即多线程下载一个文件时,将文件分成几块,每块用不同的线程进行下载。下面是一个利用多线程在写文件时的例子,其中预先分配文件所需要的空间,然后在所分配的空间中进行分块,然后写入:

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
/** 
 * 测试利用多线程进行文件的写操作 
 */
public class Test {
    public static void main(String[] args) throws Exception {
        // 预分配文件所占的磁盘空间,磁盘中会创建一个指定大小的文件  
        RandomAccessFile raf = new RandomAccessFile("D://abc.txt", "rw");
        raf.setLength(1024 * 1024); // 预分配 1M 的文件空间  
        raf.close();
        // 所要写入的文件内容  
        String s1 = "第一个字符串";
        String s2 = "第二个字符串";
        String s3 = "第三个字符串";
        String s4 = "第四个字符串";
        String s5 = "第五个字符串";
        // 利用多线程同时写入一个文件  
        new FileWriteThread(1024 * 1, s1.getBytes()).start(); // 从文件的1024字节之后开始写入数据  
        new FileWriteThread(1024 * 2, s2.getBytes()).start(); // 从文件的2048字节之后开始写入数据  
        new FileWriteThread(1024 * 3, s3.getBytes()).start(); // 从文件的3072字节之后开始写入数据  
        new FileWriteThread(1024 * 4, s4.getBytes()).start(); // 从文件的4096字节之后开始写入数据  
        new FileWriteThread(1024 * 5, s5.getBytes()).start(); // 从文件的5120字节之后开始写入数据  
    }
    // 利用线程在文件的指定位置写入指定数据  
    static class FileWriteThread extends Thread {
        private int skip;
        private byte[] content;
        public FileWriteThread(int skip, byte[] content) {
            this.skip = skip;
            this.content = content;
        }
        public void run() {
            RandomAccessFile raf = null;
            try {
                raf = new RandomAccessFile("D://abc.txt", "rw");
                raf.seek(skip);
                raf.write(content);
            } catch(FileNotFoundException e) {
                e.printStackTrace();
            } catch(IOException e) {
                // TODO Auto-generated catch block  
                e.printStackTrace();
            } finally {
                try {
                    raf.close();
                } catch(Exception e) {}
            }
        }
    }
}

Android中OkHttp的使用

前段时间研究了下Android里面非常火爆的网络请求库OkHttp,这篇文章主要来介绍下OkHttp的常用请求的使用方式

一、说明对于OkHttp的基本介绍,以及为什么要使用OkHttp而不再使用HttpURLConnection或者是HttpClient,各位自己找度娘去,我这里就不再废话了。

使用OkHttp之前,需要先下载OkHttp.jar包,大家可以在https: //github.com/square/okhttp去下载OkHttp的最新jar包以及源码,

同时因为OkHttp内部依赖okio,所以大家还要自行下载okio.jar包,下载地址在这里https: //github.com/square/okio

二、使用教程1.Get请求

//创建OkHttpClient对象,用于稍后发起请求

OkHttpClient client = new OkHttpClient();

//根据请求URL创建一个Request对象

Request request = new Request.Builder().url("https://github.com/huyongli/TigerOkHttp").build();

//根据Request对象发起Get同步Http请求

Response response = client.newCall(request).execute();

//根据Request对象发起Get异步Http请求,并添加请求回调

client.newCall(request).enqueue(new Callback() {@Override public void onResponse(final Response response) throws IOException {
        //请求成功,此处对请求结果进行处理
        //String result = response.body().string();
        //InputStream is = response.body().byteStream();
        //byte[] bytes = response.body().bytes();
    }
        @Override public void onFailure(Request request, IOException e) {
        //请求失败
          }
});

上面的代码示例演示了OkHttp中一个普通的Http get请求是如何实现的,对于上面的代码,作一下简单的说明

1.既然是get请求,当然得先构造好你要请求的URL

2.有了请求URL,紧接着就是需要通过这个URL构造一个请求对象Request

3.当然有时候可能你需要对这个Http请求添加一些自定义的请求头信息header,这时你在构造Request对象之前通过Request.Builder builder = new Request.Builder()创建的builder对象来添加自己需要添加的请求头信息builder.addHeader(key, value)

4.OkHttp是自带请求缓存控制策略的,如果你想改变某个请求的缓存控制策略,你也可以通过builder对象来修改缓存策略builder.cacheControl()

5.通过上述步骤构造好请求对象Request之后,通过OkHttpClient创建一个Call任务对象,这个对象有execute()和cancel()等方法对Call任务对象进行执行和取消

6.如果是同步阻塞请求的话,直接执行Call对象的execute()方法即可得到请求结果。

7.如果是异步请求的话,就需要执行Call对象的enqueue(new Callback() {})方法,将任务对象添加到任务请求调度队列中,同时添加请求回调接口。

8.请求成功之后,可以得到一个Response对象,如果想获得返回的字符串结果则可以通过response.body().string(),如果想获得返回结果的二进制数据的话可以通过response.body().bytes(),如果想获得返回的InputStream的话可以通过response.body().byteStream()

9.通过上面我们可以发现在Response对象中我们可以获取InputStream对象,所以从此处我们可以看出在异步请求onResponse(Response response)回调方法中我们可以通过IO方式来进行写文件,所以在此回调中我们可以进行大文件的下载处理,同时也说明此回调并不是在UI线程中执行的,所以此处如果要进行UI操作的话各位需要自行处理下。

2.Post请求

上面对OkHttp的Get请求分析完之后,OkHttp的整个请求流程和处理流程就清晰,Post请求和Get请求的流程都是一样,区别只是在于Request对象构造上有区别。

//创建OkHttpClient对象
OkHttpClient client = new OkHttpClient();
//通过FormEncodingBuilder对象添加多个请求参数键值对
FormEncodingBuilder builder = new FormEncodingBuilder();
builder.add("method", "post").add("param", value);
//通过FormEncodingBuilder对象构造Post请求体
RequestBody body = builder.build();
//通过请求地址和请求体构造Post请求对象Request
Request request = new Request.Builder().url(url).post(body).build();

上面的代码演示了如何构造普通Post请求 (不包含文件上传)的请求体和请求对象,请求对象构造成功之后,后面的请求过程和处理流程就和Get请求是一样的了。

3.文件上传

//多个图片文件列表 
List < File > list = new ArrayList < File > ();
//多文件表单上传构造器
MultipartBuilder multipartBuilder = new MultipartBuilder().type(MultipartBuilder.FORM);
//添加一个文本表单参数
multipartBuilder.addFormDataPart("method", "upload上传");
for (File file: list) {
    if (file.exists()) {
        multipartBuilder.addFormDataPart(file.getName(), file.getName(), RequestBody.create(MediaType.parse("image/png"), file));
    }
}
//构造文件上传时的请求对象Request
Request request = new Request.Builder().url(url).post(multipartBuilder.build()).build();

文件上传时的重点是通过MultipartBuilder构造器添加要上传的文件和表单参数,OkHttp中文件上传时,没有可以直接实现上传进度显示的接口,这个需要手动对OkHttp中的请求体进行扩展,这篇文章中就不写了,在下篇文章介绍TigerOkHttp时我会进行介绍。

4.文件下载

文件下载功能在第一点Get请求的几点说明中已经介绍过了,无论你是通过Get请求还是Post请求,只需要从请求结果对象Response中获取到结果输入流InputStream,然后通过IO操作进行写文件即可实现文件下载功能。在通过IO操作写文件时,也可以自行实现下载进度显示的功能。

5.编码问题

在OkHttp中,无论是Get请求还是Post请求所有参数的编码方式都是默认采用UTF – 8的编码方式进行编码。

所以在进行Get请求或Post请求时,文本参数都不需要我们进行手动编码,但是在服务端进行参数解析时,需要做简单的处理,这里我只说明tomcat下运行的Java后台服务的处理方式:

1.针对Get请求,我们只需要在tomcat的server.xml文件中设置默认编码方式 (URIEncoding = "UTF-8")为UTF – 8即可,注意此编码设置只对Get请求有效。

2.针对Post请求,我们在解析获取参数值之前需要手动设置请求的编码方式为UTF – 8,如:req.setCharacterEncoding("UTF-8");

以上就是OkHttp最基本的用法,下篇文章我会介绍我自己基于OkHttp更进一步封装的网络请求库TigerOkHttp,它主要包含了如下功能特点:

1.一般的get同步阻塞请求和异步请求

2.一般的post同步阻塞请求和异步请求

3.实现了文件上传功能(包含文件上传进度回调显示)

4.实现了大文件下载功能,只需要指定文件下载路径即可,也包含了下载进度的回调显示

5.实现了请求结果的自动解析,用户也可以根据需求扩展自定义结果解析类

6.对所有请求都支持直接将结果解析转换为JavaBean对象或集合

7.支持对返回结果结构的自定义,例如设置返回结果结构为: {

    flag:1 | 0,error:错误信息,result:请求结果

},结果解析的时候会按照此结构进行结果解析

8.支持取消某个请求