<?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"; ?>
月度归档: 2017年2月
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
主要是因为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.支持取消某个请求
InputStream的三个read的区别
1.read
这个方法是对这个流一个一个字节的读,返回的int就是这个字节的int表示方式
以下是代码片段,经过测试当eclipse的编码为gbk时,转化出的字符串不需经过重新编码,如果eclipse的编码为utf-8时则由byte转成字符串需重新编成utf-8的
InputStream in = Test.class.getResourceAsStream("/tt.txt"); byte[]tt=new byte[15];//测试用的事前知道有15个字节码 while(in.available()!=0){ for(int i=0;i<15;i++){ tt[i]=(byte)in.read(); } } String ttttt=new String(tt,"utf-8"); System.out.println(ttttt); in.close();
2.read(byte[] b)
这个方法是先规定一个数组长度,将这个流中的字节缓冲到数组b中,返回的这个数组中的字节个数,这个缓冲区没有满的话,则返回真实的字节个数,到未尾时都返回-1
in = Test.class.getResourceAsStream("/tt.txt"); byte [] tt=new byte[1024]; int b; while((b=in.read(tt))!=-1){ System.out.println(b); String tzt=new String(tt,"utf-8"); System.out.println(tzt);
3.read(byte[] b, int off, int len)
此方法其实就是多次调用了read()方法
InputStream in = Test.class.getResourceAsStream("/tt.txt"); //System.out.println(in.available());//此方法是返回这个流中有多少个字节数,可以把数组长度定为这个 byte[]tt=new byte[in.available()]; int z; while((z=in.read(tt, 0, tt.length))!=-1){ System.out.println(new String(tt,"utf-8")); }
Intent 详解
一、Intent的用途
Intent主要有以下几种重要用途:
1. 启动Activity:可以将Intent对象传递给startActivity()方法或startActivityForResult()方法以启动一个Activity,该Intent对象包含了要启动的Activity的信息及其他必要的数据。
2. 启动Service:可以将Intent对象传递给startService()方法或bindService()方法以启动一个Service,该Intent对象包含了要启动的Service的信息及其他必要的数据。关于使用startService()方法启动Service,可以参见《Android中startService基本使用方法概述》。关于使用bindService()方法启动Service,可以参见《Android中bindService基本使用方法概述》。
3. 发送广播:广播是一种所有App都可以接收的信息。Android系统会发布各种类型的广播,比如发布开机广播或手机充电广播等。我们也可以给其他的App发送广播,可以将Intent对象传递给sendBroadcast()方法或sendOrderedBroadcast()方法或sendStickyBroadcast()方法以发送自定义广播。
二、Intent的类型
有两种类型的Intent:explicit(显式)的和implict(隐式)的。
显式的Intent:如果Intent中明确包含了要启动的组件的完整类名(包名及类名),那么这个Intent就是explict的,即显式的。使用显式Intent最典型的情形是在你自己的App中启动一个组件,因为你自己肯定知道自己的要启动的组件的类名。比如,为了响应用户操作通过显式的Intent在你的App中启动一个Activity或启动一个Service下载文件。
隐式的Intent:如果Intent没有包含要启动的组件的完整类名,那么这个Intent就是implict的,即隐式的。虽然隐式的Intent没有指定要启动的组件的类名,但是一般情况下,隐式的Intent都要指定需要执行的action。一般,隐式的Intent只用在当我们想在自己的App中通过Intent启动另一个App的组件的时候,让另一个App的组件接收并处理该Intent。例如,你想在地图上给用户显示一个位置,但是你的App又不支持地图展示,这时候你可以将位置信息放入到一个Intent中,然后给它指定相应的action,通过这样隐式的Intent请求其他的地图型的App(例如Google Map、百度地图等)来在地图中展示一个指定的位置。隐式的Intent也体现了Android的一种设计哲学:我自己的App无需包罗万象所有功能,可以通过与其他App组合起来,给用户提供很好的用户体验。而连接自己的App与其他App的纽带就是隐式Intent。
当创建了一个显式Intent去启动Activity或Service的时候,系统会立即启动Intent中所指定的组件。
当创建了一个隐式Intent去使用的时候,Android系统会将该隐式Intent所包含的信息与设备上其他所有App中manifest文件中注册的组件的Intent Filters进行对比过滤,从中找出满足能够接收处理该隐式Intent的App和对应的组件。如果有多个App中的某个组件都符合条件,那么Android会弹出一个对话框让用户选择需要启动哪个App。
Intent Filter,即Intent过滤器,一个组件可以包含0个或多个Intent Filter。Intent Filter是写在App的manifest文件中的,其通过设置action或uri数据类型等指明了组件能够处理接收的Intent的类型。如果你给你的Activity设置了Intent Filter,那么这就使得其他的App有可能通过隐式Intent启动你的这个Activity。反之,如果你的Activity不包含任何Intent Filter,那么该Activity只能通过显式Intent启动,由于我们一般不会暴露出我们组件的完整类名,所以这种情况下,其他的App基本就不可能通过Intent启动我们的Activity了(因为他们不知道该Activity的完整类名),只能由我们自己的App通过显式Intent启动。
需要注意的是,为了确保App的安全性,我们应该总是使用显式Intent去启动Service并且不要为该Service设置任何的Intent Filter。通过隐式的Intent启动Service是有风险的,因为你不确定最终哪个App中的哪个Service会启动起来以响应你的隐式Intent,更悲催的是,由于Service没有UI的在后台运行,所以用户也不知道哪个Service运行了。从Android 5.0 (API level 21)开始,用隐式Intent调用bindService()方法,Android会抛出异常,但是也有相应技巧,将一个隐式的Intent转换为显式的Intent,然后用显式的Intent去调用bindService()方法就没有问题了,具体解决办法可以参见博文《Android中通过Messenger与Service实现进程间双向通信》中最后的“注意事项”部分,里面有相关代码的解决方案。
三、Intent的组成
Android可以根据Intent所携带的信息去查找要启动的组件,Intent还携带了一些数据信息以便要启动的组件根据Intent中的这些数据做相应的处理。
Intent由6部分信息组成:Component Name、Action、Data、Category、Extras、Flags。根据信息的作用用于,又可分为三类:
a. Component Name、Action、Data、Category为一类,这4中信息决定了Android会启动哪个组件,其中Component Name用于在显式Intent中使用,Action、Data、Category、Extras、Flags用于在隐式Intent中使用。
b. Extras为一类,里面包含了具体的用于组件实际处理的数据信息。
c. Flags为一类,其是Intent的元数据,决定了Android对其操作的一些行为,下面会介绍。
Component name
要启动的组件的名称。如果你想使用显式的Intent,那么你就必须指定该参数,一旦设置了component name,Android会直接将Intent传递给组件名所指定的组件去启动它。如果没有设置component name,那么该Intent就是隐式的,Android系统会根据其他的Intent的信息(例如下面要介绍到的action、data、category等)做一些比较判断决定最终要启动哪个组件。所以,如果你启动一个你自己App中的组件,你应该通过指定component name通过显式Intent去启动它(因为你知道该组件的完整类名)。
需要注意的是,当启动Service的时候,你应该总是指定Component Name。否则,你不确定最终哪个App的哪个组件被启动了,并且用户也看不到哪个Service启动了。
component name在Intent中对应的field是ComponentName对象,你可以通过要启动的组件的完整类名(包括应用的包名)指定该值,例如com.example.ExampleActivity。你可以通过Intent的setComponent()方法、setClass()方法、setClassName()方法或Intent的构造函数指定component name。
Action
是表示了要执行操作的字符串,比如查看或选择,其对应着Intent Filter中的action标签<action />。
你可以指定你独有的action以便于你的App中的Intent的使用或其他App中通过Intent调用你的App中的组件。Intent类和Android中其他framework级别的一些类也提供了许多已经定义好的具有一定通用意义的action。以下是一些用于启动Activity的常见的action:
Intent.ACTION_VIEW 其值为 “android.intent.action.VIEW”,当你有一些信息想让通过其他Activity展示给用户的时候,你就可以将Intent的action指定为ACTION_VIEW,比如在一个图片应用中查看一张图片,或者在一个地图应用中展现一个位置。
Intent.ACTION_SEND 其值为”android.intent.action.SEND”,该action常用来做“分享”使用,当你有一些数据想通过其他的App(例如QQ、微信、百度云等)分享出去的时候,就可以使用此action构建Intent对象,并将其传递给startActivity()方法,由于手机上可能有多个App的Activity均支持ACTION_SEND这一action,所以很有可能会出现如下的图片所示的情形让用户具体选择要通过哪个App分享你的数据:
可以通过查看Intent类了解更多的Intent预定义的一些常见的action。Android中framework级别的一些类也定义了一些action,例如Settings中定义了一些action用以分别打开系统中“设置”这个应用的不同界面以完成对指定配置(如WLAN设置、语言设置等)。
你可以通过调用intent对象的setAction()方法或在Intent的构造函数中指定intent的action。
如果你定义了你自己的action,请务必将你的App的包名作为该action的前缀,这是一种良好的编程习惯,避免造成混淆,例如:
复制代码 代码如下:
static final String ACTION_TIMETRAVEL = "com.example.action.TIMETRAVEL";
Data
此处所说的Intent中的data指的是Uri对象和数据的MIME类型,其对应着Intent Filter中的data标签<data />。
一个完整的Uri由scheme、host、port、path组成,格式是<scheme>://<host>:<port>/<path>,例如content://com.example.project:200/folder/subfolder/etc。Uri就像一个数据链接,组件可以根据此Uri获得最终的数据来源。通常将Uri和action结合使用,比如我们将action设置为ACTION_VIEW,我们应该提供将要被编辑修改的文档的Uri。
当创建了一个Intent对象的时候,除了指定Uri之外,指定数据的MIME类型也很重要。例如,一个Activity能够显示图片,但是不能够播放视频,显示图片的Uri和播放视频的Uri可能很类似,为了不让Android误将一个含有视频Uri的Intent对象传递给一个只能显示图片的Activity,我们需要在该Activity的Intent Filter中指定MIME类型为图片(例如<data android:mimeType="image/*" … />)并且还要给Intent对象设置对应的图片类型的MIME,这样Android就会基于Uri和MIME类型将Intent传递给符合条件的组件。然后有个特例,如果Uri使用的是content:协议,那么这就说明Uri所提供的数据将来自于本地设备,即数据由ContentProvider提供,这种情况下Android会根据Uri自动推断出MIME类型,此种情况我们无需再自己指定MIME类型。
如果只设置数据的Uri,需要调用Intent对象的setData()方法;如果只设置数据的MIME类型,需要调用Intent对象的setType()方法;如果要同时设置数据的Uri和MIME类型,需要调用Intent对象的setDataAndType()方法。
需要注意的是,如果你想要同时设置数据的Uri和MIME类型,不要先后调用Intent对象的setData()方法和setType()方法,因为setData()方法和setType()是互斥的,即如果调用了setData()方法,会将Intent中已经通过setType()方法设置的MIME类型重置为空。如果调用了setType()方法,会将Intent中已经通过setData()方法设置的Uri重置为空。所以在需要同时设置数据的Uri和MIME类型的时候,一定要调用Intent对象的setDataAndType()方法,而不是分别调用setData()方法和setType()方法。
Category
category包含了关于组件如何处理Intent的一些其他信息,虽然可以在Intent中加入任意数量的category,但是大多数的Intent其实不需要category。
以下是一些常见的category:
CATEGORY_BROWSABLE 目标组件会允许自己通过一个链接被一个Web浏览器启动,该链接可能是一个图片链接或e-mail信息等。
CATEGORY_LAUNCHER 用于标识Activity是某个App的入口Activity。
你可以在Intent类中查找到更多预定义的category。
Extras
extras,顾名思义,就是额外的数据信息,Intent中有一个Bundle对象存储着各种键值对,接收该Intent的组件可以从中读取出所需要的信息以便完成相应的工作。有的Intent需要靠Uri携带数据,有的Intent是靠extras携带数据信息。
你可以通过调用Intent对象的各种重载的putExtra(key, value)方法向Intent中加入各种键值对形式的额外数据。你也可以直接创建一个Bundle对象,向该Bundle对象传入很多键值对,然后通过调用Intent对象的putExtras(Bundle)方法将其一块设置给Intent对象中去。
例如,你创建了一个action为ACTION_SEND的Intent对象,然后想用它启动e-mail发送邮件,那么你需要给该Intent对象设置两个extra的值:
用Intent.EXTRA_EMAIL 作为key值设置收件方,用Intent.EXTRA_SUBJECT 作为key值设置邮件标题。
Intent类里面也指定了很多预定义的EXTRA_*形式的extra,例如上面我们提到的(Intent.EXTRA_EMAIL 和Intent.EXTRA_SUBJECT)。如果你想要声明你自己自定义的extra,请确保将你的App的包名作为你的extra的前缀,例如:
复制代码 代码如下:
static final String EXTRA_GIGAWATTS = "com.example.EXTRA_GIGAWATTS";
Flags
flag就是标记的意思,Intent类中定义的flag能够起到作为Intent对象的元数据的作用。这些flag会告知Android系统如何启动Activity(例如,新启动的Activity属于哪个task)以及在该Activity启动后如何对待它(比如)。更多信息可参见Intent的setFlags()方法。
1、显式Intent使用示例
Intent intent = new Intent(this, ActivityB.class); startActivity(intent);
上面的代码在Intent的构造函数中指定了要启动的组件的ComponentName是ActivityB,该intent对象是显式的,调用startActivity(intent)时,Android系统会立即启动ActivityB。
2、隐式Intent使用示例
之前提到过,在使用隐式Intent的时候需要指定其action。如果你的App不能完成某个功能,但是其他的App可能完成该功能,那么你就可以用隐式Intent启动其他的App去完成相应的功能。例如,你有一段文本信息,想通过其他App分享出去,那么隐式Intent对象去启动潜在的支持分享的App,示例代码如下:
Intent sendIntent = new Intent(); // 设置action, action对隐式Intent来说是非常重要的 sendIntent.setAction(Intent.ACTION_SEND); // 设置数据的MIME类型为纯文本类型 sendIntent.setType("text/plain"); // 设置额外的数据 sendIntent.putExtra(Intent.EXTRA_TEXT, textMessage); // 获取包管理器 PackageManager pm = getPackageManager(); // 先判断系统中有没有潜在的App的Activity支持对该sendIntent的接收与处理 if (pm.resolveActivity(sendIntent, 0) != null) { startActivity(sendIntent); }
上面的代码中,我们构建了一个Intent对象,并没有给其设置component name,所以该Intent是一个隐式的Intent对象。我们首先给intent设置了action的值为Intent.ACTION_SEND,action对隐式Intent来说是非常重要的。然后我们将intent的数据的MIME类型设置为纯文本类型(“text/plain”),告知Android我们的Intent持有的是文本类型的数据。最后我们将实际的文本数据通过putExtra()方法作为额外数据设置进去。
需要注意的是,在构建好了Intent对象之后,我们没有立即执行startActivity(sendIntent)方法,而是将sendIntent作为参数传递给了PackageManager的resolveActivity()方法中,该方法会让Android根据该sendIntent找到潜在的适合启动的组件的信息,并以ResolveInfo类的对象的形式返回结果,如果返回null,表示当前系统中没有任何组件可以接收并处理该sendIntent。如果返回不是null,就表明系统中至少存在一个组件可以接收并处理该sendIntent,只有在这种情况下,我们才会执行代码startActivity(sendIntent),在通过intent启动组件之前先判断要启动的组件存不存在是个良好的编程习惯,因为如果系统中不存在支持你的intent的组件,那么当你调用startActivity()、startService()、bindService()等方法的时候,Android就会抛出异常。
四、强制用户使用App Chooser
在上文中我们已经提到,如果我们的Intent是隐式的,当我们通过startActivity(intent)尝试启动组件的时候,可能Android系统会显示上面的截图文件询问用户要启动哪个App,有时候用户会将某一个App设置为默认的App,这样下次我们再执行代码startActivity(intent)的时候就有可能不会再出现选择App的界面,而是直接运行上次用户设置为默认App的应用。这对于用户选择一个默认浏览器打开网页这种情形是有好处的,因为一般一个用户习惯于用一个自己喜欢的浏览器。
但是如果用户不想每次都用同一个默认App处理这样的情形怎么办呢?这时候我们可以在代码中明确地使用App选择对话框,比如党我们的App执行一个action为ACTION_SEND的分享功能时,我们想让用户分享自己数据的代码,但是我们不确定用户想通过哪个App去分享,我们想每次都弹出App选择对话框让用户决定想通过哪个App分享,示例代码如下所示:
Intent sendIntent = new Intent(Intent.ACTION_SEND); String title = "请选择想通过哪个App分享数据"; // 验证是否有App能够接收并处理sendIntent if (sendIntent.resolveActivity(getPackageManager()) != null) { // 根据sendIntent创建一个需要显示App选择对话框的intent对象 Intent chooserIntent = Intent.createChooser(sendIntent, title); // 我们使用chooserIntent作为startActivity()方法的参数,而非sendIntent startActivity(chooserIntent); }
首先我们创建了我们原始的sendIntent,并对其设置action等相关信息,然后我们将sendIntent传递给了Intent.createChooser()方法中,创建了另一个chooserIntent。后面我们通过调用Intent.resolveActivity(PackageManager)方法判断系统中是否有App能够接收并处理sendIntent,该方法与上面之前提到过的PackageManager的resolveActivity()方法是等价的。最后我们使用chooserIntent作为startActivity()方法的参数,而非sendIntent,chooserIntent会让Android系统强制显示用户选择App处理Intent的界面。
本文大部分参考了Android中对Intent部分的Develop Guide的描述
Android ServiceConnection
绑定到一个Service
应用组件 (客户端)可以调用bindService()绑定到一个service.
Android系统之后调用service的onBind()方法,它返回一个用来与service交互的IBinder,绑定是异步的.
bindService()会立即返回,它不会返回IBinder给客户端.
要接收IBinder,客户端必须创建一个ServiceConnection的实例并传给bindService().
ServiceConnection包含一个回调方法,系统调用这个方法来传递要返回的IBinder.
注:只有activities,services,和contentproviders可以绑定到一个service,你不能从一个broadcastreceiver绑定到service.
所以,从你的客户端绑定到一个service,你必须:
1.实现ServiceConnection
你的实现必须重写两个回调方法:
onServiceConnected()
系统调用这个来传送在service的onBind()中返回的IBinder.
OnServiceDisconnected()
Android系统在同service的连接意外丢失时调用这个.比如当service崩溃了或被强杀了.当客户端解除绑定时,这个方法不会被调用.
2.调用bindService(),传给它ServiceConnection的实现
3.当系统调用你的onServiceConnected()方法时,你就可以使用接口定义的方法们开始调用service了
4.要与service断开连接,调用unbindService()
当你的客户端被销毁,它将从service解除绑定,但是你必须总是在你完成与service的交互时或当你的activity暂停或是service在不被使用时可以关闭此两种情况下解除绑定. (下面会讨论更多在适当的时候绑定和解除绑定的问题.)
下面是"派生Binder类"中创建的代码片段,它把客户端连接到了service.
所需要做的就是把返回的IBinder强制转换(向下转型)到LocalBinder类并且请求LocalService实例:
private ServiceConnection mConnection = new ServiceConnection() { // 当与service的连接建立后(bindService)被调用 public void onServiceConnected(ComponentName className, IBinder service) { // Because we have bound to an explicit // service that is running in our own process, we can // cast its IBinder to a concrete class and directly access it. LocalBinder binder = (LocalBinder) service; mService = binder.getService(); mBound = true; } // 当与service的连接意外断开时被调用 public void onServiceDisconnected(ComponentName className) { Log.e(TAG, "onServiceDisconnected"); mBound = false; } };
使用这个ServiceConnection,客户端可以绑定到一个service,通过把它传给bindService().例如:
Intentintent = new Intent(this, LocalService.class); bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
第一个bindService()的参数是一个明确指定了要绑定的service的Intent.
第二个参数是ServiceConnection对象.
第三个参数是一个标志,它表明绑定中的操作.它一般应是BIND_AUTO_CREATE,这样就会在service不存在时创建一个.其它可选的值是BIND_DEBUG_UNBIND和BIND_NOT_FOREGROUND,
不想指定时设为0即可.
补充事项下面是一些关于绑定到service的重要事项:
你总是需要捕获DeadObjectException异常.它会在连接被打断时抛出.这是被远程方法抛出的唯一异常.
对象引用计数是跨进程的作用的.
你应该在客户端的生命期内使绑定和解除绑定配对进行,例如:
如果你需要在你的activity可见时与service交互,你应该在onStart()绑定并在onStop()中解除绑定.
如果你想让你的activity即使在它停止时也能接收回应,那么你可以在onCreate()中绑定并在onDestroy()中解除绑定.
注意这意味着你的activity需要使用在自己整个运行期间使用service(即使位于后台),所以如果service在另一个进程中,那么你增加了这个进程的负担而使它变得更容易被系统杀掉.
注:你一般不应该在你的activity的onResume()和onPause()中绑定和解除绑定到service,因为这些回调方法,出现在每个生命期变化中,并且你需要使发生在这些变化中的处理最小化.还有,如果你应用中的多个activity绑定到同一个service,并且有一个变化发生在其中两个activity之间,service可能在当前activity解除绑定 (pause中)和下一个绑定前 (rusume中)被销毁又重建.
管理BoundService的生命期 当一个service的所有客户端都解除绑定,Android系统就销毁它 (除非它是从onStartCommand()启动).如果你的service是一个纯boundservice,你不需管理它的生命期—Android系统会为你管理它.
然而,如果你选择了实现onStartCommand()回调方法,那么你必须明确地停止service,因为service现在被认为是"开始的".在此情况下,service会一直运行,直到service使用stopSelf()停止它自己或另外的组件调用了stopService()停止了它,不管是否有客户端绑定了它.
另外,如果你的service已经启动并且接受绑定,那么当系统调用你的onUnbind()方法,你可以选择返回true表示你想在客户端下一次绑定到service时接受一个对onRebind()的调用 (而不是一个对onBind()的调用).onRebind()返回void,但是客户端依然会在它的onServiceConnected()回调中接收到IBinder.下图演示了这种生命其的逻辑:
图1.一个已开始并已绑定的service的生命期