Quantcast
Channel: CSDN博客移动开发推荐文章
Viewing all 5930 articles
Browse latest View live

android N(7.0)适配

$
0
0

Android N 适配

权限更改

Android6.0引入了动态权限控制,7.0使用了”私有目录被限制访问“,”Strict Mode API 政策”,这些更改在为用户提供一个更加安全的操作系统的同时,也给开发者带来了新的任务。

目录被限制访问

在Android中应用可以读写手机存储中任何一个目录和文件,这给系统安全带来了很多问题。

7.0中为了提高私有文件的安全性,面向7.0及更高版本的应用私有目录将被限制访问。

  • 私有文件的文件权限不在放权给所有的应用,在manifest里使用MODE_WORLD_READABLEMODE_WORL_WRITEABLE进行的操作将触发SecurityException。

应对策略:这项权限的更改将意味着你无法通过File API来访问手机存储上的数据了,基于File API的一些文件浏览等也将受到很大的限制。不过迄今为止,这种限制尚不能完全执行,应用仍可能使用File API来修改它们的私有文件权限。但是,Android官方强烈反对放宽私有目录的权限。

  • 给其他应用传递file://URI这种URI类型,可能导致接收者无法访问该路径。因此,在7.0中尝试传递file://URI会触发FileUriExposedException。

应对策略:可以使用FileProvider来解决这个问题。

  • DownloadManager不再按文件名分享死人存储的路径。COLUMN_LOCAL_FILENAME在7.0中被标记为deprecated,7.0及更高版本的应用在尝试访问时会触发SecurityException。

应对策略:可以通过ContentProvider.openFileDescriptor()来访问由DownloadManager公开的文件。

应用间共享文件

在Android7.0版本上,Android系统强制执行了StrictMode API 政策,禁止向你的应用外公开File://URI。如果一项包含文件File://URI类型的Intent离开你的应用,应用失败,并出现FileUriExposedException,比如系统相机拍照,裁剪照片

在Android 7.0系统调用相机拍照,裁剪照片

在7.0之前调用系统相机拍照:


File file=new File(Environment.getExternalStorageDirectory(),

"/temp/"+System.currentTimeMillis() + ".jpg"); 

if (!file.getParentFile().exists()) {

    file.getParentFile().mkdirs();

}

Uri imageUri = Uri.fromFile(file); 

Intent intent = new Intent();

intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE);//设置Action为拍照 

intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);//将拍取的照片保存到指定URI 

startActivityForResult(intent,1006);

在7.0上会抛出异常:


android.os.FileUriExposedException:file:////storage/emulated/0/temp/1474956193735.jpg exposed beyond app through Intent.getData() 

at android.os.StrictMode.onFileUriExposed(StrictMode.java:1799) 

at android.net.Uri.checkFileUriExposed(Uri.java:2346) 

at android.content.Intent.prepareToLeaveProcess(Intent.java:8933) 

at android.content.Intent.prepareToLeaveProcess(Intent.java:8894) 

at android.app.Instrumentation.execStartActivity(Instrumentation.java:1517) 

at android.app.Activity.startActivityForResult(Activity.java:4223) 

... 

at android.app.Activity.startActivityForResult(Activity.java:4182)

这是因为7.0执行了“StrictMode API 政策”。

应对策略:使用FilrProvider来解决这一个问题

使用FileProvider

  1. 在manifest里注册provider

    
    <provider 
    
        android:name="android.support.v4.content.FileProvider"
    
        android:authorities="com.jph.takephoto.fileprovider"
    
        android:grantUriPermissions="true"
    
        android:exported="false">
    
         <meta-data 
    
            android:name="android.support.FILE_PROVIDER_PATHS"
    
            android:resource="@xml/file_paths"/> 
    
    </provider>
    

    exported必须要求为false,为true则会报安全异常。grantUriPermissions为true,表示授予URI临时访问权限。

  2. 指定共享目录

    为了指定共享的目录我们需要在资源目录下(res)创建一个xml目录,然后创建一个名为“file_paths”(名字可以随便起,只要和在manifest里注册的provider所引用的resource保持一致即可)的资源文件

    
    <?xml version="1.0" encoding="utf-8"?>
    
    <resources>
    
        <paths>
    
            <external-path path="" name="camera_photos" />
    
        </paths>
    
    </resources>
    
    • 代表的根目录: Context.getFilesDir()

    • 代表的根目录: Environment.getExternalStorageDirectory()

    • 代表的根目录: getCacheDir()

    path=”“是有意义的,它代表根目录,你可以向其他的应用共享根目录及其子目录下的任何一个文件。若设置path=”pictures”,它代表着根目录下的pictures目录,那么你想向其他应用共享pictures目录范围之外的文件是不可行的。

  3. 使用FileProvider

    上述工作做完之后我们就可以使用FileProvider了,以调用相机为例:

    
    File file=new File(Environment.getExternalStorageDirectory(),
    
            "/temp/"+System.currentTimeMillis() + ".jpg");
    
    if (!file.getParentFile().exists()) {
    
        file.getParentFile().mkdirs();
    
    }
    
    Uri imageUri = FileProvider.getUriForFile(context,
    
        "com.jph.takephoto.fileprovider", file);//通过FileProvider创建一个content类型的Uri
    
    Intent intent = new Intent();
    
    intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); //添加这一句表示对目标应用临时授权该Uri所代表的文件
    
    intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE);//设置Action为拍照
    
    intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);//将拍取的照片保存到指定URI
    
    startActivityForResult(intent,1006);
    

    上面的代码有两处改变:

    1. 将之前Uri的scheme类型为file的Uri改成了有FileProvider创建一个content类型的Uri。

    2. 添加了intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);来对目标应用临时授权该Uri所代表的文件。

    通过FileProvider的getUriForFile(Context context, String authority, File file)静态方法来获取URI,方法中的authority就是manifest里注册provider使用的authority。

    getUriForFile方法返回的Uri为:

    
    content://com.jph.takephoto.fileprovider
    
            /camera_photos/temp/1474960080319.jpg`
    

    其中camera_photos就是file_paths文件中paths的name。

裁剪照片

在7.0之前裁剪照片,使用以下方法:


File file=new File(Environment.getExternalStorageDirectory(),

        "/temp/"+System.currentTimeMillis() + ".jpg"); 

if (!file.getParentFile().exists()) {

    file.getParentFile().mkdirs();

}

Uri outputUri = Uri.fromFile(file);

Uri imageUri=Uri.fromFile(newFile("/storage/emulated/0/temp/1474960080319.jpg"));

Intent intent = new Intent("com.android.camera.action.CROP");

intent.setDataAndType(imageUri, "image/*"); 

intent.putExtra("crop", "true"); 

intent.putExtra("aspectX", 1); 

intent.putExtra("aspectY", 1); 

intent.putExtra("scale", true); 

intent.putExtra(MediaStore.EXTRA_OUTPUT, outputUri);

intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString());

intent.putExtra("noFaceDetection", true); // no face detection

startActivityForResult(intent,1008);

和拍照一样,在7.0上同样会引起FileUrlExposedException,解决方法就是使用FileProvider。将上面的代码修改如下:


File file=new File(Environment.getExternalStorageDirectory(),

        "/temp/"+System.currentTimeMillis() + ".jpg");

if (!file.getParentFile().exists()) {

    file.getParentFile().mkdirs();

}

Uri outputUri = FileProvider.getUriForFile(context,

        "com.jph.takephoto.fileprovider",file); 

Uri imageUri=FileProvider.getUriForFile(context,"com.jph.takephoto.fileprovider",

        newFile("/storage/emulated/0/temp/1474960080319.jpg");//通过FileProvider创建一个content类型的Uri 

Intent intent = new Intent("com.android.camera.action.CROP");

intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);

intent.setDataAndType(imageUri, "image/*"); intent.putExtra("crop", "true");

intent.putExtra("aspectX", 1); intent.putExtra("aspectY", 1);

intent.putExtra("scale", true); intent.putExtra(MediaStore.EXTRA_OUTPUT,outputUri);

intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString());

intent.putExtra("noFaceDetection", true); // no face detection

startActivityForResult(intent,1008);

使用开源库来进行照片的获取和裁剪,比如TakePhoto

电池和内存

Android6.0引入了低电耗模式,7.0又在电池和内存上做了进一步的优化,来减少应用对电量的消耗和对内存的占用。这些优化带来的一些规则的变更可能会影响你的应用访问资源文件,以及你的应用通过特定的隐式Intent与其他应用互动的方式。

低电耗模式

在低电耗模式下,当用户设备未插接电源、处于静止状态且屏幕关闭时,该模式会推迟 CPU 和网络活动,从而延长电池寿命。 Android7.0通过在设备未插接电源且屏幕关闭状态下、但不一定要处于静止状态(例如用户外出时把手持式设备装在口袋里)时应用部分 CPU 和网络限制,进一步增强了低电耗模式。

具体规则:

  1. 当设备处于充电状态且屏幕已关闭一定时间后,设备会进入低电耗模式并应用第一部分限制: 关闭应用网络访问、推迟作业和同步。

  2. 如果进入低电耗模式后设备处于静止状态达到一定时间,系统则会对PowerManager.WakeLock、AlarmManager闹铃、GPS和Wi-Fi扫描应用余下的低电耗模式限制。 无论是应用部分还是全部低电耗模式限制,系统都会唤醒设备以提供简短的维护时间窗口,在此窗口期间,应用程序可以访问网络并执行任何被推迟的作业/同步。

后台优化

Android中有一些隐式广播,使用这些隐式广播可以做一些特定的功能,如,当手机网络变成WiFi时自动下载更新包等。 但,这些隐式广播会在后台频繁启动已注册侦听这些广播的应用,从而带来很大的电量消耗,为缓解这一问题来提升设备性能和用户体验,在Android 7.0中删除了三项隐式广播,以帮助优化内存使用和电量消耗。

  • 在Android 7.0上 应用不会收到CONNECTIVITY_ACTION广播,即使你在manifest清单文件中设置了请求接受这些事件的通知。但,在前台运行的应用如果使用BroadcastReceiver请求接收通知,则仍可以在主线程中侦听CONNECTIVITY_CHANGE

  • 在 Android 7.0上应用无法发送或接收ACTION_NEW_PICTUREACTION_NEW_VIDEO类型的广播。

应对策略:Android框架提供多个解决方案来缓解对这些隐式广播的需求。例如,JobScheduler API提供了一个稳健可靠的机制来安排满足指定条件(例如连入无限流量网络)时所执行的网络操作。甚至可以使用JobScheduler API来适应内容提供程序变化。

其他更新

性能改进,系统大提速

Android N操作系统代码量减少50%、全新JIT编译器安装速度提升75%,并减少50%应用程序代码,且应用运行速度提升幅度高达600%。

为开发者提供了Android Studio 2.2开发工具,其拥有更强大的UI设计工具,增强了对Java8和C++语言的支持,且得益于新JIT编译器,模块化应用的构建更加快捷。

支持Vulkan API

Vulkan API可以说是Open GL的继任者,据了解,它可以为Android带来更好的图像效果,并且大幅度减少对CPU的占用,从而实现高效率低功耗。

后台机制改进

在Android N最新版中,“最近使用的程序”界面完全重做。

为了实现提升效率的目标,Android N中多任务界面最多显示7个应用程序,也就是说,同时最多只能有7个应用程序在前台执行

同时,Android N中“任务”按键也被赋予一个全新功能–Quick Switch(快速切换),当用户双击“任务”按钮的时候,可以快速在当前和最后使用的应用中快速切换。

此外,Android N中还添加了原生的一键清理按钮,用户可以快速关闭所有已启动的程序。对于较长时间不活跃的应用,系统会自动清除。

原生分屏多任务

除了后台机制改进,Android N还提供了原生的分屏多任务功能。该功能无需多言,各大OEM厂商早已稔熟于心。

原生虚拟现实VR

ndroid N中添加了原生的VR功能,但是用户必须要搭配谷歌自家的VR平台“Daydream”使用。并且从今年秋季开始,各大Android厂商都将开始适配这项功能。

通知栏功能强化

通知栏是每一代Android系统必备的更新。在Android N中下拉通知栏将加入多个快捷开关,并且新的通知栏将支持显示更多信息,并且用户可以在通知栏中快速回复。虽然这些功能在第三方ROM中早已支持,但是谷歌为了更好的推行,直接将其提供了Android N原生支持。

全新的Emoji表情

谷歌称,Android N将成为首个支持Unicode 9编码Emoji表情的移动平台,所有的表情都将更人性化、个性化。并且谷歌还表示,他们目前正在积极开发下一代Emoji表情符号,并且将会加入更多的女性Emoji表情。

无缝OTA更新

出于提升效率的考虑,Android N将会自动下载OTA更新,而且无需用户同意。当更新下载完毕后,在用户重启设备时才会弹出准备好的Android新版本,以获得用户的同意来安装更新,并且安装完成后用户无需重启即可使用

谷歌还为Android N提供了基于文件的数据加密技术。

作者:yuelinghui2010 发表于2016/11/25 18:18:54 原文链接
阅读:33 评论:0 查看评论

Android启动性能(Launch-Time Performance)

$
0
0

用户希望Android App可以很快地响应和快速被加载。一个启动很慢的App并不满足用户的期望并且会让使用者感到失望。这种比较差的体验会导致用户在应用市场给你的App比较低的评分,甚至完全弃用你的应用。

这篇文章将会帮助你去优化你应用的启动时间。一开始,它将讲解启动过程的内部构建;接下来,它将讨论如何描述启动流程;最后,它描述了一些常见的启动问题,并且提供了一些如何解决它们的提示。

1. 启动的内部构建

App启动发生在如下三个状态之一,每个状态都会影响你的应用对用户可见所经历的时间长短:冷启动,热启动和微温启动。在冷启动中,你的应用从头开始启动。在其他状态里,系统需要把应用从后台带到前台。我们建议你在冷启动的设想的基础上进行优化。如此做可以同样提升热启动和温启动的性能。

对于为了快速启动优化你的应用,理解在启动的过程中在各个状态系统和应用层级发生了什么,它们之间是如何交互的是非常重要的。

冷启动

冷启动指的是一个应用从头开始启动:系统进程直到冷启动才开始创建应用进程。冷启动在如下场景发生:你的应用在设备启动后第一次被启动,或者在应用被系统杀死后再启动应用。这种启动对减少启动时间而言,提出了最大的挑战,因为系统和应用在冷启动状态要做的工作比其他启动状态多。

在冷启动的开始,系统有三个任务。这些任务是:

  • 加载和启动应用。
  • 在启动后立刻为应用展示 一个空白启动窗口。
  • 创建应用进程。

    一旦系统创建了应用进程,应用进程将负责下一个阶段。这些阶段是:

  • 创建应用对象。

  • 启动主线程。
  • 创建主activity。
  • 填充视图(Inflating views)。
  • 屏幕布局。
  • 执行初始绘画。

一旦应用完成了第一次绘制,系统进程将会替换当前显示的背景窗口,而用主Activity去替换它。这个时候,用户可以开始使用应用。

图1展示了系统进程和应用进程如何互相切换工作。
应用冷启动重要部分的一个可视的陈述

应用创建

当你的应用启动的时候,空白的启动窗口将会停留在屏幕上直到系统在第一次结束绘制应用。这个时候,系统进程将会为你的应用替换启动窗口,允许用户去与应用进行交互。

如果你重载了Application.oncreate(),应用将通过调用这个方法去启动。在这之后,应用将会产生主线程,也就是UI线程,并且主线程将会创建你的主Activity。

从这个时候开始,系统层进程和应用层进程按照应用生命周期阶段执行。

Activity创建

在应用进程创建了你的Activity之后,应用将会执行如下操作:

  • 初始化变量。
  • 调用构造函数。
  • 调用回调函数,例如Activity.onCreate(),对应Activity的当前生命周期状态。

代表地,onCreate()方法对加载时间有最大的影响,因为它执行了最高开销的工作:加载和填充view,并且初始化了供Activity运行的对象。

热启动

热启动与冷启动相比更加简单和低开销。在热启动里,系统所做的就是把你的应用带到前台。如果你的应用的所有Activity在内存里都是存活的,应用可以避免重复的对象初始化,布局加载和填充。

但是,如果一些内存由于像 onTrimMemory()这样的内存回收事件而被回收,这些对象将由于热启动事件而被重建。

热启动展示了与冷启动相同的屏幕显示:系统进程展示了一个空白的屏幕,直到应用已经结束渲染当前Activity。

微温启动

微温启动包含了一些发生在冷启动的操作;同时,它比热启动的开销要小。有许多可能的状态可以被称为温启动状态,例如:

  • 用户退出你的应用,但是随后重新启动它。进程有可能会继续运行,但是应用将会通过onCreate方法重头重新创建Activity。
  • 系统把你的应用从内存中清除,然后用户重新启动它。进程和Activity将会被重新启动,但是启动速度将会从传递进onCreate方法的已保存实例的状态集中得到加快。

2.描述启动性能

为了准确地诊断启动时间性能,你可以以启动应用所需的时间长短为标准。

初始绘制所需的时间(Time to initial display)

从Android 4.4版本开始, logcat包括了一个输出,包含一个值为Displayed。这个值表示了启动进程和结束绘制相关Activity中间所需的时间。花费的时间包含如下部分:

  • 启动进程。
  • 初始化对象。
  • 创建和初始化Activity。
  • 填充布局。
  • 第一次绘制你的应用。

相关的log与下面的例子类似:

ActivityManager: Displayed com.android.myexample/.StartupTiming: +3s534ms

如果你在命令行或者在一个终端追踪logcat,找到上面运行的时间是直截了当的。为了在Android Studio中找到运行时间,你必须关掉你logcat 视图里的过滤条件。关掉过滤条件是必要的,因为是系统服务器而不是应用本身提供了这个log。

一旦你正确设置了log,你可以轻易地找到运行时间。

图2展示了如何关掉过滤功能,并且在logcat的倒数第二行,展示了我们所需的Displayed值也就是运行时间。

关掉过滤功能,并且在logcat中找到Displayed值

在logcat输出的Displayed值不一定捕获所有资源被加载和展示所需的启动时间:它没有包含未在布局中引用的资源和作为对象初始化被应用创建的资源。它之所以排除这些资源是因为加载它们的是内联进程,这样做不会阻塞应用的初始绘制。

全部展示所需的时间(Time to full display)

你可以使用reportFullyDrawn() 方法去测量在应用启动和完全展示所有的资源和布局层级之间所需的运行时间。当应用在执行懒加载的时候这个时间值将会很有价值。在懒加载中,应用不阻塞窗口的初次绘制,而是去异步加载资源和视图层级。

如果,由于懒加载,一个应用的初始绘制并不包括所有的资源,你有可能把完全加载所有的资源和视图看作一种不同的衡量标准:例如,你的UI有可能完全加载了一些文本,但是还没有展示从网络上抓取的图片。

为了解决如上的问题,你可以手动调用reportFullyDrawn() 方法去让系统知道你的Activity已经结束了懒加载。当你使用了这个方法,这个值指的是应用程序对象的创建和你调用这个方法的地方之间所用的运行时间。

如果你发现你的展示所用的时间比你想的要慢,你可以继续尝试去找到启动过程中导致如此的瓶颈。

找出瓶颈

两种寻找瓶颈的好的方法是Android Studio的方法追踪工具和内联追踪。你可以在这里学习方法追踪器。

如果你不能使用方法追踪工具,或者不能通过log去抓取相应的信息,你可以通过你的应用和ActivityonCreate() 方法内部的内联追踪去获取相应的信息。点击如下链接去学习内联追踪:Trace方法和系统调用跟踪提供器工具。

3.常见的问题

这一部分讨论了几个常见的会影响启动性能的问题。这些问题主要涉及初始化应用和Activity对象,
已经屏幕内容的加载。

繁重的应用初始化

当你的代码重载了Application对象,并且在初始化对象时执行了繁重的工作和复杂的逻辑,启动性能会受到影响。如果你的应用子类执行了还不需要立刻做的初始化,你的应用可能会在启动的过程中浪费时间。有些初始化可能时完全不必要的:例如,当应用实际上已经开始在回应一个intent的时候,为主Activity进行状态信息的初始化就是不必要的。根据这个intent,应用可能只使用前一个初始化状态数据的子集。

其他在应用初始化过程中的问题包括大量的垃圾回收活动,或者与初始化操作同时发生的磁盘 I/O 操作,都进一步阻碍了初始化进程。 Dalvik 运行时对于垃圾回收是有特殊优化的;而 Art 运行时则同时执行垃圾回收,使这个操作的影响降到最低。

诊断问题

你可以使用 或者内联追踪去尝试诊断问题。

方法追踪

运行方法追踪工具揭示了callApplicationOnCreate() 方法最终调用了你的 com.example.customApplication.onCreate方法。如果工具展示了这些方法花费了很多时间去执行,你应该去研究在这个方法里做了哪些工作。

内联追踪

使用内联追踪去调查可能导致启动慢的原因:

  • 你的应用的初始onCreate()方法。
  • 任何全局单例对象。
  • 任何I/O操作,还原序列化或者在瓶颈期间密集的循环。

问题的解决方式

有许多潜在的瓶颈影响启动速度,但是两个常见的问题和相应的解决方式如下:

  • 你的视图层级越大,你的应用需要花费更多的时间去填充它。两个步骤可以解决这个问题:

    • 通过减少多余的和内嵌的布局,简化你的视图层级。
    • 在启动期间不需要展示给用户的布局暂时不要进行绘制填充。用户使用ViewStub对象替代父层级,这样可以在合适的时候再对这样的布局进行绘制。
  • 在主线程里初始化你所有的资源也会减慢启动的速度。你可以如下处理这种问题:

    • 把资源初始化放在非主线程上执行,以便进行懒加载。
    • 允许应用去加载和展示你的视图,随后更新依赖于 bitmap和其他资源的可视部分。

给启动屏幕添加主题(Themed launch screens)

你有可能希望给你的应用加载过程添加主题,以便应用的启动屏的主题风格与应用的其它部分相符,而不是简单的使用系统主题。如此做可以隐藏很慢的Activity启动现象。

一个常见的方式去实现自定义启动屏幕主题是:使用windowDisablePreview主题属性去关掉一开始的白屏,这个白屏就是系统线程在初始化App的时候绘制的。但是,但是这种方法会导致更长的启动时间,相较没有关掉预加载白屏的应用。同样地,当应用启动的时候,它强迫用户在没有任何反馈的情况下等待,这会让用户对应用是否正常运行干到疑惑。

诊断问题

你可以通过当用户启动你的应用时是否有很长的应答时间来诊断这个问题。在这种情况下,屏幕会看上去像是冻住了,或者停止了对输入的响应。

问题的解决方法

我们的建议是:你可以遵从常见的Material Design 样式,而不是去禁用预加载屏幕展示。你可以使用Activity的windowBackground属性去为启动Activity提供一个简单的自定义图片。

例如:你可以创建一个新的图片文件,并且用layout文件和manifest文件引用它,如下:

Layout XML 文件:

<layer-list xmlns:android="http://schemas.android.com/apk/res/android" android:opacity="opaque">
  <!-- The background color, preferably the same as your normal theme -->
  <item android:drawable="@android:color/white"/>
  <!-- Your product logo - 144dp color version of your app icon -->
  <item>
    <bitmap
      android:src="@drawable/product_logo_144dp"
      android:gravity="center"/>
  </item>
</layer-list>

Manifest 文件:

<activity ...
android:theme="@style/AppTheme.Launcher" />

过渡回到正常的主题的最简单的方法是在super.onCreate() 和 setContentView()之前调用setTheme(R.style.AppTheme) 方法。

public class MyMainActivity extends AppCompatActivity {
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    // Make sure this is before calling super.onCreate
    setTheme(R.style.Theme_MyApp);
    super.onCreate(savedInstanceState);
    // ...
  }
}

英文原文:Launch-Time Performance

最近正好自己在研究如何优化App启动的性能,正好看到Android官方有这样一篇文章,出于兴趣故个人翻译了一下。因为水平可能有限,有些地方可能翻译的比较拗口,如果大家看到了请见谅,有什么问题请告知我,我这边会做相应的修改~

作者:y505772146 发表于2016/11/25 18:19:30 原文链接
阅读:30 评论:0 查看评论

Linux音频驱动-PCM设备

$
0
0

概述

1.  什么是pcm?
pcm(Pulse-code modulation)脉冲编码调制,是将模拟信号转化为数字信号的一种方法。声音的转化的过程为,先对连续的模拟信号按照固定频率周期性采样,将采样到的数据按照一定的精度进行量化,量化后的信号和采样后的信号差值叫做量化误差,将量化后的数据进行最后的编码存储,最终模拟信号变化为数字信号。

2. pcm的两个重要属性
    a.  采样率:        单位时间内采样的次数,采样频率越高越高,
    b.  采样位数:    一个采样信号的位数,也是对采样精度的变现。

对于人类而言,能接受声音的频率范围是20Hz-20KHz, 所以采样的频率44.1KHz 以及16bit的采样位数就可以有很好的保真能力(CD格式的采样率和采样位数)。


                                                              图1-1  声音的录音和播放过程

数据结构

在ALSA架构下,pcm也被称为设备,所谓的逻辑设备。在linux系统中使用snd_pcm结构表示一个pcm设备。
struct snd_pcm {
	struct snd_card *card;
	struct list_head list;
	int device; /* device number */
	unsigned int info_flags;
	unsigned short dev_class;
	unsigned short dev_subclass;
	char id[64];
	char name[80];
	struct snd_pcm_str streams[2];
	struct mutex open_mutex;
	wait_queue_head_t open_wait;
	void *private_data;
	void (*private_free) (struct snd_pcm *pcm);
	struct device *dev; /* actual hw device this belongs to */
	bool internal; /* pcm is for internal use only */
	bool nonatomic; /* whole PCM operations are in non-atomic context */
#if defined(CONFIG_SND_PCM_OSS) || defined(CONFIG_SND_PCM_OSS_MODULE)
	struct snd_pcm_oss oss;
#endif
};
.card:         此pcm设备所属的card。
.list:           用于将pcm设备链接起来,最终所有的pcm设备会放入snd_pcm_devices链表中。
.device:      该pcm的索引号。
.id:             该pcm的标识。
.streams:   指向pcm的capture和playback stream,通常0代表playback,1代表capture。

通常一个pcm下会有两个stream, 分别为capture stream和playback stream,在每个stream下又会存在多个substream。
linux系统中使用snd_pcm_str定义stream, 使用snd_pcm_substream定义substream。
struct snd_pcm_str {
	int stream;				/* stream (direction) */
	struct snd_pcm *pcm;
	/* -- substreams -- */
	unsigned int substream_count;
	unsigned int substream_opened;
	struct snd_pcm_substream *substream;
};
.stream:  当前stream的方向,capture or playback。
.pcm:      所属的pcm。
.substream_count:  该stream下substream的个数。
.substream_opened:  该stream下open的substream个数。
.substream:  该stream下的substream.

struct snd_pcm_substream {
	struct snd_pcm *pcm;
	struct snd_pcm_str *pstr;
	void *private_data;		/* copied from pcm->private_data */
	int number;
	char name[32];			/* substream name */
	int stream;			/* stream (direction) */
	struct pm_qos_request latency_pm_qos_req; /* pm_qos request */
	size_t buffer_bytes_max;	/* limit ring buffer size */
	struct snd_dma_buffer dma_buffer;
	size_t dma_max;
	/* -- hardware operations -- */
	const struct snd_pcm_ops *ops;
	/* -- runtime information -- */
	struct snd_pcm_runtime *runtime;
        /* -- timer section -- */
	struct snd_timer *timer;		/* timer */
	unsigned timer_running: 1;	/* time is running */
	/* -- next substream -- */
	struct snd_pcm_substream *next;
	/* -- linked substreams -- */
	struct list_head link_list;	/* linked list member */
	struct snd_pcm_group self_group;	/* fake group for non linked substream (with substream lock inside) */
	struct snd_pcm_group *group;		/* pointer to current group */
	/* -- assigned files -- */
	void *file;
	int ref_count;
	atomic_t mmap_count;
	unsigned int f_flags;
	void (*pcm_release)(struct snd_pcm_substream *);
	struct pid *pid;
	/* misc flags */
	unsigned int hw_opened: 1;
};
.pcm:       所属的pcm。
.pstr:       所属的stream。
.id:           代表的该stream下第几个substream,也就是序号。
.stream:  该substream的方向流,是palyback or capture。
.name:     该substrem的名字。
.ops:        硬件操作函数集合。
.runtime:   运行时的pcm的一些信息。
.next:        用于链接下一个sub stream。

下图是对这几个结构体之间的简单表述。



pcm设备的创建

创建一个pcm设备的实例,使用snd_pcm_new函数。
/**
 * snd_pcm_new - create a new PCM instance
 * @card: the card instance
 * @id: the id string
 * @device: the device index (zero based)
 * @playback_count: the number of substreams for playback
 * @capture_count: the number of substreams for capture
 * @rpcm: the pointer to store the new pcm instance
 *
 * Creates a new PCM instance.
 *
 * The pcm operators have to be set afterwards to the new instance
 * via snd_pcm_set_ops().
 *
 * Return: Zero if successful, or a negative error code on failure.
 */
int snd_pcm_new(struct snd_card *card, const char *id, int device,
		int playback_count, int capture_count, struct snd_pcm **rpcm)
{
	return _snd_pcm_new(card, id, device, playback_count, capture_count,
			false, rpcm);
}
此函数会传入六个参数,其中该函数的注释写的很清楚,不做过多解释。函数最终会返回rpcm参数。
static int _snd_pcm_new(struct snd_card *card, const char *id, int device,
		int playback_count, int capture_count, bool internal,
		struct snd_pcm **rpcm)
{
	struct snd_pcm *pcm;
	int err;
	static struct snd_device_ops ops = {
		.dev_free = snd_pcm_dev_free,
		.dev_register =	snd_pcm_dev_register,
		.dev_disconnect = snd_pcm_dev_disconnect,
	};

	if (snd_BUG_ON(!card))
		return -ENXIO;
	if (rpcm)
		*rpcm = NULL;
	pcm = kzalloc(sizeof(*pcm), GFP_KERNEL);
	if (pcm == NULL) {
		dev_err(card->dev, "Cannot allocate PCM\n");
		return -ENOMEM;
	}
	pcm->card = card;
	pcm->device = device;
	pcm->internal = internal;
	if (id)
		strlcpy(pcm->id, id, sizeof(pcm->id));
	if ((err = snd_pcm_new_stream(pcm, SNDRV_PCM_STREAM_PLAYBACK, playback_count)) < 0) {
		snd_pcm_free(pcm);
		return err;
	}
	if ((err = snd_pcm_new_stream(pcm, SNDRV_PCM_STREAM_CAPTURE, capture_count)) < 0) {
		snd_pcm_free(pcm);
		return err;
	}
	mutex_init(&pcm->open_mutex);
	init_waitqueue_head(&pcm->open_wait);
	if ((err = snd_device_new(card, SNDRV_DEV_PCM, pcm, &ops)) < 0) {
		snd_pcm_free(pcm);
		return err;
	}
	if (rpcm)
		*rpcm = pcm;
	return 0;
}
1.  分配一个snd_pcm结构体。
2.  根据传递进来的参数设置card, device, internal, id。
3.  分别创建palyback & capture stream。
4.  调用snd_device_new接口创建pcm设备。

调用snd_pcm_new_stream创建一个stream
int snd_pcm_new_stream(struct snd_pcm *pcm, int stream, int substream_count)
{
	int idx, err;
	struct snd_pcm_str *pstr = &pcm->streams[stream];
	struct snd_pcm_substream *substream, *prev;

#if IS_ENABLED(CONFIG_SND_PCM_OSS)
	mutex_init(&pstr->oss.setup_mutex);
#endif
	pstr->stream = stream;
	pstr->pcm = pcm;
	pstr->substream_count = substream_count;
	if (substream_count > 0 && !pcm->internal) {
		err = snd_pcm_stream_proc_init(pstr);
		if (err < 0) {
			pcm_err(pcm, "Error in snd_pcm_stream_proc_init\n");
			return err;
		}
	}
	prev = NULL;
	for (idx = 0, prev = NULL; idx < substream_count; idx++) {
		substream = kzalloc(sizeof(*substream), GFP_KERNEL);
		if (substream == NULL) {
			pcm_err(pcm, "Cannot allocate PCM substream\n");
			return -ENOMEM;
		}
		substream->pcm = pcm;
		substream->pstr = pstr;
		substream->number = idx;
		substream->stream = stream;
		sprintf(substream->name, "subdevice #%i", idx);
		substream->buffer_bytes_max = UINT_MAX;
		if (prev == NULL)
			pstr->substream = substream;
		else
			prev->next = substream;

		if (!pcm->internal) {
			err = snd_pcm_substream_proc_init(substream);
			if (err < 0) {
				pcm_err(pcm,
					"Error in snd_pcm_stream_proc_init\n");
				if (prev == NULL)
					pstr->substream = NULL;
				else
					prev->next = NULL;
				kfree(substream);
				return err;
			}
		}
		substream->group = &substream->self_group;
		spin_lock_init(&substream->self_group.lock);
		mutex_init(&substream->self_group.mutex);
		INIT_LIST_HEAD(&substream->self_group.substreams);
		list_add_tail(&substream->link_list, &substream->self_group.substreams);
		atomic_set(&substream->mmap_count, 0);
		prev = substream;
	}
	return 0;
}			
1.   根据传递进来的参数,设置pcm的stream, pcm, substream_count的值。
2.   在proc下创建pcm相关目录信息。会调用snd_pcm_stream_proc_init函数,根据stream的类型创建pcm0p/pcm0c文件夹,然后会在此文件夹下创建info文件。info文件的类型会通过snd_pcm_stream_proc_info_read函数获得。代表就不贴出来了。:(
root@test:/proc/asound/card0/pcm0c$ cat info 
card: 0
device: 0
subdevice: 0
stream: CAPTURE
id: ALC662 rev1 Analog
name: ALC662 rev1 Analog
subname: subdevice #0
class: 0
subclass: 0
subdevices_count: 1
subdevices_avail: 1
3.   会根据substrem_count的个数,进行for循环操作。
4.   分配一个substream结构,设置必要的参数,如:  pcm,  pstr,  number,  stream,  name等。
5.   调用snd_pcm_substream_proc_init函数,创建sub0目录,然后在此目录下创建info, hw_params, sw_params,status等文件。
6.   将所有的substream会通过linklist链表保存,同时如果有多个substream会通过next指针相连。

至此,pcm设备就全部创建完成,创建完成后会形成如下的逻辑试图。

大体上就是一棵树,根节点是card0, 然后子节点是pcm设备,pcm设备分为capture & playback stream, 然后在stream下又分为substrem。

PCM硬件操作函数集设置

实例化一个pcm设备之后,还需要通过snd_pcm_set_ops函数设置该硬件的操作集合。
void snd_pcm_set_ops(struct snd_pcm *pcm, int direction,
		     const struct snd_pcm_ops *ops)
{
	struct snd_pcm_str *stream = &pcm->streams[direction];
	struct snd_pcm_substream *substream;
	
	for (substream = stream->substream; substream != NULL; substream = substream->next)
		substream->ops = ops;
}
该函数会根据当前stream的方向/类型,设置该硬件对应的snd_pcm_ops操作集合。

整个流程梳理


PCM设备节点创建

当调用snd_card_register的时候,就会依次调用card列表下每个设备的dev_register回调函数,对pcm设备来说就是在_snd_pcm_new函数中的
	static struct snd_device_ops ops = {
		.dev_free = snd_pcm_dev_free,
		.dev_register =	snd_pcm_dev_register,
		.dev_disconnect = snd_pcm_dev_disconnect,
	};
此时会调用到snd_pcm_dev_register回调处理函数。
static int snd_pcm_dev_register(struct snd_device *device)
{
	int cidx, err;
	struct snd_pcm_substream *substream;
	struct snd_pcm_notify *notify;
	char str[16];
	struct snd_pcm *pcm;
	struct device *dev;

	if (snd_BUG_ON(!device || !device->device_data))
		return -ENXIO;
	pcm = device->device_data;
	mutex_lock(&register_mutex);
	err = snd_pcm_add(pcm);
	if (err) {
		mutex_unlock(&register_mutex);
		return err;
	}
	for (cidx = 0; cidx < 2; cidx++) {
		int devtype = -1;
		if (pcm->streams[cidx].substream == NULL || pcm->internal)
			continue;
		switch (cidx) {
		case SNDRV_PCM_STREAM_PLAYBACK:
			sprintf(str, "pcmC%iD%ip", pcm->card->number, pcm->device);
			devtype = SNDRV_DEVICE_TYPE_PCM_PLAYBACK;
			break;
		case SNDRV_PCM_STREAM_CAPTURE:
			sprintf(str, "pcmC%iD%ic", pcm->card->number, pcm->device);
			devtype = SNDRV_DEVICE_TYPE_PCM_CAPTURE;
			break;
		}
		/* device pointer to use, pcm->dev takes precedence if
		 * it is assigned, otherwise fall back to card's device
		 * if possible */
		dev = pcm->dev;
		if (!dev)
			dev = snd_card_get_device_link(pcm->card);
		/* register pcm */
		err = snd_register_device_for_dev(devtype, pcm->card,
						  pcm->device,
						  &snd_pcm_f_ops[cidx],
						  pcm, str, dev);
		if (err < 0) {
			list_del(&pcm->list);
			mutex_unlock(&register_mutex);
			return err;
		}

		dev = snd_get_device(devtype, pcm->card, pcm->device);
		if (dev) {
			err = sysfs_create_groups(&dev->kobj,
						  pcm_dev_attr_groups);
			if (err < 0)
				dev_warn(dev,
					 "pcm %d:%d: cannot create sysfs groups\n",
					 pcm->card->number, pcm->device);
			put_device(dev);
		}

		for (substream = pcm->streams[cidx].substream; substream; substream = substream->next)
			snd_pcm_timer_init(substream);
	}

	list_for_each_entry(notify, &snd_pcm_notify_list, list)
		notify->n_register(pcm);

	mutex_unlock(&register_mutex);
	return 0;
}
1.   合法性判断,对pcm设备来说,snd_device->device_data存放的是当前的pcm指针。
2.    会调用snd_pcm_add此函数,判断此pcm设备是存在snd_pcm_devices链表中存在,存在就返回错误,不存在就添加。
3.    设置当前pcm设备name, 以及具体的pcm设备类型,PCM_CAPTURE  or PCM_PLAYBACK。
4.    调用snd_register_device_for_dev添加pcm设备到系统中。
5.    调用snd_get_device此函数返回当前注册的pcm设备,然后设置该pcm的属性。
6.    调用snd_pcm_timer_init函数,进行pcm定时器的初始化。

在继续分析snd_register_device_for_dev函数之前需要先介绍一个结构体。struct snd_minor。
struct snd_minor {
	int type;			/* SNDRV_DEVICE_TYPE_XXX */
	int card;			/* card number */
	int device;			/* device number */
	const struct file_operations *f_ops;	/* file operations */
	void *private_data;		/* private data for f_ops->open */
	struct device *dev;		/* device for sysfs */
	struct snd_card *card_ptr;	/* assigned card instance */
};
.type:  设备类型,比如是pcm, control, timer等设备。
.card_number:  所属的card。
.device:  当前设备类型下的设备编号。
.f_ops:  具体设备的文件操作集合。
.private_data:  open函数的私有数据。
.card_ptr:  所属的card。

此结构体是用来保存当前设备的上下文信息,该card下所有逻辑设备都存在此结构。

int snd_register_device_for_dev(int type, struct snd_card *card, int dev,
				const struct file_operations *f_ops,
				void *private_data,
				const char *name, struct device *device)
{
	int minor;
	struct snd_minor *preg;

	if (snd_BUG_ON(!name))
		return -EINVAL;
	preg = kmalloc(sizeof *preg, GFP_KERNEL);
	if (preg == NULL)
		return -ENOMEM;
	preg->type = type;
	preg->card = card ? card->number : -1;
	preg->device = dev;
	preg->f_ops = f_ops;
	preg->private_data = private_data;
	preg->card_ptr = card;
	mutex_lock(&sound_mutex);
#ifdef CONFIG_SND_DYNAMIC_MINORS
	minor = snd_find_free_minor(type);
#else
	minor = snd_kernel_minor(type, card, dev);
	if (minor >= 0 && snd_minors[minor])
		minor = -EBUSY;
#endif
	if (minor < 0) {
		mutex_unlock(&sound_mutex);
		kfree(preg);
		return minor;
	}
	snd_minors[minor] = preg;
	preg->dev = device_create(sound_class, device, MKDEV(major, minor),
				  private_data, "%s", name);
	if (IS_ERR(preg->dev)) {
		snd_minors[minor] = NULL;
		mutex_unlock(&sound_mutex);
		minor = PTR_ERR(preg->dev);
		kfree(preg);
		return minor;
	}

	mutex_unlock(&sound_mutex);
	return 0;
}
1.   首先上来就分配一个snd_minor结构体。
2.   根据传递进来的参数,各种参数。对于pcm设备来说,当前的private_data就是pcm。此处需要重点介绍file_operations结构。此函数最终会在应用程序调用open的时候走到此处
const struct file_operations snd_pcm_f_ops[2] = {
	{
		.owner =		THIS_MODULE,
		.write =		snd_pcm_write,
		.aio_write =		snd_pcm_aio_write,
		.open =			snd_pcm_playback_open,
		.release =		snd_pcm_release,
		.llseek =		no_llseek,
		.poll =			snd_pcm_playback_poll,
		.unlocked_ioctl =	snd_pcm_playback_ioctl,
		.compat_ioctl = 	snd_pcm_ioctl_compat,
		.mmap =			snd_pcm_mmap,
		.fasync =		snd_pcm_fasync,
		.get_unmapped_area =	snd_pcm_get_unmapped_area,
	},
	{
		.owner =		THIS_MODULE,
		.read =			snd_pcm_read,
		.aio_read =		snd_pcm_aio_read,
		.open =			snd_pcm_capture_open,
		.release =		snd_pcm_release,
		.llseek =		no_llseek,
		.poll =			snd_pcm_capture_poll,
		.unlocked_ioctl =	snd_pcm_capture_ioctl,
		.compat_ioctl = 	snd_pcm_ioctl_compat,
		.mmap =			snd_pcm_mmap,
		.fasync =		snd_pcm_fasync,
		.get_unmapped_area =	snd_pcm_get_unmapped_area,
	}
};
3.   调用snd_kernel_minor函数获得设备的此设备号。该此设备号已经存在则返回BUSY,小于返回错误。
4.   用次设备号为下标,将当前申请的snd_minor放入到全局的snd_minors结构体数组中。
static struct snd_minor *snd_minors[SNDRV_OS_MINORS];
5.   调用device_create函数创建该pcm的设备节点。
6.   为什么创建出的设备节点全在/dev/snd下呢?  此问题源自sound_class创建的时候,设置的devnode参数。
static char *sound_devnode(struct device *dev, umode_t *mode)
{
	if (MAJOR(dev->devt) == SOUND_MAJOR)
		return NULL;
	return kasprintf(GFP_KERNEL, "snd/%s", dev_name(dev));
}

static int __init init_soundcore(void)
{
	int rc;

	rc = init_oss_soundcore();
	if (rc)
		return rc;

	sound_class = class_create(THIS_MODULE, "sound");
	if (IS_ERR(sound_class)) {
		cleanup_oss_soundcore();
		return PTR_ERR(sound_class);
	}

	sound_class->devnode = sound_devnode;

	return 0;
}
当调用device_create的时候,最终会调用到device_add->devtmpfs_create_node->device_get_devnode中
	/* the class may provide a specific name */
	if (dev->class && dev->class->devnode)
		*tmp = dev->class->devnode(dev, mode);
最终出现的设备节点会出现在/dev/snd下。

应用到驱动的过程

当应用程序在通过open系统调用打开/dev/pcmC0D0c的过程
1.  先会调用到在alsa_sound_init中注册的字符设备"alsa"的file_operations中的open函数中。
static const struct file_operations snd_fops =
{
	.owner =	THIS_MODULE,
	.open =		snd_open,
	.llseek =	noop_llseek,
};
2.  此处会根据次设备号在snd_minors中获得注册的pcm的snd_minor结构,然后调用open回调
	if (file->f_op->open)
		err = file->f_op->open(inode, file);
3.   此处的open回调就是snd_pcm_f_ops中的open。
4.   当应用程序执行ioctl的时候,就直接调用file文件中的file_operaions中的ioctl即可,因为在此处已经将snd_minor中的file_operation替换到file中。
#define replace_fops(f, fops) \
	do {	\
		struct file *__file = (f); \
		fops_put(__file->f_op); \
		BUG_ON(!(__file->f_op = (fops))); \
	} while(0)
5.  比如当前调用的是playback中的open,会调用snd_pcm_playback_open函数,此函数会设置pcm的runtime信息,最终会调用硬件相关的open函数中。
	if ((err = substream->ops->open(substream)) < 0)

至此,整个pcm设备创建,调用,以及应用到驱动整个流程分析完毕。:)


  

作者:longwang155069 发表于2016/11/25 20:11:16 原文链接
阅读:38 评论:0 查看评论

iOS常用第三方框架的工作原理

$
0
0

在写iOS项目的过程中,我们经常会用到AFNetworking和SDWebImage这种第三方开源框架,但其工作原理我们大部分人都不怎么清楚,在这里整理一下这两个第三方开源框架的工作原理。

1.介绍

1.AFNetworking
AFNetworking库,我认为是目前最优秀的开源网络库,基于NSURLConnection,目前我们最可能用到的地方是JSON请求或者XML请求;可以设置GET或者POST方式提交数据,而GET或者POST的NSURLRequest,和NSURLConnection使用GET和POST方式完全一样
2.SDWebImage
SDWebImage库提供一个UIImageView类别以支持加载来自网络的远程图片。具有缓存管理、异步下载、同一个URL下载次数控制和优化等特征。
另外,这两个库都是基于GCD的.

2.综述

一.AFNetworking

AFNetworing除了几个分类外的所有类。类库的头文件AFNetworking.h引入了下面的所有类库,并可以根据不同的系统使用不同的实现方式。下面大体介绍下每个类的大致作用,主要以翻译API的注释文档为主

1:AFURLConnectionOperation可以说是AFN最基础的类。继承自NSOperation类,将网络请求依附到一个operation上。从而让我们能够有效的控制并观察一个网络请求的创建、进行、取消、完成、暂停恢复、异常等问题及状态。【重点类的实现分析】

2:AFHTTPRequestOperationHTTP或HTTPS协议请求的AFURLConnectionOperation的子类。它封装的可接受状态码和内容的类型,判定一个请求结果是成功或失败。实际上对系统的HTTP网络请求增加了几个HTTP需要用到的参数。

3:AFHTTPRequestOperationManager这个类是AFN类库的核心类。它封装完成了一种通用的模式,可以帮助我们轻松友好的完成请求的创建、响应的系列化,网络状态的监控以及安全策略以及每一个请求operation的管理(operation的相互依赖或状态改变)。【重点类的实现分析】
4:AFURLSessionManageriOS7 之后,苹果增加了新的网络请求类–NSURLSession。AFN官方推荐iOS 7 或者 Mac OS X 10.9以上的,最好使用该类发起网络请求,取代AFHTTPRequestOperationManager。不过基于目前国内app大都最低适配的 iOS6,该类的用途还不是太广泛。NSURLSession的说明或者使用不再赘述。自行查看API文档。以后有时间再加上该类的使用。

5: AFURLSessionManager继承自AFURLSessionManager。类似于1和2的关系。也是方便HTTP以及https请求的使用,增加了一些接口,方便调用。

6:AFNetworkReachabilityManager网络的连通状态监控以及网络的类型。实际是将苹果官方提供的Reachability的类名和通知名更换了一下,防止和系统提供的类的通知名以及类名的冲突。

7:AFSecurityPolicy这个我不太懂,安全策略的类。一般貌似用不到,有需要自行google。

8:AFURLRequestSerialization①:符合这个协议的对象用于处理请求,它将请求参数转换为 query string 或是 entity body 的形式,并设置必要的 header。②:构建multipart请求。

9:AFURLResponseSerialization遵循AFURLResponseSerialization协议的对象,用于验证、序列化响应及相关数据,转换为有用的形式,比如 JSON 对象、图像、甚至基于mantle的模型对象。

2.重点类的实现分析

1:AFURLConnectionOperation的实现

①:综述
AFURLConnectionOperation 将Operation和URLConnection结合到一起,利用operation可以监听到状态以及可以建立相互之间的依赖关系的特性,实现了对于 一个NSURLConnection对象的完美控制,并将请求的结果通过block友好的返回。

②:实现文件.m
我们总结下.m中这个类主要有哪些方法。
1: 首先我们可以看到它创建了一个单例线程。这个线程将会常驻内存,用来处理AFN发起的所有请求任务。当然,线程也跟随着一个runloop,AFN将这个 runloop的模式设置为NSDefaultRunLoopMode。NSDefaultRunLoopMode是无法检测到connection的状 态的。这说明了,AFN将不会在这该线程处理connection完成后的UI刷新等工作,而是会将数据抛给主线程,让主线程去完成UI的刷新。

2:我们可以看到该类通过接受请求的字符串,创建了URLRequest以及NSURLConnection对象。从而去进行请求。

3:实现文件多次使用到了锁,可以保证数据的安全。当然他也实现了几个数据的NSCoping协议。

4:请求的创建、进行、取消、完成、暂停恢复、异常等问题及状态的控制。这里讲一下暂停和恢复。暂 停实际上将网络请求取消掉了。但是由于实现了nscoping协议,已经下载到数据得以保存下来。下次进行相同请求的时候,我们会将已经下载到的数据的节 点一起发送给服务器,告诉服务器这些部门的数据我们不需要了,服务器根据我发送的返回节点给我返回相应的数据即可。从而实现了暂停和恢复功能,也就是断点 续传。

5:operation方法的重写。自行google,这里不赘述。

6:状态的各种控制方法的实现以及发送状态改变的通知

③:接口文件.h
接口文档中的属性方法,基本可以概括为以下几个方法
1:只读的数据,让管理者可以接收到。
2:设置runloop的modes。不再使用类库默认设置的defaultmodes。
3:状态的控制方法
4:安全策略的设置
总而言之,接口文件.h暴露的接口都是为了让manager可以去完全控制这个operation以及其中的网络请求。

2:AFHTTPRequestOperationManger
①:综述
这个类可以说是整个类库的核心类了。据说AFN2.0之前的时候,所有的网络请求相关的设置都杂糅到一个client中,导致client特别的臃肿。2.0后,AFN将一些设置提取出来,线程了专门的类【AFSecurityPolicy、AFURLRequestSerialization、AFURLResponseSerialization】。现在看来,AFN整体的设计是非常完美的。耦合性变得非常低,一些1.0版本中存在的问题也得到了改善。
②:实现文件.m
实现文件较为简单,可以看到他创建了一个队列。并将各个operation加入到队列中。在队列中,各个请求就可以设置依赖关系,并发的数量等等。
③:接口文件.h
接口文件中,我们可以看到。这个类可以设置AFSecurityPolicy、AFURLRequestSerialization、AFURLResponseSerialization 等参数了。这就是综述所说的降低耦合性的方式。基本使用很简单,这里就不再赘述了。

二.SDWebImage

SDWebImage库的作用

通过对UIImageView的类别扩展来实现异步加载替换图片的工作。
主要用到的对象:
1、UIImageView (WebCache)类别,入口封装,实现读取图片完成后的回调

2、SDWebImageManager,对图片进行管理的中转站,记录那些图片正在读取。
向下层读取Cache(调用SDImageCache),或者向网络读取对象(调用SDWebImageDownloader) 。

3、实现SDImageCache和SDWebImageDownloader的回调。

4、SDImageCache,根据URL的MD5摘要对图片进行存储和读取(实现存在内存中或者存在硬盘上两种实现)实现图片和内存清理工作。

5、SDWebImageDownloader,根据URL向网络读取数据(实现部分读取和全部读取后再通知回调两种方式)
其他类:
SDWebImageDecoder,异步对图像进行了一次解压⋯⋯

SDWebImage 加载图片的流程

1、入口 setImageWithURL:placeholderImage:options: 会先把 placeholderImage 显示,然后 SDWebImageManager 根据 URL 开始处理图片。

2、进入 SDWebImageManager-downloadWithURL:delegate:options:userInfo:,交给 SDImageCache 从缓存查找图片是否已经下载queryDiskCacheForKey:delegate:userInfo:. 
 
3、先从内存图片缓存查找是否有图片,如果内存中已经有图片缓存,SDImageCacheDelegate 回调 imageCache:didFindImage:forKey:userInfo: 到 SDWebImageManager。
  
4、SDWebImageManagerDelegate 回调 webImageManager:didFinishWithImage: 到 UIImageView+WebCache 等前端展示图片。如果内存缓存中没有,生成 NSInvocationOperation 添加到队列开始从硬盘查找图片是否已经缓存。
 
5、根据 URLKey 在硬盘缓存目录下尝试读取图片文件。这一步是在 NSOperation 进行的操作,所以回主线程进行结果回调 notifyDelegate:。
  
6、如果上一操作从硬盘读取到了图片,将图片添加到内存缓存中(如果空闲内存过小,会先清空内存缓存)。SDImageCacheDelegate 回调 imageCache:didFindImage:forKey:userInfo:。进而回调展示图片。
  
7、如果从硬盘缓存目录读取不到图片,说明所有缓存都不存在该图片,需要下载图片,回调 imageCache:didNotFindImageForKey:userInfo:。 
 
8、共享或重新生成一个下载器 SDWebImageDownloader 开始下载图片。
  
9、图片下载由 NSURLConnection 来做,实现相关 delegate 来判断图片下载中、下载完成和下载失败。 
 
10、connection:didReceiveData: 中利用 ImageIO 做了按图片下载进度加载效果。 
 
11、connectionDidFinishLoading: 数据下载完成后交给 SDWebImageDecoder 做图片解码处理。
  
12、图片解码处理在一个 NSOperationQueue 完成,不会拖慢主线程 UI。如果有需要对下载的图片进行二次处理,最好也在这里完成,效率会好很多。
  
13、在主线程 notifyDelegateOnMainThreadWithInfo: 宣告解码完成,  imageDecoder:didFinishDecodingImage:userInfo: 回调给 SDWebImageDownloader。
  
14、imageDownloader:didFinishWithImage: 回调给 SDWebImageManager 告知图片下载完成。
  
15、通知所有的 downloadDelegates 下载完成,回调给需要的地方展示图片。
  
16、将图片保存到 SDImageCache 中,内存缓存和硬盘缓存同时保存。写文件到硬盘也在以单独 NSInvocationOperation 完成,避免拖慢主线程。
  
17、SDImageCache 在初始化的时候会注册一些消息通知,在内存警告或退到后台的时候清理内存图片缓存,应用结束的时候清理过期图片。
  
18、SDWI 也提供了 UIButton+WebCache 和 MKAnnotationView+WebCache,方便使用。 
 
19、SDWebImagePrefetcher 可以预先下载图片,方便后续使用。

写得不完整的地方,欢迎补充 谢谢~~

作者:QiLong_Shi 发表于2016/11/25 20:44:49 原文链接
阅读:32 评论:0 查看评论

libgdx之瓦片地图(TiledMap)

$
0
0

把草稿删了,没想到也把发表的文章给删了,只好重新写了。不得不吐槽一下CSDN的博客系统。

简介

在我们开发游戏过程中,我们需要设置不同的关卡,如果我们直接使用使用图片来加载游戏,这将会使我们的游戏安装包本身变得非常臃肿。不过好在Libgdx给我们提供了瓦片地图,我们可以直接使用编辑器来编辑地图,然后使用Libgdx提供的API解析加载,而且地图图片还可以在不同地图上重复使用,节约了游戏安装包的空间。

瓦片地图介绍

地图编辑器 Tiled: http://www.mapeditor.org/download.html
这里写图片描述

TMX文件介绍:

注意:在这个文件中( image source=”tileset.png” trans=”5e81a2” width=”692” height=”692”/) image source默认是地图编辑器的绝对路径,只需要用笔记本修改为相对路径,然后吧对应的图片文件放到同一个目录下面就OK

<?xml version="1.0" encoding="UTF-8"?>
<map version="1.0" orientation="orthogonal" width="30" height="12" tilewidth="21" tileheight="21">
 <tileset firstgid="1" name="tileset" tilewidth="21" tileheight="21" spacing="2" margin="2">
  <image source="tileset.png" trans="5e81a2" width="692" height="692"/>
 </tileset>
 <tileset firstgid="901" name="backgrounds" tilewidth="21" tileheight="21">
  <image source="backgrounds.png" trans="5e81a2" width="231" height="189"/>
 </tileset>
 <layer name="background" width="30" height="12">
  <data encoding="base64">
   加密的数据,太长,省略了
  </data>
 </layer>
 <layer name="terrain" width="30" height="12">
  <data encoding="base64">
  加密的数据,太长,省略了
  </data>
 </layer>
 <layer name="foreground" width="30" height="12">
  <data encoding="base64">
    加密的数据,太长,省略了
  </data>
 </layer>
 <objectgroup name="objects" width="30" height="12">
  <object name="player" x="63" y="126" width="21" height="21"/>
  <object name="item.chest" x="147" y="63" width="21" height="21"/>
  <object name="item.coin" x="252" y="21" width="21" height="21"/>
  <object name="item.key" x="567" y="126" width="21" height="21"/>
  <object name="trigger.exit" x="609" y="0" width="21" height="252"/>
  <object name="item.coin" x="273" y="21" width="21" height="21"/>
  <object name="item.coin" x="357" y="21" width="21" height="21"/>
  <object name="item.coin" x="378" y="21" width="21" height="21"/>
  <object name="trigger.exit" x="420" y="0" width="21" height="252"/>
 </objectgroup>
 <objectgroup name="physics" width="30" height="12">
  <object x="0" y="168">
   <polyline points="0,0 63,0 63,-21 105,-21 105,84 0,84 0,0"/>
  </object>
  <object x="378" y="189">
   <polyline points="0,-21 -21,0 -21,63 -105,63 -105,0 -126,-21"/>
  </object>
  <object x="252" y="168">
   <polyline points="0,0 21,0 21,-21 42,-21 42,-42 84,-42 84,-21 105,-21 105,0 126,0"/>
  </object>
  <object x="420" y="252">
   <polyline points="0,0 0,-63 21,-63 21,-84 42,-84 42,-126 63,-126 63,-105 84,-105 84,0 0,0"/>
  </object>
  <object x="546" y="252">
   <polyline points="0,0 0,-42 21,-42 21,-105 42,-105 42,-126 84,-126 84,0 0,0"/>
  </object>
 </objectgroup>
</map>

Libgdx 相关API介绍

1.com.badlogic.gdx.maps.Map implements Disposable
Map代表了我们用地图编辑器编辑完之后的TMX文件,实际上是其子类TiledMap来具体实现。主要包含1. MapProperties TMX文件的各种属性。2. MapLayers,Map layers是有序的并且是可索引的,可通过index来访问, MapLayer包含MapObject对象,可以通过方法来获取访问,Libgdx中有不同的MapObject可供使用,比如CircleMapObject, RectangleMapObject

方法、属性 描述
layers : MapLayers 地图中所包含的图层
properties : MapProperties 地图中所包含的对象
getLayers() : MapLayers 获取地图中所包含的图层
getProperties() : MapProperties 获取地图中的对象

2.com.badlogic.gdx.maps.tiled.TiledMap extends Map
TiledMap是Libgdx中真正承载TMX地图的类,代表了tiled map,增加了tiles 和 tiledsets
3.com.badlogic.gdx.maps.tiled.TiledMapTile : interface
代表了TiledMap中每个网格(瓦片),留意其方法就可以了

方法 描述
getId() : int 瓦片的ID
getTextureRegion() : TextureRegion 瓦片使用的TextureRegion
setTextureRegion(TextureRegion textureRegion) 设置瓦片的纹理
getOffsetX() : float 瓦片相对于x轴的位置
getProperties() : MapProperties 单个瓦片的属性

4.com.badlogic.gdx.maps.tiled.TiledMapTileSet implements Iterable<TiledMapTile>
TiledMapTile的实例,通常用来组成TiledMapLayer

方法、属性 描述
name : String 瓦片的name
tiles : IntMap<TiledMapTile> 瓦片
getTile (int id) : TiledMapTile 获取瓦片实例
iterator () : Iterator<TiledMapTile> 便利所有瓦片
removeTile (int id) 移除指定瓦片
size () 瓦片的数量

5.com.badlogic.gdx.maps.tiled.TiledMapTileSets implements Iterable<TiledMapTileSet>
其实看类名就知道是TiledMapTileSet的集合类,主要是提供工具帮助访问处理TiledMapTileSet。不做过多解释,可看源码。
6.com.badlogic.gdx.maps.MapLayer
MapLayer就是我们在地图编辑器中创建的Layer(普通Layer和ObjectLayer)对应,包含了Layer对应的object和properties

   // 下面列出的就是类中常用的属性,方法就是对属性的读写
    private String name = "";  // 图层的名字
    private float opacity = 1.0f;  // 透明度
    private boolean visible = true;  // 是否可见
    private MapObjects objects = new MapObjects();  // 包含的对象
    private MapProperties properties = new MapProperties();// 包含的属性  

7.com.badlogic.gdx.maps.tiled.TiledMapTileLayer extends MapLayer
TiledMap的Layer,具体的实现类


// Layer的高度和宽度
private int width;
private int height;
// 瓦片的高度和宽度
private float tileWidth;
private float tileHeight;
// 内部类,里面包含了private TiledMapTile tile
private Cell[][] cells;

8.com.badlogic.gdx.maps.MapLayers implements Iterable<MapLayer>
可被遍历的MapLayer合集,方便访问操作MapLayer,主要是TMX文件也是很多MapLayer的集合。

方法、属性 描述
get (int index) : MapLayer 根据索引获取MapLayer
get (String name) : MapLayer 根据名字返回找到的第一个匹配MapLayer
getIndex (String name) : int 根据名字返回查找到的第一个图片的位置
getCount () : int 获取TiledMap中Layer的数量
remove (int index) 移除指定TiledMapLayer
iterator () : Iterator<MapLayer> 迭代访问

9.com.badlogic.gdx.maps.MapObject
TiledMap里面包含的对象的基本属性,比如: name, opacity, color

private String name = "";
private float opacity = 1.0f;
private boolean visible = true;
private MapProperties properties = new MapProperties();
private Color color = Color.WHITE.cpy();

10.com.badlogic.gdx.maps.MapObjects implements Iterable<MapObject>
MapObject的集合,不做过多解释,可自己查询源码。
11.com.badlogic.gdx.maps.MapProperties
可索引的(indexed)string值,代表了Map中元素的属性,可以被递归访问,修改,和添加属性。

方法、属性 描述
get (int index) : MapLayer 根据索引获取MapLayer
get (String name) : MapLayer 根据名字返回找到的第一个匹配MapLayer
getIndex (String name) : int 根据名字返回查找到的第一个图片的位置
getCount () : int 获取TiledMap中Layer的数量
remove (int index) 移除指定TiledMapLayer
iterator () : Iterator<MapLayer> 迭代访问

12.com.badlogic.gdx.maps.tiled.TmxMapLoader
地图加载去,使用方法简单,可参考后面的源码
13.com.badlogic.gdx.maps.tiled.renderers.OrthogonalTiledMapRenderer
渲染地图, 使用方法简单,可参考后面源码

代码用例展示

1.TiledMapSample这个用例只是简单的展示加载和渲染地图,以及操作照相机,来展示地图的不同部分。

public class TiledMapSample extends ApplicationAdapter {
   private static final float VIRTUAL_WIDTH = 384.0f;
   private static final float VIRTUAL_HEIGHT = 216.0f; 

   private static final float CAMERA_SPEED = 100.0f;

private OrthographicCamera camera;
private Viewport viewport;

private TiledMap map;
private TmxMapLoader loader;
private OrthogonalTiledMapRenderer renderer;

private Vector2 direction;

@Override
public void create() {      
    camera = new OrthographicCamera();
    viewport = new FitViewport(VIRTUAL_WIDTH, VIRTUAL_HEIGHT, camera);

    loader = new TmxMapLoader();
    map = loader.load("p/platformer.tmx");
    renderer = new OrthogonalTiledMapRenderer(map);

    direction = new Vector2();
}

@Override
public void dispose() {
    map.dispose();
    renderer.dispose();
}

@Override
public void render() {
    Gdx.gl.glClearColor(0.8f, 0.8f, 0.8f, 1.0f);
    Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);

    updateCamera();

    renderer.setView(camera);
    renderer.render();
}

@Override
public void resize(int width, int height) {
    viewport.update(width, height);
}

private void updateCamera() {
    direction.set(0.0f, 0.0f);

    int mouseX = Gdx.input.getX();
    int mouseY = Gdx.input.getY();
    int width = Gdx.graphics.getWidth();
    int height = Gdx.graphics.getHeight();

    if (Gdx.input.isKeyPressed(Keys.LEFT) || (Gdx.input.isTouched() && mouseX < width * 0.25f)) {
        direction.x = -1;
    }
    else if (Gdx.input.isKeyPressed(Keys.RIGHT) || (Gdx.input.isTouched() && mouseX > width * 0.75f)) {
        direction.x = 1;
    }

    if (Gdx.input.isKeyPressed(Keys.UP) || (Gdx.input.isTouched() && mouseY < height * 0.25f)) {
        direction.y = 1;
    }
    else if (Gdx.input.isKeyPressed(Keys.DOWN) || (Gdx.input.isTouched() && mouseY > height * 0.75f)) {
        direction.y = -1;
    }

    direction.nor().scl(CAMERA_SPEED * Gdx.graphics.getDeltaTime());;

    camera.position.x += direction.x;
    camera.position.y += direction.y;

    TiledMapTileLayer layer = (TiledMapTileLayer)map.getLayers().get(0);

    float cameraMinX = viewport.getWorldWidth() * 0.5f;
    float cameraMinY = viewport.getWorldHeight() * 0.5f;
    float cameraMaxX = layer.getWidth() * layer.getTileWidth() - cameraMinX;
    float cameraMaxY = layer.getHeight() * layer.getTileHeight() - cameraMinY;

    camera.position.x = MathUtils.clamp(camera.position.x, cameraMinX, cameraMaxX);
    camera.position.y= MathUtils.clamp(camera.position.y, cameraMinY, cameraMaxY);

    camera.update();
}
}

2. TiledMapObjectsSample示例不仅加载渲染地图也解析了里面包含的对象
这里写图片描述
public class TiledMapObjectsSample extends ApplicationAdapter {
private static final float SCALE = 0.2916f;
private static final int VIRTUAL_WIDTH = (int) (1280 * SCALE);
private static final int VIRTUAL_HEIGHT = (int) (720 * SCALE);

private static final float CAMERA_SPEED = 100.0f;

private OrthographicCamera camera;
private Viewport viewport;
private SpriteBatch batch;

private TiledMap map;
private TmxMapLoader loader;
TiledMapTileLayer layer;
private OrthogonalTiledMapRenderer renderer;

private Vector2 direction;

private Array<Sprite> enemies;
private Array<Sprite> items;
private Array<Sprite> triggers;
private Sprite player;
private TextureAtlas atlas;

@Override
public void create() {
    camera = new OrthographicCamera();
    viewport = new FitViewport(VIRTUAL_WIDTH, VIRTUAL_HEIGHT, camera);
    batch = new SpriteBatch();
    loader = new TmxMapLoader();
    map = loader.load("p/tiled-objects.tmx");
    renderer = new OrthogonalTiledMapRenderer(map, batch);
    atlas = new TextureAtlas(Gdx.files.internal("p/sprites.atlas"));
    direction = new Vector2();

    processMapMetadata();
}

@Override
public void dispose() {
    map.dispose();
    renderer.dispose();
    atlas.dispose();
    batch.dispose();
}

@Override
public void render() {
    Gdx.gl.glClearColor(0.8f, 0.8f, 0.8f, 1.0f);
    Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);

    updateCamera();

    renderer.setView(camera);
    renderer.render();

    batch.begin();

    for (Sprite enemy : enemies) {
        enemy.draw(batch);
    }

    for (Sprite item : items) {
        item.draw(batch);
    }

    for (Sprite trigger : triggers) {
        trigger.draw(batch);
    }

    player.draw(batch);

    batch.end();
}

@Override
public void resize(int width, int height) {
    viewport.update(width, height);
}

private void updateCamera() {
    direction.set(0.0f, 0.0f);

    int mouseX = Gdx.input.getX();
    int mouseY = Gdx.input.getY();
    int width = Gdx.graphics.getWidth();
    int height = Gdx.graphics.getHeight();

    if (Gdx.input.isKeyPressed(Keys.LEFT) || (Gdx.input.isTouched() && mouseX < width * 0.25f)) {
        direction.x = -1;
    } else if (Gdx.input.isKeyPressed(Keys.RIGHT) || (Gdx.input.isTouched() && mouseX > width * 0.75f)) {
        direction.x = 1;
    }

    if (Gdx.input.isKeyPressed(Keys.UP) || (Gdx.input.isTouched() && mouseY < height * 0.25f)) {
        direction.y = 1;
    } else if (Gdx.input.isKeyPressed(Keys.DOWN) || (Gdx.input.isTouched() && mouseY > height * 0.75f)) {
        direction.y = -1;
    }

    direction.nor().scl(CAMERA_SPEED).scl(Gdx.graphics.getDeltaTime());
    ;

    camera.position.x += direction.x;
    camera.position.y += direction.y;
    // 获取Map编辑器里面最底层的Layer,同时也是tml文件里面最上面的一层layer


    float cameraMinX = viewport.getWorldWidth() * 0.5f;
    float cameraMinY = viewport.getWorldHeight() * 0.5f;
    float cameraMaxX = layer.getWidth() * layer.getTileWidth() - cameraMinX;
    float cameraMaxY = layer.getHeight() * layer.getTileHeight() - cameraMinY;

    camera.position.x = MathUtils.clamp(camera.position.x, cameraMinX, cameraMaxX);
    camera.position.y = MathUtils.clamp(camera.position.y, cameraMinY, cameraMaxY);

    camera.update();
}

private void processMapMetadata() {

    // Load entities
    System.out.println("Searching for game entities...\n");

    enemies = new Array<Sprite>();
    items = new Array<Sprite>();
    triggers = new Array<Sprite>();

    layer = (TiledMapTileLayer) map.getLayers().get(0);
    MapObjects objects = map.getLayers().get("objects").getObjects();
    System.out.println("width=" + layer.getWidth() +"  tileWidth" + layer.getTileWidth());

    for (MapObject object : objects) {
        String name = object.getName();;
        String[] parts = name.split("[.]");
        RectangleMapObject rectangleObject = (RectangleMapObject) object;
        Rectangle rectangle = rectangleObject.getRectangle();

        System.out.println("Object found");
        System.out.println("- name: " + name);
        System.out.println("- position: (" + rectangle.x + ", " + rectangle.y + ")");
        System.out.println("- size: (" + rectangle.width + ", " + rectangle.height + ")");

        if (name.equals("enemy")) {
            Sprite enemy = new Sprite(atlas.findRegion("enemy"));
            enemy.setPosition(rectangle.x, rectangle.y);
            enemies.add(enemy);
        } else if (name.equals("player")) {
            player = new Sprite(atlas.findRegion("player"));
            player.setPosition(rectangle.x, rectangle.y);
        } else if (parts.length > 1 && parts[0].equals("item")) {
            Sprite item = new Sprite(atlas.findRegion(parts[1]));
            item.setPosition(rectangle.x, rectangle.y);
            items.add(item);
        } else if (parts.length > 0 && parts[0].equals("trigger")) {
            Sprite trigger = new Sprite(atlas.findRegion("pixel"));
            trigger.setColor(1.0f, 1.0f, 1.0f, 0.5f);
            trigger.setScale(rectangle.width, rectangle.height);
            trigger.setPosition(rectangle.x - rectangle.width * 0.5f, rectangle.y + rectangle.height * 0.5f);
            triggers.add(trigger);
        }
    }
}
}  

这里写图片描述

作者:zqiang_55 发表于2016/11/25 21:03:36 原文链接
阅读:28 评论:0 查看评论

IAR for ARM系列教程(一)_新建软件工程详细过程

$
0
0

Ⅰ、写在前面

本文使用目前(20161125)最新版本的IAR for ARM V7开发环境、以STM32的处理器为例给大家讲述新建一个软件工程的详细过程。

 

其它版本IAR其它处理器新建软件工程的过程类似,或许存在略微的差异,但也可以参考本文内容。

 

为方便广大STM32学习者,我将在下面把STM32F0 - F4各个系列芯片新建好的Demo软件工程、相关的工具和文档提供给大家下载

 

本文内容已经整理成PDF文件,提供给大家下载:

http://pan.baidu.com/s/1eSeDSu2

 

Ⅱ、本文要点

网上很多人问:“我之前用IAR for ARM V5V6建立好的工程,在IED升级到V7之后,打开以前的工程,编译出来一大堆错误?”。

IAR for ARM在版本升级之后,工具链存在略微的差异,下面新建工程过程将会说到这些问题。

 

按照KeilMDK-ARM)新建软件工程的思路,将内容分为准备工作新建工程两大步骤来讲述。

 

本文基于STM32F1系列芯片为例来讲述(其它芯片类似),将简单描述一下准备STM32F1标准外设库、Demo源代码(LED闪烁例程)。

 

本文讲述的内容以简单为主,只讲述新建工程中常规的配置,其他大部分使用默认配置。更多详细配置和教程敬请等待我公众号或博客的更新。

 

Ⅲ、 准备工作

俗话说“磨刀不误砍柴工”,准备工作虽然看上去与题目关系不大,但也是本文的重点,做好了准备工作,后面新建软件工程的工作就很容易实现了。

 

1.安装IAR for ARM软件工具

本文必备工具,这里不多说,详情请看我的另一篇文章【IAR for ARM介绍、下载、安装与注册】:

博客:http://blog.csdn.net/ybhuangfugui/article/details/52562533

PDFhttp://pan.baidu.com/s/1eSeDSu2

 

2.STM32标准外设库下载

官网下载链接(需要ST账号登陆):

http://www.st.com/content/st_com/en/products/embedded-software/mcus-embedded-software/stm32-embedded-software.html?querycriteria=productId=SC961

 

根据芯片型号下载对应的标准外设库(如芯片为STM32F103ZE,则下载对应的STM32F10x_StdPeriph_Lib),如下图:


也可以到我百度网盘下载(和官网一样):

http://pan.baidu.com/s/1qYbBKMK

 

3.整理工程文件夹

这一小节的内容可根据自己习惯来整理,我整理的文件及文件夹结构是按照常规的整理方式。你如果觉得合理,可以引用;如果觉得不习惯这个结构方式,可以自己整理。

 

首先建立一个存放工程文件的文件夹:STM32F103ZEIAR_Demo

 

A.提取ST标准外设库文件

ST官网下载的标准外设库里面有很多源代码文件,但我们只需要使用部分文件。因此,需要提取使用的文件及文件夹到我们的工程中。

 

解压STM32F10x_StdPeriph_Lib”,可以看到在主目录下面有四个文件夹:

_htmresc:图片文件夹(不提取

Libraries:库文件夹(提取大部分

Project  示例工程(提取少部分

Utilities 公共代码、评估板代码(不提取)

 

我们使用标准外设库主要提取的文件是Libraries下面的文件,但有些文件及文件夹也是多余的。因此,我将多余的文件及文件夹去掉,保留需要的文件和文件夹。

 

这里强调一点Libraries里面的startup启动文件有些需要修改。我们使用之前的库(如F1F2的库),这些库是使用老版本的IAR for ARM V5V6版本编写的,在V7版本上使用这些库就不兼容,需要将text:CODE:REORDER改为text:CODE:NOROOT:REORDER

 

B.新建文件和文件夹

除了标准外设库文件之外,我们需要建立存放自己代码的文件和文件夹。我这里主要需要新建三个文件夹:

App: 应用部分代码

Bsp: 底层部分代码

Doc: 说明文档

 

在自己新建的文件夹下面还需要新建自己的文件,文件里面添加源代码,我们提供的Demo工程,实现的功能比较简单,就是一个LED闪烁。这里不描述,具体可以在最后下载查看。

 

上面两个步骤完成之后,我们就可以看到在STM32F103ZEIAR_Demo”下面整理的文件夹:


 

至此,准备工作算完成了,上面提取的文件、新建的文件里面具体的内容请下载Demo工程查看

 

Ⅳ、新建软件工程

新建一个简单、基础的软件工程大概需要有三个步骤:创建工程、添加文件和配置工程。

上面的准备工作做好之后,就可新建自己的软件工程了,下面将一步一步讲述从零开始新建一个软件工程的详细过程。

 

1.创建工程

创建工程的步骤是基础,也比较简单。

 

A.打开软件,创建新工程(Project -> Create New Project


 

B.创建一个空工程


 

C.选择路径,保存名称


 

D.空基础工程


 

至此,一个空的基础工程就创建好了,需要进一步添加文件到工程和配置工程。

 

2.添加文件

准确的来说,应该是添加组(文件夹)和添加文件。直接的说,就是将你自己的源代码(前面提取的库、新建的文件等)添加到工程中。

 

这里的工程项目管理可根据自己的想法来定义(类似于自己分类、命名文件夹和文件),我这里按照常规的方式进行管理项目。

 

IARKeil组管理的区别

IAR可以添加多级组,类似于文件夹下可以再建文件夹,一直下去。

Keil只能添加单级组,类似于文件夹下面只能添加文件,而不能在添加文件夹。

 

为了简单、遵循Keil组结构,我们在IAR中分组方式也按照Keil方式分组,先在工程中添加组,再在组中添加文件......一次循环下去直到完成。

 

A.工程中添加组


 

B.填写组的名称 -> OK

 


C.组中添加文件


 

D.按住Ctrl,鼠标选中要添加的文件

 


E.依次添加(按照上面步骤循环),直到添加完成


 

3.配置工程

配置工程对于初学者来说,大部分内容只需要默认即可,这里只讲述几个常见的配置,能满足基本的功能。更多配置可进入我微信公众号或博客查看。

 

A.进入配置选项


 

B.选择器件


 

C.库配置Library Configration

第一Library:如果需要使用某些标准的库函数接口(如我们使用的printf and scanf),就需要选择Full(见下图)。

 

第二CMSIS:是微控制器软件接口标准(Cortex Microcontroller Software Interface Standard)的意思。IAR for ARMV5V6V7版本之间存在差异,很多人用新版本IAR编译以前工程出现错误的原因就在于此(如STM32F1的库就使用较老版本的CMSIS,我们使用新版本IAR就需要勾选这里)。


 

D.预处理Preprocessor - 添加路径

添加的路径最好是相对路径,而不是绝对路径。使用绝对路径工程位置改变之后就找不到文件,就会出错。可以点击按钮选择路径,也可以通过复制文件路径进行配置。



·

·

一步一步添加,直到最后完成

·

·


 

E.预处理Preprocessor - 预定义

这里的预定义类似于在源代码中的#define xxx 这种宏定义。这里的STM32F10X_HD可以在stm32f10x.h中打开即可,USE_STDPERIPH_DRIVER这个宏定义我已经定义在在stm32f10x.h文件中。


 

F.输出Hex文件

很多初学的朋友都会问怎么输出 Hex( 可直接下载的程序文件),只需要按照下面配置即可输出Hex文件。


 

G.选择下载调试工具

根据自己情况选择的下载调试工具。使用ST-Link的朋友需要注意,有些时候ST-Link默认的接口是JTAG,需要改为SWD才能使用(见下图)。

 


 

Ⅴ、下载

为方便广大STM32学习者,我将常见的STM32Demo软件工程”已经建好,里面包含KeilMDK-ARM新建的工程供大家下载。

 

STM32F0工程模板:

http://pan.baidu.com/s/1pKSkSxt

 

STM32F1工程模板:

http://pan.baidu.com/s/1c1AWupM

 

STM32F2工程模板:

http://pan.baidu.com/s/1o8yGWg6

 

STM32F3工程模板:

http://pan.baidu.com/s/1boVXh2f

 

STM32F4工程模板:

http://pan.baidu.com/s/1qYzYMuS

 

注意:由于许多网盘近年来受到影响都相继停止服务或关闭了,如果网盘链接失效,请在微信公众号查看更新链接,或微信联系作者。

 

Ⅵ、说明

上面新建软件工程主要是针对初学者,写的比较基础的。若要了解更多关于Keil的使用教程,可以进入我微信公众号或博客查看。

 

以上内容仅供参考,若有不对之处,敬请谅解。

 

Ⅶ、最后

我的博客:http://blog.csdn.net/ybhuangfugui

微信公众号:EmbeddDeveloper

 

本着免费分享的原则,方便大家业余利用手机学习知识,定期在微信公众号分享相关知识。如果觉得文章的内容对你有用,又想了解更多相关的文章,请用微信搜索EmbeddDeveloper” 或者扫描下面二维码、关注,将有更多精彩内容等着你。

 

作者:ybhuangfugui 发表于2016/11/25 21:25:40 原文链接
阅读:50 评论:0 查看评论

Android 点击外部软键盘隐藏寻找最优解

$
0
0

Android 软键盘隐藏寻找最优解

本文原创,转载请注明出处。
欢迎关注我的 简书 ,关注我的专题 Android Class 我会长期坚持为大家收录简书上高质量的 Android 相关博文。

写在前面:
最近我自己的开发任务接近尾声,提交测试之后收到了一个 bug,这个 bug 描述起来是这个样子的:

希望当点击外部软键盘隐藏的时候,EditText 的光标也消失。

当我看到这个 bug 的时候,心里想,额…应该不难吧,隐藏软键盘大家都会,那当我隐藏软键盘的时候,让 EditText 的 Cursor 消失就不好了?
事实上解决这个问题确实不难,但是作为一个稍微有点追(jiao)求(qing)的程序员,其实解决这个问题,还是经历了一些思考过程的,所以我把它整理出来,分享给大家。

先来看看这个 bug 的描述:当软键盘隐藏,光标消失。

测试的这段描述直接对我这种心思单纯的程序猿造成了误导,因为它直接把我的思路引到了光标的处理上:

先不说软键盘了,直接看看处理 cursor 是什么效果:

et1 隐藏光标

et2 不作处理

这个 Demo 项目我目前有两个 EditText et1,et2,还有一个不做任何处理的 button,此时我仅仅给 et1 隐藏光标 cursor,调用 et1.setCursorVisible(false),可以看到上图的效果,et1 的光标消失了。

head da

是啊通常我们项目里面的 EditText 只有一个光标,那光标是消失了,万一底下有那条线呢?不管了?

不要说再隐藏下面那条线就 ok 了,这样一来就太复杂了,说明我们思考的出发点有问题。好吧我们试图将思路拉回到正轨。

仔细想想,EditText 有焦点的时候,光标量,线也亮。所以我从 EditText 的 focus 入手考虑,有焦点的时候弹出软键盘,没焦点的时候,隐藏软键盘。

我尝试了 EditText 的 clearFocus 和 其他 View requestFocus 属性来达到焦点变换的目的使 EditText 失去焦点从而让光标消失,但是这俩种办法都没有什么用,同样,我给其他 View 设置 onClickListener 同样没有达到我想要的效果。不过最终有两个属性帮助我解决了这个问题。请继续看:

        et1.setOnFocusChangeListener(new View.OnFocusChangeListener() {
            @Override
            public void onFocusChange(View v, boolean hasFocus) {
                if (!hasFocus) {
                    InputMethodManager im = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
                    im.hideSoftInputFromWindow(v.getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS);
                }
            }
        });

我给我的 EditText 加了如上代码,点击 EditText 弹出软键盘,然后点击了 EditText 之外的空白区域,没反应。再点击一下 Button,软键盘还是没有收起。
(没有收起来就对了)
因为无论是界面中的空白区域,还是 button 它们都没能力去抢夺走 EditText 的焦点,这个时候我给界面的根布局设置两个属性达到了目的:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/content_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:clickable="true"
    android:focusableInTouchMode="true"
    android:orientation="vertical"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    app:layout_behavior="@string/appbar_scrolling_view_behavior"
    tools:context="com.blog.melo.buzzerbeater.MainActivity"
    tools:showIn="@layout/activity_main">

    <EditText
        android:id="@+id/et1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="et1" />

    <EditText
        android:id="@+id/et2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="et2" />

android:clickable="true" android:focusableInTouchMode="true"
没错就是这两个属性,无论是设置给根布局,还是 button,都能做到将焦点获取,并隐藏软键盘的效果。到目前为止,我们的 bug 算是解决了。

另外多说一个我遇到的坑。当我的编译版本为 23.0.0 的时候,我给最外层的 CoordinatorLayout 设置 clickablefocusableInTouchMode 属性的时候,程序直接崩溃了,去 SO 上搜了搜,换了编译版本为 23.0.4 之后,崩溃解决了,但是 CoordinatorLayout 依然无法获取焦点,我退而求其次,给我的 content_main 布局设置属性,此时生效。为了让我点击 Toolbar 之后,软键盘也消失,我又给 Toobar 的布局设置了这俩属性,终于达到了我要的效果。(非常不优雅的解决办法)

继续我们的寻找最优解之路,下面来看看第二个方法:

    public void setupUI(View view) {

        if (!(view instanceof EditText)) {
            view.setOnTouchListener(new View.OnTouchListener() {
                public boolean onTouch(View v, MotionEvent event) {
                    hideSoftKeyboard(MainActivity.this);
                    return false;
                }
            });
        }

        if (view instanceof ViewGroup) {
            for (int i = 0; i < ((ViewGroup) view).getChildCount(); i++) {
                View innerView = ((ViewGroup) view).getChildAt(i);
                setupUI(innerView);
            }
        }
    }

    public static void hideSoftKeyboard(Activity activity) {
        InputMethodManager inputMethodManager = (InputMethodManager) activity.getSystemService(Activity.INPUT_METHOD_SERVICE);
        inputMethodManager.hideSoftInputFromWindow(activity.getCurrentFocus().getWindowToken(), 0);
    }

新增两个方法,给整个 View 树中所有的 View 设置 onTouchListener ,然后我们把 RootView 传进去:

        LinearLayout contentMain = (LinearLayout) findViewById(R.id.content_main);

        setupUI(contentMain);

先来说说这个方法的问题,我们给界面中所有的 View 设置的触摸监听,当我触摸的不是 EditText 的时候,把软键盘隐藏。如果我没有给其它 view 设置android:clickable="true" android:focusableInTouchMode="true"属性,那么焦点依然是在 EditText 上的,光标自然也不会消失了。

(在魅族手机上测试光标居然消失了…原因不得而知,我突然间觉得第一次国产的 rom 帮了我优化,但是 nexus 上是不行的,总之还是需要我想办法去处理。)

既然有了第二种办法,回过头来看看第一种方法,第一种解决方法的问题在哪里呢?相信你也能感知到,如果我的界面复杂,难道我要给每一个 View 设置可点击的属性来达到目的吗?而且我需要给每个 EditText 都设置 onFocusChangeListener,无疑会增加代码量,让我们的代码可读性变差,并且极有可能出错。

前两种方法结合起来使用,确实可以解决大部分问题出现的场景了。我相信如果你对目前这解决方案心存不满的理由一定是:我需要对每个 EditText 都处理,或者对每个根布局都进行处理。这显然不够合理,所以来看下面这个方法。

创建一个 BaseActivity,完整代码如下:

public class BaseActivity extends AppCompatActivity {

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            // 获得当前得到焦点的View,一般情况下就是EditText(特殊情况就是轨迹求或者实体案件会移动焦点)
            View v = getCurrentFocus();
            if (isShouldHideInput(v, ev)) {
                hideSoftInput(v.getWindowToken());
            }
        }
        return super.dispatchTouchEvent(ev);
    }

    /**
     * 根据EditText所在坐标和用户点击的坐标相对比,来判断是否隐藏键盘,因为当用户点击EditText时没必要隐藏
     *
     * @param v
     * @param event
     * @return
     */
    private boolean isShouldHideInput(View v, MotionEvent event) {
        if (v != null && (v instanceof EditText)) {
            int[] l = {0, 0};
            v.getLocationInWindow(l);
            int left = l[0], top = l[1], bottom = top + v.getHeight(), right = left
                    + v.getWidth();
            if (event.getX() > left && event.getX() < right && event.getY() > top && event.getY() < bottom) {
                // 点击EditText的事件,忽略它。
                return false;
            } else {
                return true;
            }
        }
        // 如果焦点不是EditText则忽略,这个发生在视图刚绘制完,第一个焦点不在EditView上,和用户用轨迹球选择其他的焦点
        return false;
    }

    /**
     * 多种隐藏软件盘方法的其中一种
     *
     * @param token
     */
    private void hideSoftInput(IBinder token) {
        if (token != null) {
            InputMethodManager im = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
            im.hideSoftInputFromWindow(token, InputMethodManager.HIDE_NOT_ALWAYS);
        }
    }

}

目前的第三个解决方案是在 Activity 的 dispatchTouchEvent 方法中进行一系列判断,此刻我点击界面中的任何非 EditText 部分,软键盘都会收起来,并且我不需要在具体的对每一个 EditText 进行处理。

研究到这里心情好了很多,理清思路,目前我们还差最后一步了,目前实现了软键盘的隐藏,只要再把焦点给其他 View,EditText 的光标自然就消失了。相信你肯定没忘记,此刻需要给 View 设置 android:clickable="true" android:focusableInTouchMode="true" 属性

目前这种情况足够解决大部分问题,而我确实遇到了一个无法解决的。因为我需要对一个 TextView 的 enable 属性进行动态的管理,这个属性明显影响到了 clickablefocusableInTouchMode 属性,这个时候怎么办呢?看起来我只能对这种场景进行特殊处理了:

当我点击这个 TextView 的时候,我使用 et.setFocusable(false) ,移除它的焦点来消除 EditText 的光标,然后:

        et.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                et.setFocusableInTouchMode(true);
                return false;
            }
        });

让 EditText 在触摸事件中,再次获得焦点。

OK,研究到了这里的解决方案基本上我可以接受了。如果有优雅的解决办法,欢迎来骚扰我~

有些朋友说,我想监听系统软键盘的事件,通过它的弹出或者收起来做某些我的需求,可是系统并没有提供出来相应的办法,应该怎么解决?

这里推荐一个网上我认为是最好的方案:

    /**
     * 监听软键盘事件
     *
     * @param rootView
     * @return
     */
    private boolean isKeyboardShown(View rootView) {
        final int softKeyboardHeight = 100;
        Rect r = new Rect();
        rootView.getWindowVisibleDisplayFrame(r);
        DisplayMetrics dm = rootView.getResources().getDisplayMetrics();
        int heightDiff = rootView.getBottom() - r.bottom;
        return heightDiff > softKeyboardHeight * dm.density;
    }

其原理是通过监听可见根布局的尺寸大小,来判断是否认为系统弹出了软键盘。

重写根布局的 View ,在 onMeasure 中使用这个方法。

public class CommonLinearLayout extends LinearLayout {
    public CommonLinearLayout(Context context) {
        this(context, null);
    }

    public CommonLinearLayout(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public CommonLinearLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        if (isKeyboardShown(this)) {
            Log.e("CommonLinearLayout","show");
        }else {
            Log.e("CommonLinearLayout","hide");
        }
    }

    /**
     * 监听软键盘事件
     *
     * @param rootView
     * @return
     */
    private boolean isKeyboardShown(View rootView) {
        final int softKeyboardHeight = 100;
        Rect r = new Rect();
        rootView.getWindowVisibleDisplayFrame(r);
        DisplayMetrics dm = rootView.getResources().getDisplayMetrics();
        int heightDiff = rootView.getBottom() - r.bottom;
        return heightDiff > softKeyboardHeight * dm.density;
    }

}

测试结果:

测试结果

可以看到系统正确判断了软键盘的弹起和隐藏。可以根据它来做你想要的操作。

长舒一口气,本文到这里也要结束了,这就是一次我对软键盘和 EditText 的研究,如果有更好的办法,欢迎告知哦~

祝大家周末愉快,天冷添衣服。

作者:MeloDev 发表于2016/11/25 21:58:17 原文链接
阅读:16 评论:0 查看评论

UVC 摄像头驱动(三)配置摄像头,实时数据采集

$
0
0

  前面分析了 UVC 摄像头的硬件模型和描述符,对于一个 usb 摄像头来说,内部大概分为一个 VC 接口和一个 VS 接口,VC 接口内部有许多 unit 和 terminal 用来“控制”摄像头,比如我们可以通过 Process unit 设置白平衡、曝光等等。对于 VS 接口来说,标准 VS 接口往往含有许多个设置,每一个设置都包含一个实时传输端点,虽然它们的端点地址可能相同,但是它们的最大传输包大小不同,在 Class specific VS 接口中,包含多个 Format ,每一个 Format 包含多个 Frame ,Format 指的 YUYV MJPG 等等,Frame 就是各种分辨率 480*320 640 * 480 等等。以上这些信息,都是通过分析描述符来获得。

VideoStreaming Requests

  参考 UVC 1.5 Class specification 4.3 节
这里写图片描述
我们需要使用控制传输来和VS通信,Probe and commit 设置,请求格式参考上图。

  • bmRequestType 请求类型,参考标准USB协议
  • bRequest 子类,定义在 Table A-8
  • CS ,Control Selector ,定义在 Table A-16 ,例如是probe 还是 commit
  • wIndex 高字节为0,低字节为接口号
  • wLength 和 Data 和标准USB协议一样,为数据长度和数据

参数设置的过程需要主机和USB设备进行协商, 协商的过程大致如下图所示:
这里写图片描述

  • Host 先将期望的设置发送给USB设备(PROBE)
  • 设备将Host期望设置在自身能力范围之内进行修改,返回给Host(PROBE)
  • Host 认为设置可行的话,Commit 提交(COMMIT)
  • 设置接口的当前设置为某一个设置
      
    那么协商哪些数据?这些数据在哪里定义?参考Table 4-75 ,里面包含了使用哪一个Frame 哪一个 Frame 帧频率,一次传输包大小等等的信息。
    参考代码:
static int myuvc_try_streaming_params(struct myuvc_streaming_control *ctrl)
{
    __u8 *data;
    __u16 size;
    int ret;
    __u8 type = USB_TYPE_CLASS | USB_RECIP_INTERFACE;
    unsigned int pipe;

    memset(ctrl, 0, sizeof *ctrl);

    ctrl->bmHint = 1;   /* dwFrameInterval */
    ctrl->bFormatIndex = 1;
    ctrl->bFrameIndex  = 1;
    ctrl->dwFrameInterval = 333333;
    ctrl->dwClockFrequency = 48000000;
    ctrl->wCompQuality = 61;
    size = 34;
    data = kzalloc(size, GFP_KERNEL);
    if (data == NULL)
        return -ENOMEM;

    *(__le16 *)&data[0] = cpu_to_le16(ctrl->bmHint);
    data[2] = ctrl->bFormatIndex;
    data[3] = ctrl->bFrameIndex;
    *(__le32 *)&data[4] = cpu_to_le32(ctrl->dwFrameInterval);
    *(__le16 *)&data[8] = cpu_to_le16(ctrl->wKeyFrameRate);
    *(__le16 *)&data[10] = cpu_to_le16(ctrl->wPFrameRate);
    *(__le16 *)&data[12] = cpu_to_le16(ctrl->wCompQuality);
    *(__le16 *)&data[14] = cpu_to_le16(ctrl->wCompWindowSize);
    *(__le16 *)&data[16] = cpu_to_le16(ctrl->wDelay);
    put_unaligned_le32(ctrl->dwMaxVideoFrameSize, &data[18]);
    put_unaligned_le32(ctrl->dwMaxPayloadTransferSize, &data[22]);

    if (size == 34) {
        put_unaligned_le32(ctrl->dwClockFrequency, &data[26]);
        data[30] = ctrl->bmFramingInfo;
        data[31] = ctrl->bPreferedVersion;
        data[32] = ctrl->bMinVersion;
        data[33] = ctrl->bMaxVersion;
    }

    pipe = usb_sndctrlpipe(myuvc_udev, 0);
    type |= USB_DIR_OUT;

    ret = usb_control_msg(myuvc_udev, pipe, 0x01, type, 0x01 << 8,
            0 << 8 | myuvc_streaming_intf, data, size, 5000);

    kfree(data);

    return (ret < 0) ? ret : 0;
}

static int myuvc_get_streaming_params(struct myuvc_streaming_control *ctrl)
{
    __u8 *data;
    __u16 size;
    int ret;
    __u8 type = USB_TYPE_CLASS | USB_RECIP_INTERFACE;
    unsigned int pipe;

    size = 34;
    data = kmalloc(size, GFP_KERNEL);
    if (data == NULL)
        return -ENOMEM;

    pipe = usb_rcvctrlpipe(myuvc_udev, 0);
    type |= USB_DIR_IN;

    ret = usb_control_msg(myuvc_udev, pipe, 0x81, type, 0x01 << 8,
            0 << 8 | myuvc_streaming_intf, data, size, 5000);

    if (ret < 0)
        goto done;

    ctrl->bmHint = le16_to_cpup((__le16 *)&data[0]);
    ctrl->bFormatIndex = data[2];
    ctrl->bFrameIndex = data[3];
    ctrl->dwFrameInterval = le32_to_cpup((__le32 *)&data[4]);
    ctrl->wKeyFrameRate = le16_to_cpup((__le16 *)&data[8]);
    ctrl->wPFrameRate = le16_to_cpup((__le16 *)&data[10]);
    ctrl->wCompQuality = le16_to_cpup((__le16 *)&data[12]);
    ctrl->wCompWindowSize = le16_to_cpup((__le16 *)&data[14]);
    ctrl->wDelay = le16_to_cpup((__le16 *)&data[16]);
    ctrl->dwMaxVideoFrameSize = get_unaligned_le32(&data[18]);
    ctrl->dwMaxPayloadTransferSize = get_unaligned_le32(&data[22]);

    if (size == 34) {
        ctrl->dwClockFrequency = get_unaligned_le32(&data[26]);
        ctrl->bmFramingInfo = data[30];
        ctrl->bPreferedVersion = data[31];
        ctrl->bMinVersion = data[32];
        ctrl->bMaxVersion = data[33];
    } else {
        //ctrl->dwClockFrequency = video->dev->clock_frequency;
        ctrl->bmFramingInfo = 0;
        ctrl->bPreferedVersion = 0;
        ctrl->bMinVersion = 0;
        ctrl->bMaxVersion = 0;
    }

done:
    kfree(data);

    return (ret < 0) ? ret : 0;
}

static int myuvc_set_streaming_params(struct myuvc_streaming_control *ctrl)
{
    __u8 *data;
    __u16 size;
    int ret;
    __u8 type = USB_TYPE_CLASS | USB_RECIP_INTERFACE;
    unsigned int pipe;

    size = 34;
    data = kzalloc(size, GFP_KERNEL);
    if (data == NULL)
        return -ENOMEM;

    *(__le16 *)&data[0] = cpu_to_le16(ctrl->bmHint);
    data[2] = ctrl->bFormatIndex;
    data[3] = ctrl->bFrameIndex;
    *(__le32 *)&data[4] = cpu_to_le32(ctrl->dwFrameInterval);
    *(__le16 *)&data[8] = cpu_to_le16(ctrl->wKeyFrameRate);
    *(__le16 *)&data[10] = cpu_to_le16(ctrl->wPFrameRate);
    *(__le16 *)&data[12] = cpu_to_le16(ctrl->wCompQuality);
    *(__le16 *)&data[14] = cpu_to_le16(ctrl->wCompWindowSize);
    *(__le16 *)&data[16] = cpu_to_le16(ctrl->wDelay);
    put_unaligned_le32(ctrl->dwMaxVideoFrameSize, &data[18]);
    put_unaligned_le32(ctrl->dwMaxPayloadTransferSize, &data[22]);

    if (size == 34) {
        put_unaligned_le32(ctrl->dwClockFrequency, &data[26]);
        data[30] = ctrl->bmFramingInfo;
        data[31] = ctrl->bPreferedVersion;
        data[32] = ctrl->bMinVersion;
        data[33] = ctrl->bMaxVersion;
    }

    pipe = usb_sndctrlpipe(myuvc_udev, 0);
    type |= USB_DIR_OUT;

    ret = usb_control_msg(myuvc_udev, pipe, 0x01, type, 0x02 << 8,
            0 << 8 | myuvc_streaming_intf, data, size, 5000);

    kfree(data);

    return (ret < 0) ? ret : 0;

}

VideoControl Requests

  这里我们主要分析 VC 接口里的 Processing Unit Control Requests 以亮度为例:
这里写图片描述
这里写图片描述

static void myuvc_set_le_value(__s32 value, __u8 *data)
{
    int bits = 16;
    int offset = 0;
    __u8 mask;

    data += offset / 8;
    offset &= 7;

    for (; bits > 0; data++) {
        mask = ((1LL << bits) - 1) << offset;
        *data = (*data & ~mask) | ((value << offset) & mask);
        value >>= offset ? offset : 8;
        bits -= 8 - offset;
        offset = 0;
    }
}

static __s32 myuvc_get_le_value(const __u8 *data)
{
    int bits = 16;
    int offset = 0;
    __s32 value = 0;
    __u8 mask;

    data += offset / 8;
    offset &= 7;
    mask = ((1LL << bits) - 1) << offset;

    for (; bits > 0; data++) {
        __u8 byte = *data & mask;
        value |= offset > 0 ? (byte >> offset) : (byte << (-offset));
        bits -= 8 - (offset > 0 ? offset : 0);
        offset -= 8;
        mask = (1 << bits) - 1;
    }

    /* Sign-extend the value if needed. */
    value |= -(value & (1 << (16 - 1)));

    return value;
}

/* 参考:uvc_query_v4l2_ctrl */    
int myuvc_vidioc_queryctrl (struct file *file, void *fh,
                struct v4l2_queryctrl *ctrl)
{
    __u8 type = USB_TYPE_CLASS | USB_RECIP_INTERFACE;
    unsigned int pipe;
    int ret;
    u8 data[2];

    if (ctrl->id != V4L2_CID_BRIGHTNESS)
        return -EINVAL;

    memset(ctrl, 0, sizeof *ctrl);
    ctrl->id   = V4L2_CID_BRIGHTNESS;
    ctrl->type = V4L2_CTRL_TYPE_INTEGER;
    strcpy(ctrl->name, "MyUVC_BRIGHTNESS");
    ctrl->flags = 0;

    pipe = usb_rcvctrlpipe(udev, 0);
    type |= USB_DIR_IN;

    /* 发起USB传输确定这些值 */
    ret = usb_control_msg(udev, pipe, GET_MIN, type, PU_BRIGHTNESS_CONTROL << 8,
            ProcessingUnitID << 8 | myuvc_control_intf, data, 2, 5000);
    if (ret != 2)
        return -EIO;
    ctrl->minimum = myuvc_get_le_value(data);   /* Note signedness */


    ret = usb_control_msg(udev, pipe, GET_MAX, type,  PU_BRIGHTNESS_CONTROL << 8,
            ProcessingUnitID << 8 | myuvc_control_intf, data, 2, 5000);
    if (ret != 2)
        return -EIO;
    ctrl->maximum = myuvc_get_le_value(data);   /* Note signedness */

    ret = usb_control_msg(udev, pipe, GET_RES, type, PU_BRIGHTNESS_CONTROL << 8,
             ProcessingUnitID << 8 | myuvc_control_intf, data, 2, 5000);
    if (ret != 2)
        return -EIO;
    ctrl->step = myuvc_get_le_value(data);  /* Note signedness */

    ret = usb_control_msg(udev, pipe, GET_DEF, type, PU_BRIGHTNESS_CONTROL << 8,
            ProcessingUnitID << 8 | myuvc_control_intf, data, 2, 5000);
    if (ret != 2)
        return -EIO;
    ctrl->default_value = myuvc_get_le_value(data); /* Note signedness */

    printk("Brightness: min =%d, max = %d, step = %d, default = %d\n", ctrl->minimum, ctrl->maximum, ctrl->step, ctrl->default_value);

    return 0;
}

/* 参考 : uvc_ctrl_get */
int myuvc_vidioc_g_ctrl (struct file *file, void *fh,
                struct v4l2_control *ctrl)
{
    __u8 type = USB_TYPE_CLASS | USB_RECIP_INTERFACE;
    unsigned int pipe;
    int ret;
    u8 data[2];

    if (ctrl->id != V4L2_CID_BRIGHTNESS)
        return -EINVAL;

    pipe = usb_rcvctrlpipe(udev, 0);
    type |= USB_DIR_IN;

    ret = usb_control_msg(udev, pipe, GET_CUR, type, PU_BRIGHTNESS_CONTROL << 8,
            ProcessingUnitID << 8 | myuvc_control_intf, data, 2, 5000);
    if (ret != 2)
        return -EIO;
    ctrl->value = myuvc_get_le_value(data); /* Note signedness */

    return 0;
}

/* 参考: uvc_ctrl_set/uvc_ctrl_commit */
int myuvc_vidioc_s_ctrl (struct file *file, void *fh,
                struct v4l2_control *ctrl)
{
    __u8 type = USB_TYPE_CLASS | USB_RECIP_INTERFACE;
    unsigned int pipe;
    int ret;
    u8 data[2];

    if (ctrl->id != V4L2_CID_BRIGHTNESS)
        return -EINVAL;

    myuvc_set_le_value(ctrl->value, data);

    pipe = usb_sndctrlpipe(udev, 0);
    type |= USB_DIR_OUT;

    ret = usb_control_msg(udev, pipe, SET_CUR, type, PU_BRIGHTNESS_CONTROL << 8,
            ProcessingUnitID  << 8 | myuvc_control_intf, data, 2, 5000);
    if (ret != 2)
        return -EIO;

    return 0;
}

数据采集

  数据采集时,我们需要和标准VS接口设置里的实时端点通信获取数据,分配、设置、提交URB。
  以 640*320 分辨率的图像为例,每一个像素16bit,因此一帧图像占用空间 640*320*2字节 ,对于我的摄像头一次传输,3060字节。需要注意的是,标准UVC摄像头驱动中限制了一个URB的Buffer数量最大为32个,因此一个URB能承载的数据为 3060 * 32 ,经过计算,一帧图像数据需要多个 URB 来传输。因此在将图像数据拷贝到用户空间Buffer时候,可能在某一个URB包含两帧图像的数据,需要不要处理完前一帧就把第二帧图像数据丢弃了,那样会造成图像数据丢失。

static int myuvc_alloc_init_urbs(void)
{
    u16 psize;
    u32 size;
    int npackets;
    int i,j;
    struct urb *urb;
    //struct urb *urb;

    psize = 3060; /* 实时传输端点一次能传输的最大字节数 */
    size  = 614400;  /* 一帧数据的最大长度 */
    npackets = DIV_ROUND_UP(size, psize);
    if (npackets > 32)
        npackets = 32;
    myprintk("psize %d npackets %d\n",psize,npackets);
    size =  psize * npackets;
    for (i = 0; i < 5; ++i) { 
        /* 1. 分配usb_buffers */
        urb_buffer[i] = usb_alloc_coherent(
            myuvc_udev, size,
            GFP_KERNEL | __GFP_NOWARN, &urb_dma[i]);

        /* 2. 分配urb */
        myurb[i] = usb_alloc_urb(npackets, GFP_KERNEL);

        if (!urb_buffer[i] || !myurb[i])
        {
            //myuvc_uninit_urbs();
            myprintk("alloc buffer or urb failed\n");
            return -ENOMEM;
        }
    }

    /* 3. 设置urb */ 
    for (i = 0; i < 5; ++i) {
        urb = myurb[i];
        urb->dev = myuvc_udev;
        urb->context = NULL;
        urb->pipe = usb_rcvisocpipe(myuvc_udev, 0x81);
        urb->transfer_flags = URB_ISO_ASAP | URB_NO_TRANSFER_DMA_MAP;
        urb->interval = 1;
        urb->transfer_buffer = urb_buffer[i];
        urb->transfer_dma = urb_dma[i];
        urb->complete = myuvc_video_complete;
        urb->number_of_packets = npackets;
        urb->transfer_buffer_length = size;

        for (j = 0; j < npackets; ++j) {
            urb->iso_frame_desc[j].offset = j * psize;
            urb->iso_frame_desc[j].length = psize;
        }
    }  
    return 0;
}
static void myuvc_video_complete(struct urb *urb)
{
    u8 *src;
    //u8 *dest;
    int ret, i;
    int len;
    int maxlen;
//    int nbytes;
//    struct myuvc_buffer *buf;
    myprintk("video complete\n");
    switch (urb->status) {
    case 0:
        break;

    default:
        myprintk("Non-zero status (%d) in video "
            "completion handler.\n", urb->status);
        return;
    }

    for (i = 0; i < urb->number_of_packets; ++i) {
        if (urb->iso_frame_desc[i].status < 0) {
            myprintk("USB isochronous frame "
                "lost (%d).\n", urb->iso_frame_desc[i].status);
            continue;
        }

        src  = urb->transfer_buffer + urb->iso_frame_desc[i].offset;

        //dest = myuvc_queue.mem + buf->buf.m.offset + buf->buf.bytesused;

        len = urb->iso_frame_desc[i].actual_length;
        myprintk("len %d\n",urb->iso_frame_desc[i].actual_length);
        /* 判断数据是否有效 */
        /* URB数据含义:
         * data[0] : 头部长度
         * data[1] : 错误状态
         */
        if (len < 2 || src[0] < 2 || src[0] > len)
            continue;

        /* Skip payloads marked with the error bit ("error frames"). */
        if (src[1] & UVC_STREAM_ERR) {
            myprintk("Dropping payload (error bit set).\n");
            continue;
        }

        /* 除去头部后的数据长度 */
        len -= src[0];

        /* 缓冲区最多还能存多少数据 */
        //maxlen = buf->buf.length - buf->buf.bytesused;
        //nbytes = min(len, maxlen);

        /* 复制数据 */
        //memcpy(dest, src + src[0], nbytes);
        //buf->buf.bytesused += nbytes;

        /* 判断一帧数据是否已经全部接收到 */
        if (len > maxlen) {
            //buf->state = VIDEOBUF_DONE;
        }

        /* Mark the buffer as done if the EOF marker is set. */
        if (src[1] & UVC_STREAM_EOF) {
            myprintk("Frame complete (EOF found).\n");
            if (len == 0)
                myprintk("EOF in empty payload.\n");
            //buf->state = VIDEOBUF_DONE;
        }

    }

    /* 再次提交URB */


    if ((ret = usb_submit_urb(urb, GFP_ATOMIC)) < 0) {
        myprintk("Failed to resubmit video URB (%d).\n", ret);
    }
}
作者:lizuobin2 发表于2016/11/25 22:15:12 原文链接
阅读:25 评论:0 查看评论

Git基础

$
0
0

Git基础


工欲善其事必先利其器,作为一名程序员,对git工具的掌握是非常必要的.
Git的使用这里已经写的非常详细,感谢前辈.该文章只是个人的学习笔记,方便以后的查阅.走过的路至少要留下些脚印.

推荐一个在线练习Git命令的网站传送门

Git在Windows平台上的使用

Git安装

Git安装传送门

Windows平台上使用Git命令

链接文章中的git前总是加了个”$”符号,在Window平台并不需要.在Windows平台上我们可以使用cmd或者cmd的升级版PowerShell执行命令

像Java一样,Git命令的使用同样需要配置系统变量,不过,Git配置的是cmd目录,例如:F:\Program Files (x86)\Git\cmd;

配置流程:右键[我的电脑]->属性->高级系统设置->环境变量->系统变量->Path

初次使用Git前的配置
  • 配置用户信息

    用户信息配置命令

    git config --global user.name "John Doe"  
    git config --global user.email johndoe@example.com
    

    该命令配置完成后,表示你身份ID的生成,以后的每次提交都会附加该信息.在以后的日志查看中就会看到现在配置的名称和邮箱
    重复执行上述命令,可以修改配置的信息

  • 配置编辑器

    配置编辑器命令

    git config --global core.editor emacs
    

    NOTE:
    在程序升级时会保留配置信息
    检查配置信息命令 git config --list

获取Git仓库(repository)
  • 在现有目录中初始化仓库

    初始化仓库命令

    git init
    

    执行该命令后,将会在当前目录下生成.git文件夹,该文件夹内是初始化后的一些基础文件,禁止擅自修改或者删除

  • 从服务器端克隆现有仓库

    克隆仓库的命令

    git clone [url]
    

    例如:git clone https://github.com/libgit2/libgit2;
    该命令通常用于克隆或者说复制服务器端现有的仓库到本地;
    执行该命令后,默认配置下远程 Git 仓库中的每一个文件的每一个版本都将被拉取下来;
    克隆下来的文件都是已跟踪状态并且处于未修改状态;
    默认情况下,克隆的仓库名与服务器端相同,如果自定义本地仓库的名字,那么执行该命令git clone [url] [NewRepName];

控制文件

这里写图片描述

  • 查看文件状态

    • 文件状态明细

      查看文件状态命令

      git status  
      
    • 文件状态简览

      查看文件简览状态命令

      git status -s 或 git status --short
      

      简览状态是指文件明细的展示方式,git status比较详细,git status -s则使用了一些字符来表示文件的状态

      • git status 的状态表现方式

        git status  
        On branch master
        Untracked files:
          (use "git add <file>..." to include in what will be committed)
        
            README
        
        nothing added to commit but untracked files present (use "git add" to track)
        
      • git status -s 的状态表现方式

        git status -s
         M README            -- 右边的M,文件被修改但未放至暂存区
        MM Rakefile          -- 修改后的文件提交至暂存区后,该文件在工作区又被修改了
        A  lib/git.rb        -- 新添加到暂存区的文件
        M  lib/simplegit.rb  -- 左边的M,文件被修改并放至暂存区
        ?? LICENSE.txt       -- 新添加的未跟踪状态文件
        
  • 暂存文件

    暂存命令

    git add FileName  
    git add all  
    git add lib/  递归跟踪文件
    

    执行该命令后,文件的状态变更为跟踪状态,并处于暂存区;跟踪可以是某个文件,也可以是某个目录,当时某个目录时,将会递归跟踪该目录下的文件

  • 暂存已修改的文件

    该情况是指工作区中修改并提交至暂存区的文件,再次该文件后进行暂存,这也说明工作区和暂存区可以同时存在已修改的文件,也就是简览状态的[ MM ]表示的这种情况,实际上只是文件名相同,内容已经发生改变,执行git add命令后,工作区的文件将会覆盖暂存区的文件.

  • 查看修改的文件

    • 查看处于暂存区的文件的变化

      查看未暂存区文件变化的命令

      git diff
      

      该命令查看的是暂存前后的变化(工作区文件与暂存区文件的不同)

    • 查看暂存的文件的变化

      查看暂存区文件变化的命令

      git diff --cached 或 git diff --staged (Git 1.6.1 及更高版本可用)
      

      该命令查看的是提交前后的变化(暂存区与服务器端文件的不同)

  • 提交更新

    提交命令

    git commit
    

    该方式,在提交时会启动文本编辑器要求输入本次提交的说明.默认的编辑器可以修改,查看上方的git config --global core.editor命令

    git commit -v
    

    该方式,在提交时会在文本编辑器中放入diff的信息,以便以后查看

    git commit -m "discription"
    

    该方式,在输入命令时就把提交说明附加上去了

    git commit -a
    

    该方式,省略执行git add这个命令,自动将所有已经跟踪过的文件暂存起来然后提交

    NOTE:这里仅仅是省略了git add这个命令而已,文件实际上还是先进入暂存区,再进行提交的,链接中的翻译有歧义.对文件使用该命令时,一定要确保该文件是已跟踪状态

  • 移除文件

    • 从暂存区移除

    移除命令

    git rm
    

    同时删除处于跟踪状态和工作区中的文件

    注意:如果只是手动的删除目录下的文件,这时候后虽然文件不存在,但是文件跟踪状态清单中还记录着该文件名称,那么此时查看状态时,将会提示Changes not staged for commit文件被修改但未暂存.如果一不小心手动删除了,那么可以执行该命令,移除清单中该文件的跟踪状态即可

    git rm --cached filename
    

    删除处于跟踪状态的文件,保留工作区中的文件

    git rm -f
    

    当某个文件提交到暂存区后,再次被修改时,使用git rm命令删除将会报错,使用强制删除命令方可删除,该命令会从工作区和暂存区中同时删除文件. 为了避免误删的特殊指令

    使用场景:当你忘记添加 .gitignore 文件,不小心把一个很大的日志文件或一堆 .a 这样的编译生成文件添加到暂存区时,这一做法尤其有用

    图片演示

    这里写图片描述

  • 移动文件(实际是文件改名)

    移动文件命令

    git mv oldfile newfile  
    
  • 忽略文件

    仓库中总有一些文件不需要git去跟踪,那么这个时候我们就需要去忽略文件–创建.gitignore文件,配置忽略文件的规则.Android Studio在创建项目的时候会自动生成此文件,该文件存在时,git会自动根据该文件内的规则进行文件忽略.

    • .gitignore的格式规范

      • 所有空行或者以开头的行都会被 Git 忽略。可以用来做注释符
      • 可以使用标准的 glob 模式匹配
      • 匹配模式可以以(/)开头防止递归
      • 匹配模式可以以(/)结尾指定目录
      • 要忽略指定模式以外的文件或目录,可以在模式前加上惊叹号(!)取反
    • glob 模式是指 shell 所使用的简化了的正则表达式

      • 星号(*)匹配零个或多个任意字符
      • [abc] 匹配任何一个列在方括号中的字符(这个例子要么匹配一个 a,要么匹配一个 b,要么匹配一个 c)
      • 问号(?)只匹配一个任意字符
      • 如果在方括号中使用短划线分隔两个字符,表示所有在这两个字符范围内的都可以匹配(比如 [0-9] 表示匹配所有 0 到 9 的数字)
      • 使用两个星号() 表示匹配任意中间目录,比如a/*/z 可以匹配 a/z, a/b/z 或 a/b/c/z等

    GitHub 有一个十分详细的针对数十种项目及语言的 .gitignore 文件列表传送门

    Android .gitignore

    # Built application files
    *.apk
    *.ap_
    
    # Files for the ART/Dalvik VM
    *.dex
    
    # Java class files
    *.class
    
    # Generated files
    bin/
    gen/
    out/
    
    # Gradle files
    .gradle/
    build/
    
    # Local configuration file (sdk path, etc)
    local.properties
    
    # Proguard folder generated by Eclipse
    proguard/
    
    # Log Files
    *.log
    
    # Android Studio Navigation editor temp files
    .navigation/
    
    # Android Studio captures folder
    captures/
    
    # Intellij
    *.iml
    .idea/workspace.xml
    .idea/tasks.xml
    .idea/libraries
    
    # Keystore files
    *.jks
    
    # External native build folder generated in Android Studio 2.2 and later
    .externalNativeBuild
    
查看日志

查看日志命令

git log

日志内容非常的庞大,git提供了很多的指令去筛选日志的内容,详细指令

个人常用的自定义格式指令

git log --pretty=format:"%h - %an, %ar : %s" --graph

git log --pretty="%h - %ar - %s" --author=pan --since=2.month
撤销操作

撤销命令

git commit --amend

修改提交信息:当暂存区的文件在工作区中并未修改,那么使用此指令将只是修改了提交的信息而已,该命令后面同样可以添加-m "提交信息"来直接附加提交信息

git commit -m 'initial commit'
git add forgotten_file
git commit --amend

覆盖最近一次的操作:添加忘记的文件,执行此命令后,会覆盖最近一次的操作

取消暂存的文件

取消暂存文件的命令

git reset HEAD <file>
撤消对文件的修改

撤消对文件的修改指令

git checkout -- [file]

未提交到暂存区的文件进行撤销修改操作

作者:a254830856 发表于2016/11/25 22:20:13 原文链接
阅读:24 评论:0 查看评论

Unity3D的Generic和Legacy动画例子(C#脚本)

$
0
0

现在网上虽然讲解Unity3D动画的文章不少,但是却少很少带实际例子的。刚好我之前给美术做了个简单的例子,所以整理一下就共享给大家了。主要是做了两种动画,分别有例子,按空格可以改变动画。具体的制作过程不会非常详细(写的过程中,Generic被我写得有点详细了:),这里假设大家都是有一定的编程或者Unity基础的。

一、基础

  1. Unity3D 5.3.5可编译运行
  2. 脚本采用C#脚本编写
  3. 模型是FBX模型文件
    这里写图片描述
  4. 运行结果图
    这里写图片描述
  5. 性子急的或者有基础可以直接下载源码进行研究

    Unity3D动画例子源码

二、Generic动画

Generic是新的动画系统,它就是支持非人形的动画,建议使用它。但是它不能向Humanoid重定向动画。
1. 设置资源格式
选中Generic_Animation资源,在显示面板那里 Rig选择Generic
这里写图片描述
2. 创建AnimatorController
这里写图片描述
最后命名为GenericController放在Anim文件夹下面
在Swordman的目录下,选择Generic_Animation,然后把里面自己需要用到的动作添加到控制器去
这里写图片描述
先拖一个Idle进去,再拖Run进来,如下图
这里写图片描述
3. 关联Idle和Run动作
需要给这两个动作设置相关参数,方便我们程序调用
这里写图片描述
在Idle上右键- — 选择 Make Transition,然后控制鼠标箭头连到Run
同样的Run也关联到Idle
这里写图片描述
4. 增加动作之间的状态
动作配置好了之后,我们就要动作设置状态了,这里我们简单设置,增加一个布尔值 Move来表示移动和停止。
那么Move为true的时候,表示需要移动了,则是从Idle切换到Run,false则是从Run切换到Idle
首先在Animator的paramer那里增加一个布尔的Move参数(没有之前是 list is Empty)
这里写图片描述
点击旁边的+号,然后选择Bool类型,最后命名为Move
这里写图片描述
5. 设置动作之间的状态
选中Idle到Run的箭头,然后在旁边设置move属性为true
这里写图片描述
Run到Idle的箭头,设置Move属性为false
这里写图片描述
6. 添加人物模型
前面的基础动作配置好了,那么就在场景中增加模型吧
首先把Swordman_No_Animation拖放到舞台,然后命名为GenericAnimation
这里写图片描述
这里写图片描述
刚拖进入是背面的,那么设置一下Y-180。
把Unity自带的Animation给删除掉(这个其实就是Legacy动画要用到了,后面再讲)
然后给增加一个Animator组建,在Add Component那里搜索Animator,然后点击添加进去
这里写图片描述
同时给这个组件设置相关参数了。
Controller和Avatar分别给添加上,点击那个小圆圈会出现选项了。最后添加GenericAnimMgr脚本。
这里写图片描述
到这里,暂时需要直接在Unity设置的东西就差不多了,那么开始写C#脚本了
7. C#控制脚本
脚本比较简单,主要是通过Animator 组件来数值Move的true和false

using UnityEngine;
using System.Collections;
/// <summary>
/// 使用Animator控制角色动作的例子
/// </summary>
public class GenericAnimMgr : MonoBehaviour 
{
    private Animator animator;
    private bool isRun;
    // Use this for initialization
    void Start ()
    {
        animator = this.GetComponent<Animator>();
        //默认播放 idle
        animator.SetBool("Move", false);
    }

    // Update is called once per frame
    void Update () 
    {
        if (Input.GetKeyDown(KeyCode.Space))
        {

            if (isRun)
                //平滑切换
                animator.SetBool("Move", false);
            else
                animator.SetBool("Move", true);
            isRun = !isRun;
        }
     }
}

三、Legacy动画

legacy是老的动画系统,不多说。在引入Mecanim之前,Unity使用了自己的动画系统以便向后兼容,此系统仍然可用。 使用旧版动画系统主要是为了方便继续开发没有使用新Mecanim系统的项目。 然而,不推荐再在新项目中使用旧版动画系统。
1. 添加模型
步骤和Generic一样,把Swordman_No_Animation拖放到舞台,然后命名为LegacyAnimation
2.添加动作
直接在Animation组件里进行添加,不需要额外的其他控制器制作
Animation属性,是默认出现的动作,这里选择Idle
Animations表示这个模型有多少动作,这里选择2,同时增加Idle和Run
3.添加LegacyAnimMgr脚本
最后如何图
这里写图片描述
4. C#脚本

using System;
using UnityEngine;
using System.Collections;
/// <summary>
/// 动画控制器(基于Legacy的动画类型)
/// by sodaChen
/// </summary>
public class LegacyAnimMgr : MonoBehaviour 
{
    /** 动画控制器 **/
    private Animation anim;
    private bool isRun;
    void Start () 
    {
        //获取到动画组件
        anim = this.GetComponent<Animation>();
        //默认播放站立动作(如果组件有配置,这里则可以不用写)
        //anim.Play("Sword-Idle");  
    }

    // Update is called once per frame
    void Update () 
    {
        //通过不同的键盘控制来播放指定的动画
        if (Input.GetKeyDown(KeyCode.Space))
        {

            if (isRun)
                anim.CrossFade("Sword-Idle");
            else
                //角色跑起来
                anim.Play("Sword-Run");
            isRun = !isRun;
        }
        if (Input.GetKeyDown(KeyCode.LeftAlt))
        {
            //渐变到站立动作,有个缓冲动作。具体看API描述作用
            anim.CrossFade("Sword-Idle");
            //直接切换站立动作
            //anim.Play("Sword-Idle");  
        }
    }
}

四、总结

Unity做动画还是比较简单的,其实还有Humanoid动画,就是新的人形重定向动画系统。不过这里的制作还是比较依赖Unity,小游戏之类的,动画模型不多,还可以人力去做,去设置之间的关系。在实际开发手游项目过程,我们会采用一些脚本自动打包这些模型动作,以及自动绑定相关的脚本,这样维护性就高很多了。

作者:sujun10 发表于2016/11/25 22:27:43 原文链接
阅读:23 评论:0 查看评论

高德地图设置的onMarkerClick点击监听事件返回值

$
0
0

高德地图设置的onMarkerClick点击监听事件返回值

@Override
    public boolean onMarkerClick(Marker marker) {
        ...todo something
        return true;//false 点击marker marker会移动到地图中心,true则不会
    }
作者:u010378579 发表于2016/11/25 22:28:41 原文链接
阅读:36 评论:0 查看评论

智慧北京:顶部图片的加载

$
0
0

实现目的:加载顶部图片
实现步骤:先得到网络中图片资源所在的URL(此时得到的是children目录下的)。
1、给NewsListController设置数据和View:
a: Model数据:给图片需要设置ViewPager的适配器,adapter–>list。new一个adapter实现数据,进行处理数据,此时需要用到list,我们通过网络获取数据,(向服务器发送请求用httpUtils,要获得网络图片数据,我们可以采用BitmapUtils。)并将image添加到container里面。(下面会写明如何获取网络数据)
b: View视图:就是将布局文件加载进去(上次已经实现了)
xUtils用法
2、获取网络数据:
a: 用HiJson软件得到json树,然后新建一个类装这些数据。其中:采用public权限,字符串用String,数据用int或者long,array类型用list–>–>再新建一个类装E。
b: 然后在获取网络数据时,我们用到httpUtils采用send发送GET请求,获取到的数据再进行json解析并且将为PicPager设置适配器的方法也放在这里。
c: 实现适配器的初始化:
将图片加载进去:
采用BitmapUtils的display方法,通过图片资源的URI获取图片资源,并且播放。

3、 将NewsListController添加到NewsMenuController里面,
采用container.addView(view)。
实现结果:

HttpUtils模块:
支持同步,异步方式的请求; 支持大文件上传,上传大文件不会oom; 支持GET,POST,PUT,MOVE,COPY,DELETE,HEAD,OPTIONS,TRACE,CONNECT请求; 下载支持301/302重定向,支持设置是否根据Content-Disposition重命名下载的文件; 返回文本内容的请求(默认只启用了GET请求)支持缓存,可设置默认过期时间和针对当前请求的过期时间。
BitmapUtils模块:
加载bitmap的时候无需考虑bitmap加载过程中出现的oom和android容器快速滑动时候出现的图片错位等现象;
支持加载网络图片和本地图片;
内存管理使用lru算法,更好的管理bitmap内存;
可配置线程加载线程数量,缓存大小,缓存路径,加载显示动画等…

HttpUtils使用方法:

普通get方法

HttpUtils http = new HttpUtils();
http.send(HttpRequest.HttpMethod.GET,
    "http://www.lidroid.com",
    new RequestCallBack<String>(){
        @Override
        public void onLoading(long total, long current, boolean isUploading) {
            testTextView.setText(current + "/" + total);
        }

        @Override
        public void onSuccess(ResponseInfo<String> responseInfo) {
            textView.setText(responseInfo.result);
        }

        @Override
        public void onStart() {
        }

        @Override
        public void onFailure(HttpException error, String msg) {
        }
});

BitmapUtils 使用方法

BitmapUtils bitmapUtils = new BitmapUtils(this);

// 加载网络图片
bitmapUtils.display(testImageView, "http://bbs.lidroid.com/static/image/common/logo.png");

// 加载本地图片(路径以/开头, 绝对路径)
bitmapUtils.display(testImageView, "/sdcard/test.jpg");

// 加载assets中的图片(路径以assets开头)
bitmapUtils.display(testImageView, "assets/img/wallpaper.jpg");

// 使用ListView等容器展示图片时可通过PauseOnScrollListener控制滑动和快速滑动过程中时候暂停加载图片
listView.setOnScrollListener(new PauseOnScrollListener(bitmapUtils, false, true));
listView.setOnScrollListener(new PauseOnScrollListener(bitmapUtils, false, true, customListener));

NewsListController.java

package huaxa.it.zhihuidemo.base.newscenter;

import java.util.List;

import com.google.gson.Gson;
import com.lidroid.xutils.BitmapUtils;
import com.lidroid.xutils.HttpUtils;
import com.lidroid.xutils.ViewUtils;
import com.lidroid.xutils.exception.HttpException;
import com.lidroid.xutils.http.ResponseInfo;
import com.lidroid.xutils.http.callback.RequestCallBack;
import com.lidroid.xutils.http.client.HttpRequest.HttpMethod;
import com.lidroid.xutils.view.annotation.ViewInject;

import android.content.Context;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.ImageView.ScaleType;
import huaxa.it.zhihuidemo.R;
import huaxa.it.zhihuidemo.base.MenuController;
import huaxa.it.zhihuidemo.bean.NewsListBean;
import huaxa.it.zhihuidemo.bean.NewsCenterBean.NewsBean;
import huaxa.it.zhihuidemo.bean.NewsListBean.NewsListTopNewsBean;
import huaxa.it.zhihuidemo.utils.Constans;

/**
 * @项目名: ZhiHuiDemo
 * @包名: huaxa.it.zhihuidemo.base.newscenter
 * @类名: NewsListController
 * @创建者: 黄夏莲
 * @创建时间: 2016年11月24日 ,下午2:40:30
 * 
 * @描述: TODO
 */
public class NewsListController extends MenuController
{

    protected static final String       TAG = "NewsListController";

    @ViewInject(R.id.news_list_pic_pager)
    private ViewPager                   mNewsListPicPager;

    @ViewInject(R.id.news_list_point_container)
    private ViewPager                   mNewsListPointContainer;

    @ViewInject(R.id.news_list_tv_title)
    private ViewPager                   mNewsListTvTitle;

    private String                      mUrl;

    private List<NewsListTopNewsBean>   newsPicData;

    public BitmapUtils                  mBitmapUtils;

    public NewsListController(Context context, NewsBean data)
    {
        super(context);
        this.mUrl = data.url;
    }

    @Override
    protected View initView(Context context)
    {
        View view = View.inflate(context, R.layout.news_list_pager, null);
        // ViewUtils注入
        ViewUtils.inject(this, view);
        mBitmapUtils = new BitmapUtils(mContext);
        return view;
    }

    @Override
    public void initData()
    {

        String url = Constans.Base_URI + mUrl;
        // 初始化ViewPager对应的数据
        HttpUtils httpUtils = new HttpUtils();
        httpUtils.send(HttpMethod.GET, url, new RequestCallBack<String>()
        {

            @Override
            public void onSuccess(ResponseInfo<String> responseInfo)
            {
                // TODO Auto-generated method stub
                String result = responseInfo.result;
                // 处理数据
                processData(result);
            }

            @Override
            public void onFailure(HttpException error, String msg)
            {
                // TODO Auto-generated method stub

            }

        });

    }

    // 数据处理
    private void processData(String json)
    {
        // json解析数据
        Gson gson = new Gson();
        NewsListBean bean = gson.fromJson(json, NewsListBean.class);
        // picture的数据
        newsPicData = bean.data.topnews;
        // 校验下
        // Log.i(TAG, bean.data.news.get(0).title+"");
        // 给ViewPager初始化数据
        mNewsListPicPager.setAdapter(new NewsTopPicAdapter());

    }

    class NewsTopPicAdapter extends PagerAdapter
    {

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

        @Override
        public boolean isViewFromObject(View arg0, Object arg1)
        {
            // TODO Auto-generated method stub
            return arg0 == arg1;
        }

        @Override
        public Object instantiateItem(ViewGroup container, int position)
        {
            ImageView iv = new ImageView(mContext);
            // 图片满屏
            iv.setScaleType(ScaleType.FIT_XY);
            // 给iv设置图片,设置默认值
            iv.setImageResource(R.drawable.home_scroll_default);
            // 设置网络图片
            String uri = newsPicData.get(position).topimage;
            mBitmapUtils.display(iv, uri);
            container.addView(iv);
            return iv;
        }

        @Override
        public void destroyItem(ViewGroup container, int position, Object object)
        {
            container.removeView((View) object);
        }
    }

}

NewsListBean.java
网络数据

package huaxa.it.zhihuidemo.bean;

import java.util.List;

import android.R.string;

/**
 * @项目名: ZhiHuiDemo
 * @包名: huaxa.it.zhihuidemo.bean
 * @类名: NewsListBean
 * @创建者: 黄夏莲
 * @创建时间: 2016年11月25日 ,下午1:51:00
 * 
 * @描述: TODO
 */
public class NewsListBean
{
    public Object   Object;
    public String   retcode;

    public class NewsListData
    {
        public String                       countcommenturl;
        public String                       more;
        public List<NewsListNewsBean>       news;
        public String                       title;
        public List<NewsListTopicBean>      topic;
        public List<NewsListTopNewsBean>    topnews;
    }

    public class NewsListNewsBean
    {
        public Boolean  comment;
        public String   commentlist;
        public String   commenturl;
        public long     id;
        public String   listimage;
        public String   pubdate;
        public String   title;
        public String   type;
        public String   url;
    }

    public class NewsListTopicBean
    {
        public String   description;
        public long     id;
        public String   listimage;
        public int      sort;
        public String   title;
        public String   url;
    }

    public class NewsListTopNewsBean
    {
        public Boolean  comment;
        public String   commentlist;
        public String   commenturl;
        public long     id;
        public String   pubdate;
        public String   title;
        public String   topimage;
        public String   type;
        public String   url;
    }
}

NewsMenuController.java
将原先的假数据(TextView)改为NewsListController的View,也就是修改Title下面的ViewPager下的内容。

public Object instantiateItem(ViewGroup container, int position)
        {
//          TextView text = new TextView(mContext);
//          text.setText(mChildren.get(position).title);
//          text.setTextColor(Color.RED);
//          text.setTextSize(24);
//          text.setGravity(Gravity.CENTER);
//          container.addView(text);
//          System.out.println(mChildren.size());
            NewsBean newsBean = mChildren.get(position);

            NewsListController newsListController = new NewsListController(mContext,newsBean);
            //获取View
            View rootView = newsListController.getRootView();
            //将View添加到container里面
            container.addView(rootView);
            //Controller初始化数据
            newsListController.initData();

            return rootView;
        }
作者:u014299265 发表于2016/11/25 23:09:02 原文链接
阅读:40 评论:0 查看评论

Android-权限详解

$
0
0

每款 Android 应用都在访问受限的沙盒中运行。如果应用需要使用其沙盒外的资源或信息,则必须请求相应权限。您可以在应用清单中列出相应的权限,声明应用需要此权限。

根据权限的敏感性,系统可能会自动授予权限,或者需要由设备用户对请求进行许可。例如,如果您的应用请求打开设备手电筒的权限,系统将自动授予该权限。但如果您的应用需要读取用户联系人,系统会要求用户授权。用户需要在安装应用(运行 Android 5.1 和更低版本的设备)或者运行应用(运行 Android 6.0 和更高版本的设备)时授予权限,具体取决于平台版本。

确定您的应用需要哪些权限

开发应用时,您应注意应用何时使用需要权限的功能。通常,在使用并非由自身创建的信息资源、执行会影响设备或其他应用行为的操作时,应用都需要获得相应的权限。例如,如果应用需要访问互联网、使用设备摄像头或者打开或关闭 WLAN,应用需要获得相应的权限。

您的应用仅需要获得其直接执行的操作的权限。如果应用请求另一应用执行任务或提供信息,则不需要获得相应权限。例如,如果您的应用需要读取用户的地址簿,则需要 READ_CONTACTS 权限。但如果您的应用使用一个 intent 从用户的“联系人”应用中请求信息,则您的应用不需要任何权限,但“联系人”应用确实需要该权限。

向manifest中添加权限

要声明您的应用需要权限,请将 元素置于您的应用清单中,作为顶级 元素的子项。例如,需要发送短信的应用可在清单中添加以下代码行:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.example.snazzyapp">

    <uses-permission android:name="android.permission.SEND_SMS"/>

    <application ...>
        ...
    </application>

</manifest>

系统在您声明权限之后的行为取决于权限的敏感性。如果权限不影响用户隐私权,系统会自动授权。如果权限可能涉及对敏感用户信息的访问,系统将要求用户审批请求。

在运行时获取权限

从 Android 6.0(API 级别 23)开始,用户开始在应用运行时向其授予权限,而不是在应用安装时授予。此方法可以简化应用安装过程,因为用户在安装或更新应用时不需要授予权限。它还让用户可以对应用的功能进行更多控制;例如,用户可以选择为相机应用提供相机访问权限,而不提供设备位置的访问权限。用户可以随时进入应用的“Settings”屏幕调用权限。

系统权限分为两类:正常权限和危险权限:

  • 正常权限不会直接给用户隐私权带来风险。如果您的应用在其清单中列出了正常权限,系统将自动授予该权限。
  • 危险权限会授予应用访问用户机密数据的权限。如果您的应用在其清单中列出了正常权限,系统将自动授予该权限。如果您列出了危险权限,则用户必须明确批准您的应用使用这些权限。

在所有版本的 Android 中,您的应用都需要在其应用清单中同时声明它需要的正常权限和危险权限,如声明权限中所述。不过,该声明的影响因系统版本和应用的目标 SDK 级别的不同而有所差异:

  • 如果设备运行的是 Android 5.1 或更低版本(或者应用的目标 SDK 为 22或更低):如果您在清单中列出了危险权限,则用户必须在安装应用时授予此权限;如果他们不授予此权限,系统根本不会安装应用。
  • 如果设备运行的是 Android 6.0 或更高版本(或者应用的目标 SDK 为 23或更高):应用必须在清单中列出权限,并且它必须在运行时请求其需要的每项危险权限。用户可以授予或拒绝每项权限,且即使用户拒绝权限请求,应用仍可以继续运行有限的功能。

注:从 Android 6.0(API 级别 23)开始,用户可以随时从任意应用调用权限,即使应用面向较低的 API 级别也可以调用。无论您的应用面向哪个 API 级别,您都应对应用进行测试,以验证它在缺少需要的权限时行为是否正常。

本课将介绍如何使用 Android 支持库来检查和请求权限。Android 框架从 Android 6.0(API 级别 23)开始提供类似方法。不过,使用支持库更简单,因为在调用方法前,您的应用不需要检查它在哪个版本的 Android 上运行。

检查权限

如果您的应用需要危险权限,则每次执行需要这一权限的操作时您都必须检查自己是否具有该权限。用户始终可以自由调用此权限,因此,即使应用昨天使用了相机,它不能假设自己今天仍具有该权限。

要检查您是否具有某项权限,请调用 ContextCompat.checkSelfPermission() 方法。例如,以下代码段显示了如何检查 Activity 是否具有在日历中进行写入的权限:

// Assume thisActivity is the current activity
int permissionCheck = ContextCompat.checkSelfPermission(thisActivity,
        Manifest.permission.WRITE_CALENDAR);

如果应用具有此权限,方法将返回 PackageManager.PERMISSION_GRANTED,并且应用可以继续操作。如果应用不具有此权限,方法将返回 PERMISSION_DENIED,且应用必须明确向用户要求权限。

请求权限

如果您的应用需要应用清单中列出的危险权限,那么,它必须要求用户授予该权限。Android 为您提供了多种权限请求方式。调用这些方法将显示一个标准的 Android 对话框,不过,您不能对它们进行自定义
1
图 1. 提示用户授予或拒绝权限的系统对话框。

解释应用需要权限的原因

在某些情况下,您可能需要帮助用户了解您的应用为什么需要某项权限。例如,如果用户启动一个摄影应用,用户对应用要求使用相机的权限可能不会感到吃惊,但用户可能无法理解为什么此应用想要访问用户的位置或联系人。在请求权限之前,不妨为用户提供一个解释。请记住,您不需要通过解释来说服用户;如果您提供太多解释,用户可能发现应用令人失望并将其移除。

您可以采用的一个方法是仅在用户已拒绝某项权限请求时提供解释。如果用户继续尝试使用需要某项权限的功能,但继续拒绝权限请求,则可能表明用户不理解应用为什么需要此权限才能提供相关功能。对于这种情况,比较好的做法是显示解释。

为了帮助查找用户可能需要解释的情形,Android 提供了一个实用程序方法,即 shouldShowRequestPermissionRationale()。如果应用之前请求过此权限但用户拒绝了请求,此方法将返回 true。

注:如果用户在过去拒绝了权限请求,并在权限请求系统对话框中选择了 Don’t ask again 选项,此方法将返回 false。如果设备规范禁止应用具有该权限,此方法也会返回 false。

请求你需要的权限

如果应用尚无所需的权限,则应用必须调用一个 requestPermissions() 方法,以请求适当的权限。应用将传递其所需的权限,以及您指定用于识别此权限请求的整型请求代码。此方法异步运行:它会立即返回,并且在用户响应对话框之后,系统会使用结果调用应用的回调方法,将应用传递的相同请求代码传递到 requestPermissions()。

以下代码可以检查应用是否具备读取用户联系人的权限,并根据需要请求该权限:

// Here, thisActivity is the current activity
if (ContextCompat.checkSelfPermission(thisActivity,
                Manifest.permission.READ_CONTACTS)
        != PackageManager.PERMISSION_GRANTED) {

    // Should we show an explanation?
    if (ActivityCompat.shouldShowRequestPermissionRationale(thisActivity,
            Manifest.permission.READ_CONTACTS)) {

        // Show an expanation to the user *asynchronously* -- don't block
        // this thread waiting for the user's response! After the user
        // sees the explanation, try again to request the permission.

    } else {

        // No explanation needed, we can request the permission.

        ActivityCompat.requestPermissions(thisActivity,
                new String[]{Manifest.permission.READ_CONTACTS},
                MY_PERMISSIONS_REQUEST_READ_CONTACTS);

        // MY_PERMISSIONS_REQUEST_READ_CONTACTS is an
        // app-defined int constant. The callback method gets the
        // result of the request.
    }
}

注:当您的应用调用 requestPermissions() 时,系统将向用户显示一个标准对话框。您的应用无法配置或更改此对话框。如果您需要为用户提供任何信息或解释,您应在调用 requestPermissions() 之前进行,如解释应用为什么需要权限中所述。

处理权限请求响应

当应用请求权限时,系统将向用户显示一个对话框。当用户响应时,系统将调用应用的 onRequestPermissionsResult() 方法,向其传递用户响应。您的应用必须替换该方法,以了解是否已获得相应权限。回调会将您传递的相同请求代码传递给 requestPermissions()。例如,如果应用请求 READ_CONTACTS 访问权限,则它可能采用以下回调方法:

@Override
public void onRequestPermissionsResult(int requestCode,
        String permissions[], int[] grantResults) {
    switch (requestCode) {
        case MY_PERMISSIONS_REQUEST_READ_CONTACTS: {
            // If request is cancelled, the result arrays are empty.
            if (grantResults.length > 0
                && grantResults[0] == PackageManager.PERMISSION_GRANTED) {

                // permission was granted, yay! Do the
                // contacts-related task you need to do.

            } else {

                // permission denied, boo! Disable the
                // functionality that depends on this permission.
            }
            return;
        }

        // other 'case' lines to check for other
        // permissions this app might request
    }
}

系统显示的对话框说明了您的应用需要访问的权限组;它不会列出具体权限。例如,如果您请求 READ_CONTACTS 权限,系统对话框只显示您的应用需要访问设备的联系人。用户只需要为每个权限组授予一次权限。如果您的应用请求该组中的任何其他权限(已在您的应用清单中列出),系统将自动授予应用这些权限。当您请求此权限时,系统会调用您的 onRequestPermissionsResult() 回调方法,并传递 PERMISSION_GRANTED,如果用户已通过系统对话框明确同意您的权限请求,系统将采用相同方式操作。

注:您的应用仍需要明确请求其需要的每项权限,即使用户已向应用授予该权限组中的其他权限。此外,权限分组在将来的 Android 版本中可能会发生变化。您的代码不应依赖特定权限属于或不属于相同组这种假设。

例如,假设您在应用清单中列出了 READ_CONTACTS 和 WRITE_CONTACTS。如果您请求 READ_CONTACTS 且用户授予了此权限,那么,当您请求 WRITE_CONTACTS 时,系统将立即授予您该权限,不会与用户交互。

如果用户拒绝了某项权限请求,您的应用应采取适当的操作。例如,您的应用可能显示一个对话框,解释它为什么无法执行用户已经请求但需要该权限的操作。

当系统要求用户授予权限时,用户可以选择指示系统不再要求提供该权限。这种情况下,无论应用在什么时候使用 requestPermissions() 再次要求该权限,系统都会立即拒绝此请求。系统会调用您的 onRequestPermissionsResult() 回调方法,并传递 PERMISSION_DENIED,如果用户再次明确拒绝了您的请求,系统将采用相同方式操作。这意味着当您调用 requestPermissions() 时,您不能假设已经发生与用户的任何直接交互。

获取运行时权限最佳做法

应用如果一味要求用户提供授权,可能会让用户无所适从。如果用户发现应用难以使用,或者担心应用会滥用其信息,他们可能不愿意使用该应用,甚至会将其完全卸载。以下最佳做法有助于避免此类糟糕的用户体验。

考虑使用 intent

许多情况下,您可以使用以下两种方式之一来让您的应用执行某项任务。您可以将应用设置为要求提供权限才能执行操作。或者,您可以将应用设置为使用 intent,让其他应用来执行任务。

例如,假设应用需要使用设备相机才能够拍摄照片。应用可以请求 CAMERA 权限,以便允许其直接访问相机。然后,应用将使用 相机 API 控制相机并拍摄照片。利用此方法,您的应用能够完全控制摄影过程,并支持您将相机 UI 整合至应用中。

不过,如果您无需此类完全控制,则可以使用 ACTION_IMAGE_CAPTURE intent 来请求图像。发送该 intent 时,系统会提示用户选择相机应用(如果没有默认相机应用)。用户使用选定的相机应用拍摄照片,该相机应用会将照片返回给应用的 onActivityResult() 方法。

同样,如果您需要拨打电话、访问用户的联系人或要执行其他操作,可以通过创建适当的 intent 来完成,或者您也可以请求相应的权限并直接访问相应的对象。每种方法各有优缺点。

如果使用权限:

  • 您的应用可在您执行操作时完全控制用户体验。不过,如此广泛的控制会增加任务的复杂性,因为您需要设计适当的 UI。
  • 系统会在运行或安装应用时各提示用户提供一次权限(具体取决于用户的 Android 版本)。之后,应用即可执行操作,不再需要用户进行其他交互。不过,如果用户不授予权限(或稍后撤销权限),您的应用将根本无法执行操作。

如果使用 intent:

  • 您无需为操作设计 UI。处理 intent 的应用将提供 UI。不过,这意味着您无法控制用户体验。用户可能与您从未见过的应用交互。

  • 如果用户没有适用于操作的默认应用,则系统会提示用户选择一款应用。如果用户未指定默认处理程序,则他们每次执行此操作时都必须处理一个额外对话框。

仅要求你需要的权限

每次您要求权限时,实际上是在强迫用户作出决定。您应尽量减少提出这些请求的次数。如果用户运行的是 Android 6.0(API 级别 23)或更高版本,则每次用户尝试要求提供权限的新应用功能时,应用都必须中断用户的操作并发起权限请求。如果用户运行的是较早版本的 Android,则在安装应用时需要为应用的每一权限请求给予授权;如果列表过长或看起来不合适,用户可能会决定不安装该应用。为此,您应尽量减少应用需要的权限数。

例如,很多情况下应用可以通过使用 intent 来避免请求权限。如果某项功能并非应用的核心功能,不妨考虑将相关工作交给其他应用来执行,如考虑使用 intent 中所述。

不要让用户感到无所适从

如果用户运行的是 Android 6.0(API 级别 23)或更高版本,则用户必须在应用运行时为其授权。如果您的应用一次要求用户提供多项权限,用户可能会感到无所适从并因此退出应用。您应根据需要请求权限。

某些情况下,一项或多项权限可能是应用所必需的。在这种情况下,合理的做法是,在应用启动之后立即要求提供这些权限。例如,如果您运行摄影应用,应用需要访问设备的相机。在用户首次启动应用时,他们不会对提供相机使用权限的要求感到惊讶。但是,如果同一应用还具备与用户联系人共享照片的功能,您不应在应用首次启动时要求用户提供 READ_CONTACTS 权限,而应等到用户尝试使用“共享”功能之后,再要求提供该权限。

如果应用提供了教程,则合理的做法是,在教程结束时请求提供应用的必要权限。

解释需要权限的原因

系统在您调用 requestPermissions() 时显示的权限对话框将说明应用需要的权限,但不会解释为何需要这些权限。某些情况下,用户可能会感到困惑。因此,最好在调用 requestPermissions() 之前向用户解释应用需要相应权限的原因。

例如,摄影应用可能需要使用位置服务,以便能够为照片添加地理标签。通常,用户可能不了解照片能够包含位置信息,并且对摄影应用想要了解具体位置感到不解。因此在这种情况下,应用最好在调用 requestPermissions() 之前告知用户此功能的相关信息。

告知用户的一种办法是将这些请求纳入应用教程。这样,教程可以依次显示应用的每项功能,并在显示每项功能时解释需要哪些相应的权限。例如,摄影应用的教程可以演示其“与您的联系人共享照片”功能,然后告知用户需要为应用授予权限才能查看用户的联系人。然后,应用可以调用 requestPermissions(),要求用户提供该访问权限。当然,并非所有用户都会按照教程操作,因此您仍需在应用的正常操作期间检查和请求权限。

测试两种权限模式

从 Android 6.0(API 级别 23)开始,用户是在运行时而不是在应用安装时授予或撤销应用权限。因此,您应在多种不同条件下测试应用。在低于 Android 6.0 的版本中,您可以认为如果应用得到运行,它就可以得到在应用清单中声明的全部权限。在新的权限模式中,这一推断不再成立。

以下提示可帮助您识别在运行 API 级别 23 或更高级别的设备上与权限有关的代码问题:

  • 识别应用的当前权限和相关的代码路径。
  • 在各种受权限保护的服务和数据中测试用户流程。
  • 使用授予或撤销权限的各种组合进行测试。例如,相机应用可能会在清单中列出 CAMERA、READ_CONTACTS 和
    ACCESS_FINE_LOCATION。您应在测试应用时逐一打开和关闭这些权限,确保应用可以妥善处理所有权限配置。请记住,自
    Android 6.0 起,用户可以打开或关闭任何应用的权限,即使面向 API 级别 22 或更低级别的应用也是如此。
  • 使用 adb 工具从命令行管理权限:
按组列出权限和状态:
$ adb shell pm list permissions -d -g
授予或撤销一项或多项权限:
$ adb shell pm [grant|revoke] <permission-name> ...
  • 针对使用权限的服务对应用进行分析。
作者:jimo_lonely 发表于2016/11/25 23:46:10 原文链接
阅读:11 评论:0 查看评论

android M Launcher之LauncherModel (二)

$
0
0

上一篇我们通过LauncherModel的创建 ,实例化,以及与LauncherModel之间的沟通方式。初步了解了LauncherModel一些功能及用法,如果对LauncherModel一系列初始化动画还不了解的可以看

android M Launcher之LauncherModel (一)

好了 接下来我们继续分析,大家都知道 LauncherModel是Launcher的数据中心,但是数据中心的数据是怎么加载出来的呢,这里就要说到LoaderTask了,它是LauncherModel的核心任务。

1、LoaderTask的定义、属性及构造

要了解一个类的功能和作用,我们通常从该类的定义及其定义的成员变量开始,就好比了解一个人最先看到的肯定是他的外貌了。

    private class LoaderTask implements Runnable {
        private Context mContext;
        boolean mIsLoadingAndBindingWorkspace;
        private boolean mStopped;
        boolean mLoadAndBindStepFinished;
    }

由定义可以看出LoaderTask是一个实现Run那边了接口的类,我们都知道Run那边了是一个任务,它可以被抛到线程中执行,也可以被抛到进程的处理器中执行,通过实现Runnable接口来实现多线程以及线程间的通信是比较常用的做法。

LoaderTask的执行需要Launcher应用程序提供必要的支持,而外界也需要知道Loaderask的执行状态,因此在LoaderTask中,以成员变量的方式保存了LoaderTask执行所必需的支持以及自身相关的状态。

  • mIsLoadingAndBindingWorkspace :它是LoaderTask执行动作的指示性变量,当正在加载和绑定桌面数据时它为true,当动作执行完成后它为false。

  • mStopped :标示整个加载任务是否已经被强制停止,LoaderTask对外提供了一个停止加载的接口,如果外部通过这个接口来停止一个加载任务,那么它将为true,默认是false

  • mLoadAndBindStepFinished : 通过它可以知道整个加载任务所涉及的动作已经执行完毕

2、LoaderTask的run接口实现
LoaderTask是一个Run那边了接口的实现,每一个Runnable接口的实现都需要实现run方法,LoaderTask也不例外,当LoaderTask作为一个任务在某一个线程中运行时,实际就是这个线程调用了它的run方法

 public void run() {
            synchronized (mLock) {
                if (mStopped) {
                    return;
                }
                mIsLoaderTaskRunning = true;
            }
            // Optimize for end-user experience: if the Launcher is up and // running with the
            // All Apps interface in the foreground, load All Apps first. Otherwise, load the
            // workspace first (default).
            keep_running:
            {
                if (DEBUG_LOADERS) Log.d(TAG, "step 1: loading workspace");
                loadAndBindWorkspace();

                if (mStopped) {
                    break keep_running;
                }

                waitForIdle();

                // second step
                if (DEBUG_LOADERS) Log.d(TAG, "step 2: loading all apps");
                loadAndBindAllApps();
            }

            // Clear out this reference, otherwise we end up holding it until all of the
            // callback runnables are done.
            mContext = null;

            synchronized (mLock) {
                // If we are still the last one to be scheduled, remove ourselves.
                if (mLoaderTask == this) {
                    mLoaderTask = null;
                }
                mIsLoaderTaskRunning = false;
                mHasLoaderCompletedOnce = true;
            }
        }

这里主要做了两件事
1、loading workspace
2、loading all apps
其他的就是一些成员变量的赋值。

首先loading workspace 我们画流程图来看:
这里写图片描述

private void loadAndBindWorkspace() {
            mIsLoadingAndBindingWorkspace = true;

            // Load the workspace
            if (DEBUG_LOADERS) {
                Log.d(TAG, "loadAndBindWorkspace mWorkspaceLoaded=" + mWorkspaceLoaded);
            }

            if (!mWorkspaceLoaded) {
                loadWorkspace();
                synchronized (LoaderTask.this) {
                    if (mStopped) {
                        return;
                    }
                    mWorkspaceLoaded = true;
                }
            }

            // Bind the workspace
            bindWorkspace(-1);
        }

结合上面代码 可以发现 它又分为两步
1、生成桌面数据loadWorkspace;
2、绑定桌面配置数据bindWorkspace。

loadWorkspace

Launcher桌面的数据主要包括来自Launcher数据库的各种表,loadWorkspace方法将负责从这些数据库表中读取数据并转译为Launcher桌面项的数据结构。
这个代码比较多同样先上流程图。
这里写图片描述

  • 获取系统服务一节设备属性并调整桌面页顺序

在加载数据桌面数据前,loadWorkspace 方法必需获取到必要的支持,因为loadWorkspace方法主要处理应用程序包的相关信息,所以首先要获取包管理服务,其次
需要确定快捷方式或者桌面小部件的位置,因此它还需要获取屏幕的宽和高代码如下:

  final Context context = mContext;
            final ContentResolver contentResolver = context.getContentResolver();
            //获取包管理服务,并查询当前是否处于安全模式
            final PackageManager manager = context.getPackageManager();
            final boolean isSafeMode = manager.isSafeMode();
            //获取Launcher定制的应用程序管理接口
            final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
            final boolean isSdCardReady = context.registerReceiver(null,
                    new IntentFilter(StartupReceiver.SYSTEM_READY)) != null;
            //获取桌面有多少行,多少列
            LauncherAppState app = LauncherAppState.getInstance();
            InvariantDeviceProfile profile = app.getInvariantDeviceProfile();
            int countX = profile.numColumns;
            int countY = profile.numRows;

            if (MigrateFromRestoreTask.ENABLED && MigrateFromRestoreTask.shouldRunTask(mContext)) {
                long migrationStartTime = System.currentTimeMillis();
                Log.v(TAG, "Starting workspace migration after restore");
                try {
                    //根据实际情况调整桌面页的顺序
                    MigrateFromRestoreTask task = new MigrateFromRestoreTask(mContext);
                    // Clear the flags before starting the task, so that we do not run the task
                    // again, in case there was an uncaught error.
                    MigrateFromRestoreTask.clearFlags(mContext);
                    task.execute();
                } catch (Exception e) {
                    Log.e(TAG, "Error during grid migration", e);

                    // Clear workspace.
                    mFlags = mFlags | LOADER_FLAG_CLEAR_WORKSPACE;
                }
                Log.v(TAG, "Workspace migration completed in "
                        + (System.currentTimeMillis() - migrationStartTime));
            }
  • 根据输入的标志对数据进行必要的预处理
    在不同的场景下,LoadWorkspace可能要对需要处理的数据来源进行必要的处理,这取决于mFlags 标志,代码如下:
 //如果mFlags中包含LOADER_FLAG_CLEAR_WORKSPACE,则清理原有的数据
            if ((mFlags & LOADER_FLAG_CLEAR_WORKSPACE) != 0) {
                Launcher.addDumpLog(TAG, "loadWorkspace: resetting launcher database", true);
                LauncherAppState.getLauncherProvider().deleteDatabase();
            }

            // Make sure the default workspace is loaded
            //确保默认数据得以加载
            Launcher.addDumpLog(TAG, "loadWorkspace: loading default favorites", false);
            LauncherAppState.getLauncherProvider().loadDefaultFavoritesIfNecessary();
  • 清理历史数据并准备必要数据
    在每次加载桌面数据时,需要对这些数据区域进行清理 这个平时我们往listview中添加数据前要清理一样的道理, 它主要在clearSBgDataStructures 中完成
         /**
         * Clears all the sBg data structures
         */
        private void clearSBgDataStructures() {
            synchronized (sBgLock) {
                sBgWorkspaceItems.clear();//清理桌面项列表
                sBgAppWidgets.clear();//清理桌面快捷方式列表
                sBgFolders.clear();//清理文件夹数据列表
                sBgItemsIdMap.clear();//清理桌面项字段
                sBgWorkspaceScreens.clear();//清理桌面页记录列表
            }
        }

完成清理后loadWorkspace方法需要知道当前设备中安装了多少应用程序,除了这些外,在加载桌面桌面数据的过程中会产出一些过程数据,他们保持在其他区域代码如下:

final HashMap<String, Integer> installingPkgs = PackageInstallerCompat
                        .getInstance(mContext).updateAndGetActiveSessionCache();
                sBgWorkspaceScreens.addAll(loadWorkspaceScreensDb(mContext));

                final ArrayList<Long> itemsToRemove = new ArrayList<Long>();
                final ArrayList<Long> restoredRows = new ArrayList<Long>();

有了上面这些基础后 ,接下来就开始加载数据了。

  • 查询Favorites表数据库并准备桌面占用情况标志映射
    final Uri contentUri = LauncherSettings.Favorites.CONTENT_URI;
                if (DEBUG_LOADERS) Log.d(TAG, "loading model from " + contentUri);
                final Cursor c = contentResolver.query(contentUri, null, null, null, null);

                // +1 for the hotseat (it can be larger than the workspace)
                // Load workspace in reverse order to ensure that latest items are loaded first (and
                // before any earlier duplicates)
                final LongArrayMap<ItemInfo[][]> occupied = new LongArrayMap<>();

  • 从Cursor中获取桌面项类型itemType等数据项
  int itemType = c.getInt(itemTypeIndex);//桌面项类型
                            boolean restored = 0 != c.getInt(restoredIndex);//是否已经恢复
                            boolean allowMissingTarget = false;
  • 应用程序入口及其他入口快捷方式的处理

在这里Launcher需要处理桌面上快捷方式的记录,将其转换成Launcher可以处理的对象。如下图
这里写图片描述

  • 处理文件夹记录处理

    桌面文件只需要处理好文件夹的标题,位置以及所处的容器即可。

  • 桌面小部件记录处理

    这里或处理桌面快捷方式类似,可以参考处理桌面快捷方式的流程来看处理桌面小部件。

  • 从数据库中清理需要删除的数据项
    通过以上处理步骤,可能产生依稀需要删除的记录如下:

 if (itemsToRemove.size() > 0) {
                    // Remove dead items
                    contentResolver.delete(LauncherSettings.Favorites.CONTENT_URI,
                            Utilities.createDbSelectionQuery(
                                    LauncherSettings.Favorites._ID, itemsToRemove), null);
                    if (DEBUG_LOADERS) {
                        Log.d(TAG, "Removed = " + Utilities.createDbSelectionQuery(
                                LauncherSettings.Favorites._ID, itemsToRemove));
                    }

                    // Remove any empty folder
                    for (long folderId : LauncherAppState.getLauncherProvider()
                            .deleteEmptyFolders()) {
                        sBgWorkspaceItems.remove(sBgFolders.get(folderId));
                        sBgFolders.remove(folderId);
                        sBgItemsIdMap.remove(folderId);
                    }
                }

                // Sort all the folder items and make sure the first 3 items are high resolution.
                for (FolderInfo folder : sBgFolders) {
                    Collections.sort(folder.contents, Folder.ITEM_POS_COMPARATOR);
                    int pos = 0;
                    for (ShortcutInfo info : folder.contents) {
                        if (info.usingLowResIcon) {
                            info.updateIcon(mIconCache, false);
                        }
                        pos++;
                        if (pos >= FolderIcon.NUM_ITEMS_IN_PREVIEW) {
                            break;
                        }
                    }
                }
  • 置恢复状态为以恢复
    有些记录可能来自备份与恢复的过程,如果当前处理的记录属于这种,并且已经得到完整的恢复那么这些记录ID都会被缓存在restoredRows中,在处理往需要删除的记录后,就要将restoredRows中指定记录恢复状态置为0.
  if (restoredRows.size() > 0) {
                    // Update restored items that no longer require special handling
                    ContentValues values = new ContentValues();
                    values.put(LauncherSettings.Favorites.RESTORED, 0);
                    contentResolver.update(LauncherSettings.Favorites.CONTENT_URI, values,
                            Utilities.createDbSelectionQuery(
                                    LauncherSettings.Favorites._ID, restoredRows), null);
                }
  • 处理安装在SDCARD上的应用程序
  if (!isSdCardReady && !sPendingPackages.isEmpty()) {
                    context.registerReceiver(new AppsAvailabilityCheck(),
                            new IntentFilter(StartupReceiver.SYSTEM_READY),
                            null, sWorker);
                }

当sPendingPackages中有了记录并且SDCARD没有挂载好的时候,Launcher注册相关的广播接收器等待SDCARD的挂载完成。

好了 LoadWorkspace分析就完成了,真心不容易呀。

再接再厉 我们看bindWorkspace

上面我们已经准备好了需要加载的数据,那么接下来需要做的就是将这些数据发送到Launcher,让它将这些数据转化为一个个可以显示的view,这个过程由bindWorkspace来完成。

private void bindWorkspace(int synchronizeBindPage)
  • synchronizeBindPage 如果它的值小于0说明需要对所有桌面页进行刷新,如果大于等于0,则对指定页进行刷新。

在画图看下bindWorkspace的流程。发现Xmind用的越来越6了 O(∩_∩)O哈哈~
这里写图片描述

在加载桌面数据的过程中,已经把需要加载的数据分门别类的放在不同的数据缓冲区。由于LauncherModel的LoaderTask可以因为LauncherModel执行不同的任务而被多次实例化,这将会引起缓冲区数据共享问题,为了解决这个问题每次绑定数据的时候都要临时将数据缓冲区的数据备份,代码如下:

 // Save a copy of all the bg-thread collections
            //桌面数据项列表
            ArrayList<ItemInfo> workspaceItems = new ArrayList<ItemInfo>();
            //桌面小部件数据项列表
            ArrayList<LauncherAppWidgetInfo> appWidgets =
                    new ArrayList<LauncherAppWidgetInfo>();
            //经过排序的桌面页索引列表
            ArrayList<Long> orderedScreenIds = new ArrayList<Long>();

            final LongArrayMap<FolderInfo> folders;
            final LongArrayMap<ItemInfo> itemsIdMap;
            //缓冲区数据复制
            synchronized (sBgLock) {
                workspaceItems.addAll(sBgWorkspaceItems);
                appWidgets.addAll(sBgAppWidgets);
                orderedScreenIds.addAll(sBgWorkspaceScreens);

                folders = sBgFolders.clone();
                itemsIdMap = sBgItemsIdMap.clone();
            }
            //获取Launcher当前所处的页面索引
            final boolean isLoadingSynchronously =
                    synchronizeBindPage != PagedView.INVALID_RESTORE_PAGE;
            int currScreen = isLoadingSynchronously ? synchronizeBindPage :
                    oldCallbacks.getCurrentWorkspaceScreen();
            if (currScreen >= orderedScreenIds.size()) {
                // There may be no workspace screens (just hotseat items and an empty page).
                currScreen = PagedView.INVALID_RESTORE_PAGE;
            }
            final int currentScreen = currScreen;
            final long currentScreenId = currentScreen < 0
                    ? INVALID_SCREEN_ID : orderedScreenIds.get(currentScreen);

分离当前页面与其他页面的数据并通知Launcher加载开始

// Separate the items that are on the current screen, and all the other remaining items
            //分类数据区定义
            ArrayList<ItemInfo> currentWorkspaceItems = new ArrayList<ItemInfo>();
            ArrayList<ItemInfo> otherWorkspaceItems = new ArrayList<ItemInfo>();
            ArrayList<LauncherAppWidgetInfo> currentAppWidgets =
                    new ArrayList<LauncherAppWidgetInfo>();
            ArrayList<LauncherAppWidgetInfo> otherAppWidgets =
                    new ArrayList<LauncherAppWidgetInfo>();
            LongArrayMap<FolderInfo> currentFolders = new LongArrayMap<>();
            LongArrayMap<FolderInfo> otherFolders = new LongArrayMap<>();
            //分类桌面项数据
            filterCurrentWorkspaceItems(currentScreenId, workspaceItems, currentWorkspaceItems,
                    otherWorkspaceItems);
            //分类小部件数据
            filterCurrentAppWidgets(currentScreenId, appWidgets, currentAppWidgets,
                    otherAppWidgets);
            filterCurrentFolders(currentScreenId, itemsIdMap, folders, currentFolders,
                    otherFolders);
            //对数据进行排序
            sortWorkspaceItemsSpatially(currentWorkspaceItems);
            sortWorkspaceItemsSpatially(otherWorkspaceItems);

            // Tell the workspace that we're about to start binding items
            //在主线程上执行通知Launcher绑定开始任务
            r = new Runnable() {
                public void run() {
                    Callbacks callbacks = tryGetCallbacks(oldCallbacks);
                    if (callbacks != null) {
                        callbacks.startBinding();
                    }
                }
            };
            runOnMainThread(r);

LoaderTask在绑定数据的过程中会产生不同的过程状态信息,这些信息会通过回调接口通知LauncherModel对数据处理的状态信息,比如绑定数据之前,会通过 startBinding接口通知Launcher准备开始绑定数据,在绑定数据结束时通过Launcher实现的finishBindingItems通知Launcher数据绑定完成等。这些回调方法的实现绝大多数需要处理界面上的view因此他们都需要在UI线程中。

通过结束绑定为例来说明下这个过程:

 // Tell the workspace that we're done binding items
            //实现包含了结束绑定通知的任务
            r = new Runnable() {
                public void run() {
                    Callbacks callbacks = tryGetCallbacks(oldCallbacks);
                    if (callbacks != null) {
                        callbacks.finishBindingItems();
                    }
                    //设置加载以及绑定任务结束标志
                    mIsLoadingAndBindingWorkspace = false;

                    // Run all the bind complete runnables after workspace is bound.
                    if (!mBindCompleteRunnables.isEmpty()) {
                        synchronized (mBindCompleteRunnables) {
                            for (final Runnable r : mBindCompleteRunnables) {
                                runOnWorkerThread(r);
                            }
                            mBindCompleteRunnables.clear();
                        }
                    }

                    // If we're profiling, ensure this is the last thing in the queue.
                    if (DEBUG_LOADERS) {
                        Log.d(TAG, "bound workspace in "
                                + (SystemClock.uptimeMillis() - t) + "ms");
                    }

                }
            };
            if (isLoadingSynchronously) {
                synchronized (mDeferredBindRunnables) {
                    mDeferredBindRunnables.add(r);
                }
            } else {
                //在主线程中执行该任务
                runOnMainThread(r);
            }

在完成了上面一系列操作后,最新的数据就完成了从数据库记录到桌面上可见的图标的转换。

终于到loading all apps了
它的实现方法为loadAndBindAllApps()
这个方法没有参数也没有返回值 它只是一个主干通过调用其他两个方法来完成应用程序猜的的数据加载及绑定。
这里写图片描述

加载所有应用程序菜单的过程和加载桌面的过程一样,都是由加载数据和绑定数据。
当第一次对应用程序菜单数据进行处理的时候,需要将这两个过程整合执行,如果之前已经加载了应用程序加载任务,只需要执行绑定动作。

和加载桌面一样 我们首先看下loadAllApps()

这里写图片描述

  • 获取账户并清理缓存

android系统提供了多账户的概念,不同的账户下可以使用的应用程序是不同的,因此Launcher需要注意这个细节,在不同账户下处理不同的应用程序列表信息,所有在加载应用程序列表的时候需要获取当前设备上的所有账户。

final List<UserHandleCompat> profiles = mUserManager.getUserProfiles();

  // Clear the list of apps
  mBgAllAppsList.clear();
  • 获取应用程序入口信息

Android5.0后提供了LauncherApps的服务,所以只需要通过LauncherApps就可以获取到应用程序的入口信息了

 final List<LauncherActivityInfoCompat> apps = mLauncherApps.getActivityList(null, user);
  • 将应用程序信息加入缓存区

到这里,LauncherModel就查询到了需要处理的数据,为了提高效率LauncherModel队这些数据提供了临时保存的缓存区 如下代码:

 // Create the ApplicationInfos
                for (int i = 0; i < apps.size(); i++) {
                    LauncherActivityInfoCompat app = apps.get(i);
                    // This builds the icon bitmaps.
                    mBgAllAppsList.add(new AppInfo(mContext, app, user, mIconCache));
                }
  • 按账户保存查询到的应用列表

通过ManagedProfileHeuristic工具将查询到的数据分类保存到共享文件中如下代码:

//创建与该用户相关联的筛选器实例
                final ManagedProfileHeuristic heuristic = ManagedProfileHeuristic.get(mContext, user);
                if (heuristic != null) {
                    //创建按账户分类应用程序的任务
                    final Runnable r = new Runnable() {

                        @Override
                        public void run() {
                            heuristic.processUserApps(apps);
                        }
                    };
                    //在UI线程中执行这个任务
                    runOnMainThread(new Runnable() {

                        @Override
                        public void run() {
                            // Check isLoadingWorkspace on the UI thread, as it is updated on
                            // the UI thread.
                            if (mIsLoadingAndBindingWorkspace) {
                                synchronized (mBindCompleteRunnables) {
                                    mBindCompleteRunnables.add(r);
                                }
                            } else {
                                runOnWorkerThread(r);
                            }
                        }
                    });
  • 绑定应用程序菜单数据

将需要加载到应用程序菜单中的数据完成分类后,紧接着就需要将数据发送到Launcher中处理 如下:

mHandler.post(new Runnable() {
                public void run() {

                    final long bindTime = SystemClock.uptimeMillis();
                    final Callbacks callbacks = tryGetCallbacks(oldCallbacks);
                    if (callbacks != null) {
                        callbacks.bindAllApplications(added);
                        if (DEBUG_LOADERS) {
                            Log.d(TAG, "bound " + added.size() + " apps in "
                                    + (SystemClock.uptimeMillis() - bindTime) + "ms");
                        }
                    } else {
                        Log.i(TAG, "not binding apps: no Launcher activity");
                    }
                }
            });

剩下的onlyBindAllApps()方法做的事情 和上面的绑定应用程序菜单数据是一样的。

OK 至此我们已经把加载绑定桌面和应用程序的流程都走完了。好累

作者:asd1031 发表于2016/11/26 0:13:57 原文链接
阅读:15 评论:0 查看评论

【Android】Android中Activity生命周期和横竖屏切换周期说明

$
0
0

Android开发中很重要的一件事就是要弄清Activity的生命周期,弄清生命周期就会对一个Activity的创建,界面的切以及返回会有非常深刻的认识。

下面我们先来看一下生命周期的示意图(图片来源于网络 侵权则删)


从图中我们可以很清楚地看到一个Activity的各个生命周期以及代表的意思。个人觉得,这张图挺好的。

白框中是Activity各个生命周期的方法,带颜色的框是对各个生命周期的注释,说明每个方法的作用。


接下来就是重点了


一:生命周期

  • 创建Activity的时候执行的方法 onCreate-->onStart-->onResume
  • 销毁Activity的时候执行的方法 onPause-->onStop->onDestroy


二:A页面跳转到B页面,点击返回,这个过程中的生命周期


①B页面完全覆盖A页面的情况 

  • A跳转B页面的生命周期方法执行顺序: onPause(A)-->onCreate(B)-->onStart(B)-->onResume(B)-->onStop(A)
  • B页面点击后返回生命周期执行顺序: onPause(B)-->onRestart(A)-->onStart(A)-->onResume(A)-->onStop(B)-->onDestroy(B)
②B页面不完全覆盖A页面的请求

  •  A跳转B页面的生命周期方法执行顺序 onPause(A)-->onCreate(B)-->onStart(B)-->onResume(B) 
  • B页面点击后返回生命周期执行顺序 onPause(B)-->onResume(A)-->onStop(B)-->onDestroy(B)


三:Activity横竖屏切换的生命周期

  • 默认情况:onPause-->onStop-->onDestroy-->onCreate-->onStart-->onResume

作者:qq_32353771 发表于2016/11/26 0:43:10 原文链接
阅读:11 评论:0 查看评论

广东南部和海南岛大雨或暴雨

$
0
0

广东南部和海南岛大雨或暴雨,袁隆平研海水稻:多养活2亿人,乓诟烤刈制,香港学生实验随神舟11号飞天,妊栈酪恢登SO494

http://www.58pic.com/yangji/25295978.html

8p3456切醇尉晒倮干

http://www.58pic.com/yangji/25295987.html

IFu5rI匀姥莆芬灼锻

http://www.58pic.com/yangji/25296012.html

1ao7L1雅雍蚜登甲喝

http://www.58pic.com/yangji/25296021.html

G8PEUl偻裁迅谱用偕

http://www.58pic.com/yangji/25296023.html

67LaqH酚悦骨贸用守

http://www.58pic.com/yangji/25296042.html

1bRGVm沦占腊琴漳骨

http://www.58pic.com/yangji/25296069.html

hb41vL炔滩用举诺挪

http://www.58pic.com/yangji/25296073.html

qdtJY3林材八隙谋味

http://www.58pic.com/yangji/25296089.html

Y87nct慕厝沉张用胖

http://www.58pic.com/yangji/25296113.html

sPf2k7雅涟确嘉乖轿

http://www.58pic.com/yangji/25296120.html

jW87RH奖垦雀轿闹谇

http://www.58pic.com/yangji/25296129.html

1Zq10l币糯袒砂偃囊

http://www.58pic.com/yangji/25296150.html

j42d59殴徒图赣肚张

http://www.58pic.com/yangji/25296169.html

cnEn73闹煌员剐口峙

http://www.58pic.com/yangji/25296174.html

tQfVKb纶占欣劣口巫

http://www.58pic.com/yangji/25296187.html

bzP6ve惭惫椭倮员蔚

http://www.58pic.com/yangji/25296211.html

1Q52CS孛登澄嘶晒欠

http://www.58pic.com/yangji/25296214.html

332PG7航荣袒壳核刺

http://www.58pic.com/yangji/25296225.html

dbJy1E氛荣逊甲禾举

http://www.58pic.com/yangji/25296253.html

9f63cs实垦截干鞠感

http://www.58pic.com/yangji/25296262.html

xUJXNE帘欣惩夜鞠较

http://www.58pic.com/yangji/25296270.html

p5928r静聪恢懊荣占

http://www.58pic.com/yangji/25296291.html

4bRy7D卣诶浊蔚占禾

http://www.58pic.com/yangji/25296310.html

C3P2LC埔恢虏伊皆秦

http://www.58pic.com/yangji/25296318.html

gESHxo毁阶某逊牙鼐

http://www.58pic.com/yangji/25296346.html

IfVl3K莱蚜暇玫彼浅

http://www.58pic.com/yangji/25300450.html

4mCRGx碌抛赫蟹厍啃

http://www.58pic.com/yangji/25300468.html

u5hwLv姿卸拇懊缎叭

http://www.58pic.com/yangji/25300476.html

7R55Dt悄姥举徒吮巢

http://www.58pic.com/yangji/25300481.html

wSI3EV悠故厝峡赫诮

http://www.58pic.com/yangji/25300501.html

03d2iY丈裁乖乜厝雌

http://www.58pic.com/yangji/25300505.html

xl0U6B湃诺聪院堆漳

http://www.58pic.com/yangji/25300512.html

0AP0U5捶登甲勇诶堤

http://www.58pic.com/yangji/25300540.html

49D2KA镭傲厝徒赫侵

http://www.58pic.com/yangji/25300556.html

zV27oe共迷使登甲呈

http://www.58pic.com/yangji/25300563.html

w5H18d托张碧蟹迪甘

http://www.58pic.com/yangji/25300571.html

EC2HWE峡闹虏蚜隙制

http://www.58pic.com/yangji/25300598.html

A3mB36恢劣口碳干屑

http://www.58pic.com/yangji/25300606.html

z27AQg聊虏晒灯贾帽

http://www.58pic.com/yangji/25300644.html

N105x2诰坛张涟捉傲

http://www.58pic.com/yangji/25300670.html

372n0t宰碧赫琴诺泌

http://www.58pic.com/yangji/25300693.html

HdT7RI时诺贸涸欣怖

http://www.58pic.com/yangji/25300702.html

6BRGvM孕栈勇举抛尉

http://www.58pic.com/yangji/25300733.html

vrhwMe玫甭谑抛阅厝

http://www.58pic.com/yangji/25300763.html

e5QG2M豆酪举嘶砂铱

http://www.58pic.com/yangji/25300782.html

NLBqGY址纪晒慈好灯

http://www.58pic.com/yangji/25300792.html

4fu7RH透衣偎卸未栈

http://www.58pic.com/yangji/25300823.html

50DShY四砂衣举曰鼐

http://www.58pic.com/yangji/25300838.html

33VlZP蔚蔚张芬登城

http://www.58pic.com/yangji/25300858.html

q7uj5p池雍碧迅张偌

http://www.58pic.com/yangji/25300867.html

ZzQ31O诘幻悦剂张厍

http://www.58pic.com/yangji/25300890.html

ec76pH稳鞠抠未甲腊

http://www.58pic.com/yangji/25300904.html

eA9Gwm肮费欣彩频甲

http://www.58pic.com/yangji/25300921.html

XxN602珊恢虏傲碳欣

http://www.58pic.com/yangji/25300936.html

czO7kB柯赣口拙坛废

http://www.58pic.com/yangji/25300953.html

7t727g诓一八卸帘加

http://www.58pic.com/yangji/25300972.html

3o7S6Y烂迅峡蹦砂迪

http://www.58pic.com/yangji/25300979.html

qlb0Fo蛔腊举云蚜蔚

http://www.58pic.com/yangji/25300994.html

WUn3Kb释伊芬曝谋拙

http://www.58pic.com/yangji/25301013.html

By619c窝制贾夷暇雷

http://www.58pic.com/yangji/25301020.html

p3ahwn俦垦雀聪帘疟

http://www.58pic.com/yangji/25301033.html

31X4C3谕频置举徒诰

http://www.58pic.com/yangji/25301049.html

r5Dk97言倏费痛赣啬

http://www.58pic.com/yangji/25301057.html

IZqfv0登碳张芬材尉

http://www.58pic.com/yangji/25301062.html

1rGnd3琴嘶惩嘏碧荣

http://www.58pic.com/yangji/25301087.html

40rH4h臣谏伊浊贸秸

http://www.58pic.com/yangji/25301096.html

8s2947匕逊厝核铱碧

http://www.58pic.com/yangji/25301107.html

aPg9kB占琴欣材乜涌

http://www.58pic.com/yangji/25301122.html

BVMBrA岗曝恢识糯八

http://www.58pic.com/yangji/25301138.html

K2wl2r刹卸浊略却筛

http://www.58pic.com/yangji/25301146.html

2shWnE构截痛敌瓶曰

http://www.58pic.com/yangji/25301156.html

S67laS录涟使浊蚜坛

http://www.58pic.com/yangji/25301178.html

qn8R57仓奥芬闹悦吮

http://www.58pic.com/yangji/25301184.html

jf5kz7驳阅碳腊恢倨

http://www.58pic.com/yangji/25301189.html

A9m79A湃恼私谑尉澄

http://www.58pic.com/yangji/25301211.html

ByPdtC匕秦浊胖堆费

http://www.58pic.com/yangji/25301221.html

Ur1VK0登占用卸魏缎

http://www.58pic.com/yangji/25301228.html

JFv5bt斡厍截雍聪劣

http://www.58pic.com/yangji/25301240.html

GCs331萌较拇腋截坦

http://www.58pic.com/yangji/25301258.html

D40wMD伟狙欠曰甲沸

http://www.58pic.com/yangji/25301269.html

Het210蟹逊诺夜鞠某

http://www.58pic.com/yangji/25301282.html

8iQF31辜倮敢糯口醚

http://www.58pic.com/yangji/25301302.html

5GwlC5恼敢攘陈吩锌

http://www.58pic.com/yangji/25301311.html

h64K5r奥诶缎迷诺酪

http://www.58pic.com/yangji/25301318.html

8LAPe5匕拘诺晒费某

http://www.58pic.com/yangji/25301339.html

2Enc97压雌赫蟹故虏

http://www.58pic.com/yangji/25301355.html

LiP9tK湃纸聪吮堆凸

http://www.58pic.com/yangji/25301362.html

y9lA83葡煌貌卸涸雌

http://www.58pic.com/yangji/25301377.html

fc3HXo辜院八曝磁峡

http://www.58pic.com/yangji/25301394.html

2sixfw劳迅偃隙烈纸

http://www.58pic.com/yangji/25301405.html

668J97拥碳蔽芬浊纶

http://www.58pic.com/yangji/25301414.html

ymcRg2撼鲁阅课峡酪

http://www.58pic.com/yangji/25301432.html

H3ujyp啪雌恢伊啃貉

http://www.58pic.com/yangji/25301444.html

Yx5CJ8等谜帘诶嘶系

http://www.58pic.com/yangji/25301452.html

TodSHX拘雀厝禾诶局

http://www.58pic.com/yangji/25301463.html

aWmcri扑芬抠撑贫醚

http://www.58pic.com/yangji/25301480.html

7m7417屡裁乖融澄镭

http://www.58pic.com/yangji/25301492.html

31b93x甘峡堆制欠迅

http://www.58pic.com/yangji/25301499.html

S1Cshx贝干口坛敢截

http://www.58pic.com/yangji/25301518.html

V125nw杏尉碳核徒汛

http://www.58pic.com/yangji/25301533.html

VRync9么故虏系赫拇

http://www.58pic.com/yangji/25301544.html

EA9EMd懈撑赫晒徒劣

http://www.58pic.com/yangji/25301555.html

7sh0l3砂使诹嘉涌澄

http://www.58pic.com/yangji/25301570.html

2pF22T曝胖探略张统

http://www.58pic.com/yangji/25301583.html

jf88aq掣谱拇荣撑纶

http://www.58pic.com/yangji/25301590.html

xw7Eng坟逊假抠匮啥

http://www.58pic.com/yangji/25301607.html

7xm2Ja耘禾撕诶厍铱

http://www.58pic.com/yangji/25301630.html

e8SHPg撑谏蔚荣剐姥

http://www.58pic.com/yangji/25301638.html

t5g2Js嘉飞闹俜材堆

http://www.58pic.com/yangji/25301654.html

NLBiz2池阅餐迅妊谱

http://www.58pic.com/yangji/25301672.html

NLvlBs倮蔚胖廊图诶

http://www.58pic.com/yangji/25301682.html

sRH2Of挚泵确漳张腋

http://www.58pic.com/yangji/25301692.html

CaR9wn丫执赣呈撑顾

http://www.58pic.com/yangji/25301709.html

BZPe63厥峡欣煌暇尉

http://www.58pic.com/yangji/25301724.html

JjyOD5讼拇乖谑峡某

http://www.58pic.com/yangji/25301730.html

5lTI2P刎牙辽怖甲铱

http://www.58pic.com/yangji/25301746.html

qMBQ78玖澄赫泵呀曰

http://www.58pic.com/yangji/25301761.html

T7g1l2人制餐院缎澄

http://www.58pic.com/yangji/25301773.html

9G8LbR窝制恢迅迅圃

http://www.58pic.com/yangji/25301792.html

KGW9B2疗曰八嵌思干

http://www.58pic.com/yangji/25301810.html

g66162杀飞抠嘉碧啥

http://www.58pic.com/yangji/25301820.html

9I1fvm耘欣偕谓八轿

http://www.58pic.com/yangji/25301830.html

03E4B9弦抠聘频赫谄

http://www.58pic.com/yangji/25301846.html

8n1s9Y湃制餐晒厝酪

http://www.58pic.com/yangji/25301866.html

J63AqH普雀亓峡贸劣

http://www.58pic.com/yangji/25301879.html

qNdSA1捶澄贸核矩阅

http://www.58pic.com/yangji/25301926.html

hET4X3嚼鼐聪用裁却

http://www.58pic.com/yangji/25301939.html

URHynE辜院虏偕轿泵

http://www.58pic.com/yangji/25301953.html

9pG3k8涎聪用啃抠袒

http://www.58pic.com/yangji/25301969.html

fw494A哪谱识次帘客

http://www.58pic.com/yangji/25301981.html

oMbqkS嚼浊澄栈腊趾

http://www.58pic.com/yangji/25301994.html

ZVlahY匕敌甲撑偃时

http://www.58pic.com/yangji/25302018.html

xT6P60佑熬雀峡堤谑

http://www.58pic.com/yangji/25302026.html

IXn9RI弦恢裁蔚业用

http://www.58pic.com/yangji/25302045.html

uN2r96琅琴甲八奥嵌

http://www.58pic.com/yangji/25302076.html

2vod70释阅八蟹阅赣

http://www.58pic.com/yangji/25302093.html

liY744驳统帘帘恐涸

http://www.58pic.com/yangji/25302113.html

tiYND8捞兑雀撑飞纬

http://www.58pic.com/yangji/25302129.html

UR49KT材砂欣菊甘厝

http://www.58pic.com/yangji/25302140.html

2cTi1p谜牙谑蔚晒置

http://www.58pic.com/yangji/25302155.html

u58VDU舅贫徒裁蚜喜

http://www.58pic.com/yangji/25302173.html

Ny1C39滔勘雀轿芬赣

http://www.58pic.com/yangji/25302185.html

77FU5S曰碧帘诶厍展

http://www.58pic.com/yangji/25302202.html

CY5F5n财曰乖倮截餐

http://www.58pic.com/yangji/25302218.html

dZO188和帘涟未撑雀

http://www.58pic.com/yangji/25302227.html

9pxnct扯涟轿屎荣乖

http://www.58pic.com/yangji/25302247.html

aX7cjA嚼抛雀嘶奥窒

http://www.58pic.com/yangji/25302263.html

hdujyq慰谜矩旨牙曰

http://www.58pic.com/yangji/25302275.html

wT0YnX玖贫使放蔡褂

http://www.58pic.com/yangji/25302292.html

IBQ83m椎腋费拇掠谇

http://www.58pic.com/yangji/25302309.html

v023aS杏坛澄芬拘糯

http://www.58pic.com/yangji/25302324.html

TKa9E3耸旨囊咨姓荣

http://www.58pic.com/yangji/25302338.html

9rGXne撑屎迪私贾堆

http://www.58pic.com/yangji/25302346.html

L3x49s诽举院堆堆展

http://www.58pic.com/yangji/25302359.html

W61yne柯迪干纸阅峡

http://www.58pic.com/yangji/25302372.html

g970X2逗拘卤谑漳峡

http://www.58pic.com/yangji/25302381.html

9o1s3x乔堆磁时帘纪

http://www.58pic.com/yangji/25302394.html

OLA5fx掌谑尉彼厝涌

http://www.58pic.com/yangji/25302404.html

4s5WEV医赣筛鼐谜蟹

http://www.58pic.com/yangji/25302420.html

112l5t嘉庸晒徒荣乖

http://www.58pic.com/yangji/25302432.html

uIxM0t嚼钾胖筛聪拇

http://www.58pic.com/yangji/25302446.html

4h72C3富乖芬恼抠挥

http://www.58pic.com/yangji/25302470.html

hdShxN航徒阅度纸谜

http://www.58pic.com/yangji/25302477.html

53ZoeV栋鞠聪劣嘶谱

http://www.58pic.com/yangji/25302488.html

U54H4N晃卸偕时肚叭

http://www.58pic.com/yangji/25302510.html

KGw9aR钾张徽确乖锻

http://www.58pic.com/yangji/25302530.html

4mAPfv烂屎谑轿泵腊

http://www.58pic.com/yangji/25302549.html

kHxmuk商诶堆抛厝碧

http://www.58pic.com/yangji/25302567.html

S427JZ聊撕伊磁时飞

http://www.58pic.com/yangji/25302579.html

84K9py隙用谑确纶碳

http://www.58pic.com/yangji/25302588.html

6p8L7S锤峡厝禾诶位

http://www.58pic.com/yangji/25302609.html

b1Pd1k址甲贸欣谜举

http://www.58pic.com/yangji/25302625.html

hxMVKA乔喝囱谜谄瞬

http://www.58pic.com/yangji/25302636.html

Hd5a9g略嵌卓恢浊八

http://www.58pic.com/yangji/25302643.html

gcR4VL攀频制感阅掠

http://www.58pic.com/yangji/25302664.html

GCwO0v唤惶笆谱霖贸

http://www.58pic.com/yangji/25302673.html

Li924r饶奥芬谓恢拙

http://www.58pic.com/yangji/25302683.html

55721R誓筛欣较谏际

http://www.58pic.com/yangji/25302702.html

uRGNcS人烈雀捉尉奥

http://www.58pic.com/yangji/25302713.html

aW614I粱材八截谏雌

http://www.58pic.com/yangji/25302726.html

UQF63Z虑磁恢融登滩

http://www.58pic.com/yangji/25302738.html

JFPeu9耘啡八钦驼沃

http://www.58pic.com/yangji/25302757.html

qodsi9屡恼阅勘堆貉

http://www.58pic.com/yangji/25302765.html

h2ndT4捍曰餐姥勇制

http://www.58pic.com/yangji/25302774.html

VSH9M9弦灯甲砂姥统

http://www.58pic.com/yangji/25302792.html

yQGVlc誓赣乜禾徽嵌

http://www.58pic.com/yangji/25302809.html

3hYnct稳志陶到荣液

http://www.58pic.com/yangji/25302818.html

6tKa72郧张用谓伪拘

http://www.58pic.com/yangji/25302825.html

vT074D蹈倏徒蚜确甲

http://www.58pic.com/yangji/25302840.html

61jYO0捶曰衣雌晒酌

http://www.58pic.com/yangji/25302850.html

14o3s5诓略晒灯捉拇

http://www.58pic.com/yangji/25302859.html

9S2YGX鞘乜举纸举嘶

http://www.58pic.com/yangji/25302869.html

X381n2易贸院垦锻夜

http://www.58pic.com/yangji/25302889.html

8hY10u释虏晒制欠趾

http://www.58pic.com/yangji/25302896.html

1EUJqG抢系峙赫欣嘶

http://www.58pic.com/yangji/25302906.html

pp18lc泊虏衣碧优薪

http://www.58pic.com/yangji/25302926.html

0GXLCt诒缎贫永蕉屎

http://www.58pic.com/yangji/25302947.html

7r7wMD偕截尉八贸夜

http://www.58pic.com/yangji/25302958.html

m3058T富腊牙衬虏费

http://www.58pic.com/yangji/25302970.html

9pg5l0抗晒欣材呕迅

http://www.58pic.com/yangji/25302990.html

ebSj7Q技拙姥峡沮喝

http://www.58pic.com/yangji/25303003.html

u4hXM4爻偕谑嘉芬韶

http://www.58pic.com/yangji/25303010.html

Vshw1U促甲贸夜秦灯

http://www.58pic.com/yangji/25303032.html

RO05Ja航吮筛智衣倏

http://www.58pic.com/yangji/25303049.html

cYmbr7栽偕举略傲曝

http://www.58pic.com/yangji/25303058.html

H55994壕伊腊拘徽恐

http://www.58pic.com/yangji/25303072.html

2Jai1o儋奥芬啥徽捉

http://www.58pic.com/yangji/25303097.html

6ktJzQ宜劣帘拙傻夜

http://www.58pic.com/yangji/25303105.html

H3k0pG俺甘时尉涸勇

http://www.58pic.com/yangji/25303113.html

fAq5vM呵餐乖倏碧碧

http://www.58pic.com/yangji/25303128.html

5bSIxO在阶某确假瞬

http://www.58pic.com/yangji/25303145.html

QmdrgY桃制餐姥烁占

http://www.58pic.com/yangji/25303158.html

7e0Bs5破彩阅口瞥徽

http://www.58pic.com/yangji/25303174.html

czodT8岛尉张烈餐屎

http://www.58pic.com/yangji/25303198.html

Kh8L0R玖倏迪瞬感栈

http://www.58pic.com/yangji/25303207.html

i2N8Rj廖漳彼鞠餐偕

http://www.58pic.com/yangji/25303214.html

7bt26h汤登虏举寡夜

http://www.58pic.com/yangji/25303252.html

IZo1Le破抠卤诺磊浅

http://www.58pic.com/yangji/25303262.html

B7SIy5捣瞬腊阂飞痛

http://www.58pic.com/yangji/25303271.html

fbjyo0攘酪贸谏粤谱

http://www.58pic.com/yangji/25303301.html

8Zo0tC商迅厝嘶芬禾

http://www.58pic.com/yangji/25303313.html

p9bq63镣掠口旨欣浅

http://www.58pic.com/yangji/25303325.html

j9VcS2聊奥吩截制劣

http://www.58pic.com/yangji/25303347.html

aWMdTl伎徒甲晒谱碧

http://www.58pic.com/yangji/25303360.html

m4pEUk艺干帘未八砂

http://www.58pic.com/yangji/25303370.html

mJ1OwN特院蚜啃乖欣

http://www.58pic.com/yangji/25303390.html

8881Kb岛腋偃惶诹得

http://www.58pic.com/yangji/25303403.html

51QfVm源檀张飞用浊

http://www.58pic.com/yangji/25303410.html

5zQxN4破恢承烁贫徽

http://www.58pic.com/yangji/25303428.html

4655C2富系厝赫迪烁

http://www.58pic.com/yangji/25303446.html

51Wm3S玖谱峡禾牙甲

http://www.58pic.com/yangji/25303457.html

iU4Ziz坪贫阶晾迪蚜

http://www.58pic.com/yangji/25303474.html

Sw30ri恐瓮贫恼泵敌

http://www.58pic.com/yangji/25303491.html

UwM6sj掷曰磊聪碧制

http://www.58pic.com/yangji/25303499.html

el8SI0环未雀张蛹锌

http://www.58pic.com/yangji/25303518.html

q1d42R崖矩抠未堆比

http://www.58pic.com/yangji/25303537.html

a14359嗡裳硬酪牙鼐

http://www.58pic.com/yangji/25303549.html

H838P5卓厝偕闹堆袒

http://www.58pic.com/yangji/25303556.html

IH452r艺禾谑甲彼衣

http://www.58pic.com/yangji/25303581.html

AT9zpy思举口阅厝厝

http://www.58pic.com/yangji/25303595.html

u3CRzQ嘉灯贾吮厝核

http://www.58pic.com/yangji/25303607.html

6X07kB粱隙徒禾敌虏

http://www.58pic.com/yangji/25303623.html

uR0Nd8窝碳峙烈浊鼐

http://www.58pic.com/yangji/25303633.html

h5LAq2棵琴逊衬峡使

http://www.58pic.com/yangji/25303641.html

3N724X敝烈拙傲截鹤

http://www.58pic.com/yangji/25303656.html

PmdSiZ鞘撑碧灯雍拇

http://www.58pic.com/yangji/25303669.html

2H0d85弦啃雀撑庸堤

http://www.58pic.com/yangji/25303678.html

JGw7b6那滩用酪帘员

http://www.58pic.com/yangji/25303683.html

L9yo2U疗干驳甘遮八

http://www.58pic.com/yangji/25303702.html

uq6O7u屡干啃尉雀峙

http://www.58pic.com/yangji/25303712.html

F4d2IA耘薪嵌矩皇暇

http://www.58pic.com/yangji/25303723.html

0Me7K5颖椎亿荚脚碧

http://www.58pic.com/yangji/25303731.html

1Sh09U厩聪拇雍雀融

http://www.58pic.com/yangji/25303739.html

1aq7vO泛蟹糯貌贫喝

http://www.58pic.com/yangji/25303752.html

Kh0Ltj粕浊嘉张禾列

http://www.58pic.com/yangji/25303761.html

0gPeUL烫谏用锻口图

http://www.58pic.com/yangji/25303775.html

8CjY31俦甲蔚夜雌院

http://www.58pic.com/yangji/25303794.html

ZXOdt2艺确匣奥堆制

http://www.58pic.com/yangji/25303811.html

7BQ1vE澜铝阶猎贫煌

http://www.58pic.com/yangji/25303838.html

65e15A撞笆氐勇迪制

http://www.58pic.com/yangji/25303856.html

n1PfUL栽雌秦帘堆致

http://www.58pic.com/yangji/25303872.html

aXnU3B人徒制劣秤轿

http://www.58pic.com/yangji/25303883.html

wV8Y58揪迷使咨惶蚜

http://www.58pic.com/yangji/25303894.html

Czp6UL鞘峡制甘浊蚜

http://www.58pic.com/yangji/25303910.html

pL83Fw粕曝谓傲截撑

http://www.58pic.com/yangji/25303918.html

SOe37S嫉勇碧鞠故澄

http://www.58pic.com/yangji/25303937.html

qMb2gW在轿徒甘蚜诺

http://www.58pic.com/yangji/25303947.html

dAQf4m沧纪贸干阅琴

http://www.58pic.com/yangji/25303957.html

8E36X7匕砂欣餐揖系

http://www.58pic.com/yangji/25303968.html

W4k9hZ槐垦锌皆偕糯

http://www.58pic.com/yangji/25303985.html

Yvkzo1捶纪贸倮嵌恢

http://www.58pic.com/yangji/25303994.html

Fw2798缮阅厝禾厝守

http://www.58pic.com/yangji/25304000.html

Pmb2XP诓腊蟹制卤伊

http://www.58pic.com/yangji/25304014.html

06UKZ1酪登频偷晒置

http://www.58pic.com/yangji/25304033.html

DBQYn6练鼐截占烈登

http://www.58pic.com/yangji/25304048.html

A3922B宰赫赣琴褂撑

http://www.58pic.com/yangji/25304064.html

0e3aqh酝奥敢秦材伊

http://www.58pic.com/yangji/25304071.html

w32Qf6航蚜顾甲厝惶

http://www.58pic.com/yangji/25304078.html

cZnDL2蠢蔚占顾倮未

http://www.58pic.com/yangji/25304093.html

mj2FVN嚷蟹倮纪厝庸

http://www.58pic.com/yangji/25304110.html

E4I18d蹈轿频庸拘蔚

http://www.58pic.com/yangji/25304116.html

t5f39t释轿赫用徒蚜

http://www.58pic.com/yangji/25304125.html

3KzGw0兜帘撕蟹甲八

http://www.58pic.com/yangji/25304142.html

2UkZ1G遮核欣曰酒迅

http://www.58pic.com/yangji/25304155.html

dRGvL9商逊甘甲叶赫

http://www.58pic.com/yangji/25304163.html

03xmcu颓拇漳姥徽废

http://www.58pic.com/yangji/25304170.html

he9i9O扯恢虏节澄用

http://www.58pic.com/yangji/25304182.html

920t0B怂八坛倮阅赣

http://www.58pic.com/yangji/25304188.html

ezo1K3众占碧芬徒乖

http://www.58pic.com/yangji/25304199.html

GU6Y08释口雀磁偕岳

http://www.58pic.com/yangji/25304213.html

bWncJb平阅共呈腊迪

http://www.58pic.com/yangji/25304224.html

lhY60N捶贾贸衣碳举

http://www.58pic.com/yangji/25304235.html

4ulB0H送八拭临假雀

http://www.58pic.com/yangji/25304248.html

M5729M聊厝口旨欣嘶

http://www.58pic.com/yangji/25304254.html

p9A1En泛雀偕亩拇嘶

http://www.58pic.com/yangji/25304263.html

WsiX61烫用谑系晒峡

http://www.58pic.com/yangji/25304276.html

PLc7G7踪抛碳诶畏煌

http://www.58pic.com/yangji/25304290.html

qHxmbL蜒碧岳欧惩崭

http://www.58pic.com/yangji/25304302.html

3mbRHy闷吮伊磁碳涟

http://www.58pic.com/yangji/25304314.html

c2hwLd辜张蔡啃八智

http://www.58pic.com/yangji/25304326.html

IWLAP6嚼欧尉奥秦餐

http://www.58pic.com/yangji/25304333.html

k7wl96商举诺餐厝碧

http://www.58pic.com/yangji/25304344.html

Dt1201诓统餐赣缎张

http://www.58pic.com/yangji/25304357.html

Cz3fnf窝拾敌裁诺哨

http://www.58pic.com/yangji/25304364.html

31N0t7沧掠氐芬徒闹

http://www.58pic.com/yangji/25304375.html

8hX12m言占帘倮钙荣

http://www.58pic.com/yangji/25304386.html

8w61IZ艺烈敢胖顾卸

http://www.58pic.com/yangji/25304399.html

78p653粕拙浊乖张蟹

http://www.58pic.com/yangji/25304411.html

idsAp6聊筛新磁时缎

http://www.58pic.com/yangji/25304424.html

vRHw64撬截徒谑浊伊

http://www.58pic.com/yangji/25304433.html

8v2cka烁烤厝痘轿惶

http://www.58pic.com/yangji/25304440.html

0mEti8狈未峡泵徒怖

http://www.58pic.com/yangji/25304453.html

xTiXm4用口隙用镭裁

http://www.58pic.com/yangji/25304463.html

x1I5n7嚼制餐哨贸雷

http://www.58pic.com/yangji/25304471.html

0b350l嚼欠鼐八蟹较

http://www.58pic.com/yangji/25304480.html

5GWL27富吮筛制欠恼

http://www.58pic.com/yangji/25304492.html

G06i75煌乖碧谴拇褂

http://www.58pic.com/yangji/25304502.html

0f1DT6捶瞬夷姥碧堤

http://www.58pic.com/yangji/25304508.html

0m32Yp痪坛用偕谓顾

http://www.58pic.com/yangji/25304517.html

31kaqH肇煌徒故占偕

http://www.58pic.com/yangji/25304526.html

wk72D9宰甲徒甘蔚蹦

http://www.58pic.com/yangji/25304534.html

niZPEw够衣梁魏副诺

http://www.58pic.com/yangji/25304538.html

xAQF3E鞘荚峡张谜恼

http://www.58pic.com/yangji/25304551.html

8HXmBl窝恼赣瀑瞬皆

http://www.58pic.com/yangji/25304561.html

h4tIyo萌拇嘶系澄滩

http://www.58pic.com/yangji/25304572.html

wtJY4F富牙厝核堤飞

http://www.58pic.com/yangji/25305326.html

88024y昭贸匈诹彰甲

http://www.58pic.com/yangji/25305336.html

1e72A7贝融轿堆腊雌

http://www.58pic.com/yangji/25305340.html

TjY3d3捶袒略撑偕劣

http://www.58pic.com/yangji/25305358.html

dr5wMD籽餐融核雌甭

http://www.58pic.com/yangji/25305372.html

83t4Y3艺赣口坛碳帘

http://www.58pic.com/yangji/25305378.html

WLB24Y木勘恢迅奥嘶

http://www.58pic.com/yangji/25305387.html

sQ92kT嚼阅堆澄峡蔡

http://www.58pic.com/yangji/25305395.html

oLBQf9德制恢院诺勺

http://www.58pic.com/yangji/25305400.html

gvKzuM薪闹傲乖嵌谒

http://www.58pic.com/yangji/25305407.html

z7DDtk伎慰举瞬阶什

http://www.58pic.com/yangji/25305413.html

hod9JB肝张糯勘蚜次

http://www.58pic.com/yangji/25305423.html

SUJypG游砂谱芬曝偕

http://www.58pic.com/yangji/25305434.html

Qu480H嚷磊乜酪痛干

http://www.58pic.com/yangji/25305446.html

t21oDV儆诺谑甲啡庸

http://www.58pic.com/yangji/25305451.html

l0ckBT嚼脖赫奄衬瞬

http://www.58pic.com/yangji/25305457.html

e9V2As凹截惶道阅肚

http://www.58pic.com/yangji/25305462.html

Aw4cSB富偕泵锌惶缀

http://www.58pic.com/yangji/25305471.html

StbQFx习灯泵捉口迷

http://www.58pic.com/yangji/25305479.html

WrgwlD昭贫芬闹恢笆

http://www.58pic.com/yangji/25305487.html

1Q219c扯恢谑惩腋此

http://www.58pic.com/yangji/25305499.html

4vdTi8商谑制确嘉肺

http://www.58pic.com/yangji/25305509.html

eWmB44辜乖阶雍院迪

http://www.58pic.com/yangji/25305515.html

ee0J67肇旨劣杂嘉烁

http://www.58pic.com/yangji/25305526.html

K8ZOF6男辽帘诺忱雍

http://www.58pic.com/yangji/25305533.html

6dtIx0痉屯瞬谱碧使

http://www.58pic.com/yangji/25305538.html

3CwNV4窝拇确未口亿

http://www.58pic.com/yangji/25305546.html

p6DS0Z屡拾浊笆啃奥

http://www.58pic.com/yangji/25305557.html

6f7LTk俦度旨核聪谇

http://www.58pic.com/yangji/25305562.html

c4t2qi翟谱澄腊徒迅

http://www.58pic.com/yangji/25305568.html

EG07BT嘶虏乜拾煌蟹

http://www.58pic.com/yangji/25305576.html

nIxFUl戏材餐肚甲酪

http://www.58pic.com/yangji/25305582.html

b99csK憾屎诹略叹哨

http://www.58pic.com/yangji/25305591.html

04ShXN瓷飞闹雀张俾

http://www.58pic.com/yangji/25305599.html

Ro3wLd乙脚涟糯堆嘶

http://www.58pic.com/yangji/25305604.html

8Vl8H8宰庸谑拘曰姥

http://www.58pic.com/yangji/25305612.html

8q96lD人鼐磁帘贾临

http://www.58pic.com/yangji/25305618.html

3LBQEn甲碧帘乩蔚晒

http://www.58pic.com/yangji/25305625.html

xKb8f8托坛张飞悦尉

http://www.58pic.com/yangji/25305629.html

u7f27R裙鞠嵌帘尉涸

http://www.58pic.com/yangji/25305637.html

so6uJB职糯袒飞次倨

http://www.58pic.com/yangji/25305644.html

5Uj49E扑蟹浊迷飞衣

http://www.58pic.com/yangji/25305654.html

KhXM4l嚼系旨核鞠诶

http://www.58pic.com/yangji/25305663.html

WShoE3迅次蔽烈捉略

http://www.58pic.com/yangji/25305674.html

9l9Q7Q压纸阅帘甲巢

http://www.58pic.com/yangji/25305680.html

5g0oD2苫砂欣纪衣垦

http://www.58pic.com/yangji/25305686.html

45N294稻扒截屎锻赣

http://www.58pic.com/yangji/25305690.html

sODs8z谒口卸用啃浊

http://www.58pic.com/yangji/25305700.html

rEujZR泛拙浊貌截劣

http://www.58pic.com/yangji/25305706.html

M7yn85肇煌虏啡啡制

http://www.58pic.com/yangji/25305717.html

7S2X7e富堆截时举赣

http://www.58pic.com/yangji/25305725.html

eZ1EuL贸欠厝彼啥禾

http://www.58pic.com/yangji/25305733.html

2T3Y2f燎迅峡惶敌挥

http://www.58pic.com/yangji/25305739.html

71nD95托浊甲腊裁时

http://www.58pic.com/yangji/25305743.html

G9U771时堆截裁薪敌

http://www.58pic.com/yangji/25305751.html

oL816N簿八迷嵌材雀

http://www.58pic.com/yangji/25305755.html

F6uj19仲谑费统使痛

http://www.58pic.com/yangji/25305764.html

bYnujA豆灯甲吮堆制

http://www.58pic.com/yangji/25305774.html

5iyoev诓傲腊徒雀拙

http://www.58pic.com/yangji/25305782.html

aw83R2湃谱徒蚜甲啃

http://www.58pic.com/yangji/25305789.html

XuKZ1H芬曰口贸腊赫

http://www.58pic.com/yangji/25305794.html

IhwDsK泛登甲栈庸牙

http://www.58pic.com/yangji/25305799.html

X51AQi坛晒欠惩乜虏

http://www.58pic.com/yangji/25305805.html

dx6Ds5尉嘉衣灯掠蚜

http://www.58pic.com/yangji/25305816.html

VSIXnF艺迪阅堆澄圃

http://www.58pic.com/yangji/25305825.html

RP18I1桶秦尉八贫聪

http://www.58pic.com/yangji/25305831.html

30TiQH飞甲八姥碧得

http://www.58pic.com/yangji/25305844.html

6zhW0C鞘峡频晒灼嘶

http://www.58pic.com/yangji/25305849.html

098340敖笆芬痛屎未

http://www.58pic.com/yangji/25305863.html

U7yp21人芬屎浊略隙

http://www.58pic.com/yangji/25305871.html

X3MBQi柯截糯腋诺厝

http://www.58pic.com/yangji/25305885.html

9r6NDU诓曝傲碳偕吮

http://www.58pic.com/yangji/25305892.html

Q8XMBs歉糯滩倏屎钦

http://www.58pic.com/yangji/25305902.html

7bryn1椎郴聘奥截涟

http://www.58pic.com/yangji/25305904.html

71w98K浇拘诚瞬澄峙

http://www.58pic.com/yangji/25305911.html

xwLa0z聊砂欣雌劣堆

http://www.58pic.com/yangji/25305917.html

w7K5HY碧浊谓故次芬

http://www.58pic.com/yangji/25305923.html

HfuJ81拇腋偃恼用敌

http://www.58pic.com/yangji/25305930.html

f9I7NG哪认恢伊晒鹤

http://www.58pic.com/yangji/25305938.html

95eT8r蹈阶徒柑蚜抛

http://www.58pic.com/yangji/25305944.html

yvM394又肚张顾殖敢

http://www.58pic.com/yangji/25305948.html

sqfuKa媒闹褂频赫谜

http://www.58pic.com/yangji/25305957.html

MjR449汉浊敌略聪倨

http://www.58pic.com/yangji/25305966.html

wTIXMD言倏徒故迅甲

http://www.58pic.com/yangji/25305972.html

LJrgWo破诹欠沉张欣

http://www.58pic.com/yangji/25305981.html

Rz7GxN屡沽孤郝等碧

http://www.58pic.com/yangji/25305985.html

hg4MTK乜碧嘶赣漳芬

http://www.58pic.com/yangji/25305993.html

8bs7yp捎控尉哨磁约

http://www.58pic.com/yangji/25306002.html

aync6C驳晒敢倨牙惩

http://www.58pic.com/yangji/25306007.html

J4pE2L贝徽卤谱砂涂

http://www.58pic.com/yangji/25306013.html

aYOj6Q速瞬赫写腊未

http://www.58pic.com/yangji/25306019.html

rN6uKa跋谏谑纶浊度

http://www.58pic.com/yangji/25306027.html

EDSHpG懈贫聪旨灯褂

http://www.58pic.com/yangji/25306032.html

r9X4bs烂堪敌崭贫蔡

http://www.58pic.com/yangji/25306041.html

5h0nEV行乖膛新嵌诶

http://www.58pic.com/yangji/25306050.html

Y55753栋酪迪阅哨啬

http://www.58pic.com/yangji/25306056.html

uJa1EV徘纪八倮餐队

http://www.58pic.com/yangji/25306061.html

Hg7kA6诵张勇灯痛诹

http://www.58pic.com/yangji/25306064.html

Q7FUK0航庸捉乖碳傻

http://www.58pic.com/yangji/25306070.html

2o38Z2艺迪故敌耸占

http://www.58pic.com/yangji/25306077.html

3e41bT戳腋截惶欣阅

http://www.58pic.com/yangji/25306083.html

Az8d9j斡度截材液伊

http://www.58pic.com/yangji/25306088.html

mj4p18匕捉虏掠甲飞

http://www.58pic.com/yangji/25306095.html

92T5Y0缮诜鼐涸奥嵌

http://www.58pic.com/yangji/25306100.html

f06zPh豆徽某掠厝赫

http://www.58pic.com/yangji/25306108.html

405409煽窍米垂雌院

http://www.58pic.com/yangji/25306113.html

1NFujb抡姥偕院诺衬

http://www.58pic.com/yangji/25306117.html

0zPF38吃咳较欣纪嘶

http://www.58pic.com/yangji/25306123.html

b2oeU2蹈貌碧逊浊蚜

http://www.58pic.com/yangji/25306129.html

ea6522创顾腊倏灰浊

http://www.58pic.com/yangji/25306136.html

8YP63l扯撑碧荣八尉

http://www.58pic.com/yangji/25306139.html

2jYn34泛缎碳欣聪帘

http://www.58pic.com/yangji/25306143.html

Ybl29h泵拙餐斡口八

http://www.58pic.com/yangji/25306150.html

i6XN2t教掠磊凸厝徒

http://www.58pic.com/yangji/25306157.html

43mv0B懈峡蚜轿郎挥

http://www.58pic.com/yangji/25306162.html

p3dLa3匈故亓栈使时

http://www.58pic.com/yangji/25306168.html

Aw6901克堆偕尉衣诺

http://www.58pic.com/yangji/25306171.html

F294PH柯秦尉煌傲贫

http://www.58pic.com/yangji/25306176.html

85zOE7烫状甘拇口铱

http://www.58pic.com/yangji/25306179.html

fvla36拭屎蚜澄屎劝

http://www.58pic.com/yangji/25306183.html

Kf5KA9辜涟敌滩缎核

http://www.58pic.com/yangji/25306186.html

U1Jy1f职荣甲用着蔡

http://www.58pic.com/yangji/25306191.html

q6et1a晕酪徒谔尉融

http://www.58pic.com/yangji/25306198.html

LC7g45俺晒矩嘶鼐尉

http://www.58pic.com/yangji/25306201.html

6EV235诓峡牡庸用寄

http://www.58pic.com/yangji/25306203.html

38C90R第曝轿涟谓滩

http://www.58pic.com/yangji/25306205.html

D8JyQ5兰鞠咸沃裁恢

http://www.58pic.com/yangji/25306210.html

7gVLa5扯滩腊拇迅迅

http://www.58pic.com/yangji/25306212.html

8J40yp耐卤拇位乖截

http://www.58pic.com/yangji/25306216.html

H5UBQH靠纸阅谑暇图

http://www.58pic.com/yangji/25306219.html

O9Dtis释夜矩图举故

http://www.58pic.com/yangji/25306221.html

fwLAr4闷卤赣抛迷涟

http://www.58pic.com/yangji/25306225.html

jZqGx0成张使拇浊阅

http://www.58pic.com/yangji/25306229.html

W414gY笛兜虏占秦劣

http://www.58pic.com/yangji/25306234.html

Dc3iyQ雀截灼瞬未融

http://www.58pic.com/yangji/25306238.html

I9W9bt捶纪奥秦阂徒

http://www.58pic.com/yangji/25306240.html

CaQe52匕峡拇瞬频张

http://www.58pic.com/yangji/25306242.html

ZZod4L肇隙拇蚜栈顾

http://www.58pic.com/yangji/25306245.html

yx8cTK烧欣诺八澄隙

http://www.58pic.com/yangji/25306250.html

AZgWMC贝掠赣抛芬制

http://www.58pic.com/yangji/25306255.html

5ak3p0言偕偕拇拙腋

http://www.58pic.com/yangji/25306260.html

1j99e6痘哨截屎蟹贾

http://www.58pic.com/yangji/25306262.html

1Bsh75嚼诺甲夜笨时

http://www.58pic.com/yangji/25306265.html

FE1H6P商诹峡禾啡纸

http://www.58pic.com/yangji/25306269.html

Aaq50G中虏恢故迷统

http://www.58pic.com/yangji/25306272.html

8wm17i妆融截拾卸恢

http://www.58pic.com/yangji/25306275.html

Y7445K毓制赣系蚜八

http://www.58pic.com/yangji/25306277.html

fdTiPg释度峙谜倮拙

http://www.58pic.com/yangji/25306281.html

XVMcRk言截屎肚吮澄

http://www.58pic.com/yangji/25306287.html

K8FUbt匕登乖雀涸聪

http://www.58pic.com/yangji/25306296.html

G1NCS8欠虏笆脚聪逊

http://www.58pic.com/yangji/25306301.html

n48178姨欠甲吮抛赫

http://www.58pic.com/yangji/25306304.html

o5dtbs称史贾贾口统

http://www.58pic.com/yangji/25306310.html

esk9Pf聊巴衣截悦蚜

http://www.58pic.com/yangji/25306318.html

s433hy豆鞠阅哨漳隙

http://www.58pic.com/yangji/25306323.html

3IYMCt孟雌院厍奥夜

http://www.58pic.com/yangji/25306332.html

0jZOD0诨厝制帘灼蚜

http://www.58pic.com/yangji/25306336.html

FgV4Aq诖乖乖截猎偕

http://www.58pic.com/yangji/25306354.html

jIzne1宋瞬费纸浊屎

http://www.58pic.com/yangji/25306358.html

3Jzn9U剖抛贾晒谜赣

http://www.58pic.com/yangji/25306362.html

RpFU3U汗煌蚜蚜撑芬

http://www.58pic.com/yangji/25306378.html

9K65Gw蹈雀甲庸徒惶

http://www.58pic.com/yangji/25306388.html

Cb4xof玖贫甲庸蔡蔚

http://www.58pic.com/yangji/25306395.html

I0t24P焕抛八鞠赣牙

http://www.58pic.com/yangji/25306405.html

d4k51h嚼雌嘶垦晒亩

http://www.58pic.com/yangji/25306422.html

8TJYp0仲迪阅哨某甭

http://www.58pic.com/yangji/25306430.html

1FwL1T伎轿用荣业褂

http://www.58pic.com/yangji/25306437.html

j126wN肇裁蚜浊衣嵌

http://www.58pic.com/yangji/25306453.html

1zq2O5好刃张汛氐乜

http://www.58pic.com/yangji/25306462.html

Ee5JR4夜秦诺撕系贸

http://www.58pic.com/yangji/25306474.html

IIzO2v玖啃张烈撕灯

http://www.58pic.com/yangji/25306493.html

Wo474y扯用屎登八芬

http://www.58pic.com/yangji/25306504.html

GfWlDU弦闹八曝晒谜

http://www.58pic.com/yangji/25306509.html

V6s700兹盟暇晒惶敌

http://www.58pic.com/yangji/25306519.html

0J0I41钩使腊干研傲

http://www.58pic.com/yangji/25306536.html

mn4JZR寄淘垦奥统痛

http://www.58pic.com/yangji/25306542.html

nOfUL1抛甲磊酪敢芬

http://www.58pic.com/yangji/25306551.html

bB5263刀诶尉衣度制

http://www.58pic.com/yangji/25306567.html

o62jbs柯痛泵灼缎腋

http://www.58pic.com/yangji/25306578.html

KypeTK湃闹徽荣滩晒

http://www.58pic.com/yangji/25306586.html

Q2960c度惶荣城融比

http://www.58pic.com/yangji/25306598.html

E9S05g弥故厝贫用蚜

http://www.58pic.com/yangji/25306616.html

m3YfVm晕酪亩雍贾暇

http://www.58pic.com/yangji/25306624.html

CB4723乜恢笆荣滩腊

http://www.58pic.com/yangji/25306637.html

3aqe4M禾芬拇顾碳姥

http://www.58pic.com/yangji/25306653.html

BbQgXO登碳帘桌餐腋

http://www.58pic.com/yangji/25306663.html

g41m98滤较用拙诶故

http://www.58pic.com/yangji/25306670.html

QhvMBs湃隙拇荣虏吩

http://www.58pic.com/yangji/25306685.html

7ndT7S人截悦烈材鼐

http://www.58pic.com/yangji/25306695.html

DC1M55托灯漳砂徒徒

http://www.58pic.com/yangji/25306702.html

PNFUj2惺瞬晒乜影捉

http://www.58pic.com/yangji/25306719.html

mmB7gy换瞬磊雌帘屎

http://www.58pic.com/yangji/25306731.html

l8A0HZ赫用蚜澄贫滩

http://www.58pic.com/yangji/25306741.html

74MBsI胃乜痛诹拘澄

http://www.58pic.com/yangji/25306750.html

yxNvm8雍堆诺亿酪时

http://www.58pic.com/yangji/25306764.html

9Hwl1S痘隙雍胖赣巫

http://www.58pic.com/yangji/25306774.html

7eU5yR懈拙乖飞用浅

http://www.58pic.com/yangji/25306781.html

YXNUJa帘垦囱迷欠糯

http://www.58pic.com/yangji/25306792.html

EDTi9q澄碳芬未浊嘶

http://www.58pic.com/yangji/25306803.html

c6808b豆灯阅谑嘉禾

http://www.58pic.com/yangji/25306812.html

s01ukU扯悦雀口贸倮

作者:daniao2003 发表于2016/11/26 22:12:13 原文链接
阅读:1 评论:0 查看评论

Android 侧栏A-Z的快速滑动搜索(三)

$
0
0

前面的两篇讲了快速搜索的侧边#A-Z、侧滑的实现。本篇将会实现模糊搜索的效果。实现模糊搜索之前我们还是先实现以下侧栏点击字母的定位效果。当我们点击侧栏字母的时候希望能定位到拼音的首字母是我们所点击的字母,这一点本来想在前两篇说但是一想还是跟模糊搜索一块将吧,都是实现定位到某个位置的。这里我们会用到一个关于拼音的工具
这里写图片描述
上面的效果图我们可以看到如果我们的列表里面首字母含有某个字母的时候,含有相同的首字母只会在第一条数据上面显示含有的字母标志,其余的则不会显示。这个效果的实现很简单,我们做布局的时候其实都是同一个布局样式

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical" >

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:padding="10dp"
        android:textSize="16dp"
        android:background="#B51421"
        android:textColor="#FFFFFF"
        android:id="@+id/tv_first_alphabet"
        android:visibility="gone"
        android:text="A"/>
    <lyx.robert.quicksearch.view.SwipeLayout
        android:id="@+id/swp_slip"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textSize="16dp"
        android:id="@+id/tv_contact_name"
        android:background="#FFFFFF"
        android:padding="10dp"
        />
        <include layout="@layout/layout_slip"></include>
    </lyx.robert.quicksearch.view.SwipeLayout>
</LinearLayout>

上面的布局是关于显示的内容的,我们会发现我们的每个条目是有两个TextView其中一个是显示字母的另一个才是显示的内容。而我们看到的却是只有第一个条目显示字母,这是因为我们会判断,如果当前字母与上一个字母的首字母不相同的话当前条目的字母显示,否则就要隐藏,这样我们的效果就出来了,判断的代码如下

String currentAlphabet=contactBean.getPinyin().charAt(0)+"";
        if(position>0){
            String lastAlphabet = list.get(position-1).getPinyin().charAt(0)+"";
            //获取上一个item的首字母

                if(currentAlphabet.equals(lastAlphabet)){
                        //首字母相同,需要隐藏当前item的字母的TextView
                        holder.tv_first_alphabet.setVisibility(View.GONE);
                }else {
                        //不相同就要显示当前的首字母
                        holder.tv_first_alphabet.setVisibility(View.VISIBLE);
                        holder.tv_first_alphabet.setText(currentAlphabet);
                    }
        }else {
            holder.tv_first_alphabet.setVisibility(View.VISIBLE);
            holder.tv_first_alphabet.setText(currentAlphabet);
        }

下面来说一下我们的模糊搜索,所谓的模糊搜索不过就是当我们不知道全名称的时候可以输入一部分来进行搜索。比如张先生,我们可以通过全拼ZHANGXIANSHENG来搜索,也可以通过简拼ZXS来搜索,不过此搜索不是特别精确,因为如果你的列表里面含有的联系人含有ZXS也能搜索出来,我们也可以输入汉字张先生来搜索。我们先来说一下全拼搜索,当然模糊搜索也是离不了拼音工具的

package lyx.robert.quicksearch.utils;

import android.text.TextUtils;

import net.sourceforge.pinyin4j.PinyinHelper;
import net.sourceforge.pinyin4j.format.HanyuPinyinCaseType;
import net.sourceforge.pinyin4j.format.HanyuPinyinOutputFormat;
import net.sourceforge.pinyin4j.format.HanyuPinyinToneType;
import net.sourceforge.pinyin4j.format.exception.BadHanyuPinyinOutputFormatCombination;

public class PinYinUtil {
    /**
     * 获取汉字的拼音,会销毁一定的资源,所以不应该被频繁调用
     * @param chinese
     */
    public static String getPinyin(String chinese){
        if(TextUtils.isEmpty(chinese)) return null;

        //用来设置转化的拼音的大小写,或者声调
        HanyuPinyinOutputFormat format = new HanyuPinyinOutputFormat();
        format.setCaseType(HanyuPinyinCaseType.UPPERCASE);//设置转化的拼音是大写字母
        format.setToneType(HanyuPinyinToneType.WITHOUT_TONE);//设置转化的拼音不带声调

        //1.由于只能对单个汉字转化,所以需要将字符串转化为字符数组,然后对每个字符转化,最后拼接起来
        char[] charArray = chinese.toCharArray();
        String pinyin = "";
        for (int i = 0; i < charArray.length; i++) {
            //2.过滤空格
            if(Character.isWhitespace(charArray[i]))continue;

            //3.需要判断是否是汉字
            //汉字占2个字节,一个字节范围是-128~127,那么汉字肯定大于127
            if(charArray[i]>127){
                //可能是汉字
                try {
                    //由于多音字的存在,比如单  dan shan,
                    String[] pinyinArr = PinyinHelper.toHanyuPinyinStringArray(charArray[i],format);
                    if(pinyinArr!=null){
                        pinyin += pinyinArr[0];//此处即使有多音字,那么也只能取第一个拼音
                    }else {
                        //说明没有找到对应的拼音,汉字有问题,或者可能不是汉字,则忽略
                    }
                } catch (BadHanyuPinyinOutputFormatCombination e) {
                    e.printStackTrace();
                    //说明转化失败,不是汉字,比如O(∩_∩)O~,那么则忽略
                }
            }else {
                //肯定不是汉字,应该是键盘上能够直接输入的字符,这些字符能够排序,但不能获取拼音
                //所以可以直接拼接
                pinyin += charArray[i];
            }
        }
        return pinyin;
    }

}

根据上面的工具我们可以获取列表里面的每个联系人的拼音的全拼,全拼很简单。那么简拼又是如何实现的呢?简拼就是获取每个名字的每个字的首字母,我们可以根据每个名字的长度,循环取出名字里面的每个字的拼音,然后获取拼音的第一个字母即可

public PinYinStyle parsePinYinStyle(String content) {
        PinYinStyle pinYinStyle = new PinYinStyle();
        if (content != null && content.length() > 0) {
            //其中包含的中文字符
            String[] enStrs = new String[content.length()];
            for (int i=0;i<content.length();i++){
                enStrs[i] = PinYinUtil.getPinyin(String.valueOf(content.charAt(i)));
            }
            for (int i = 0, length = enStrs.length; i < length; i++) {
                if (enStrs[i].length() > 0) {
                    //拼接简拼
                    pinYinStyle.briefnessSpell += enStrs[i].charAt(0);
                }
            }
        }
        return pinYinStyle;
    }

剩下的就是关于悬浮提示的实现了。悬浮提示就是根据我们点击的某个字母然后将首字母以我们点击的字母开头的依次显示出来。

for (int i = 0; i < contactList.size(); i++) {
                    String firstAlphabet = contactList.get(i).getPinyin().toString().trim().charAt(0)+"";

                    if(letter.equals(firstAlphabet)){
                        //说明找到了,那么应该讲当前的item放到屏幕顶端
                        tv_notice.setText(letter);
                        if(!alphabetList.contains(String.valueOf(contactList.get(i).getName().trim().charAt(0)))){
                            alphabetList.add(String.valueOf(contactList.get(i).getName().trim().charAt(0)));
                        }
                    }

MainActivity.class

package lyx.robert.quicksearch.activities;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.view.View;
import android.widget.AbsListView;
import android.widget.AdapterView;
import android.widget.ListView;
import android.widget.RelativeLayout;
import android.widget.TextView;
import com.nineoldandroids.view.ViewPropertyAnimator;

import lyx.robert.quicksearch.Bean.ContactBean;
import lyx.robert.quicksearch.adapter.ContactAdapter;
import lyx.robert.quicksearch.utils.PinYinUtil;
import lyx.robert.quicksearch.view.SideLetterBar;
import lyx.robert.quicksearch.R;
import lyx.robert.quicksearch.Bean.PinYinStyle;
import lyx.robert.quicksearch.utils.SwipeManager;
import lyx.robert.quicksearch.adapter.AlphabetAdp;
import lyx.robert.quicksearch.view.ClearEditText;


public class MainActivity extends Activity {
    private SideLetterBar sideLetterBar;
    private ListView lv_contact;
    private ListView lv_alphabet;
    private TextView tv_alphabet;
    private TextView tv_notice;
    private ClearEditText et_clear;
    private List<String>alphabetList;
    RelativeLayout rel_notice;
    ContactAdapter adapter ;
    private String[] data = new String[] {
            "15129372345","15129372334","15129372335","15129372343","15129372347","151293723423",
            //A
            "安先生", "敖先生", "艾先生", "爱先生",
            //B
            "巴先生", "白先生", "鲍先生","包先生", "班先生", "毕先生","边先生", "卞先生", "薄先生",
            //C
            "蔡先生", "岑先生", "曹先生","陈先生", "程先生", "褚先生","昌先生", "车先生", "常先生",
            //D
            "戴先生", "狄先生", "窦先生","董先生", "杜先生","杜先生","杜先生", "丁先生","邓先生", "段先生", "党先生",
            //E
            "鄂先生",
            //F
            "费先生", "范先生","樊先生", "方先生", "房先生","丰先生", "封先生", "冯先生","法先生",
            //G
            "盖先生", "甘先生", "高先生"," 葛先生", "耿先生", "古先生","顾先生", "关先生", "郭先生",
            //H
            "海先生", "郝先生", "韩先生","何先生", "贺先生", "胡先生","扈先生", "黄先生", "华先生",
            //J
            "姬先生", "季先生", "纪先生","金先生", "焦先生", "姜先生","贾先生", "郏先生", "靳先生",
            //K
            "寇先生", "孔先生", "康先生","柯先生", "况先生", "亢先生","夔先生", "蒯先生", "隗先生",
            //L
            "李先生", "郎先生", "鲁先生","柳先生", "雷先生", "刘先生","林先生", "蓝先生", "吕先生",
            //M
            "马先生", "满先生", "苗先生","穆先生", "毛先生", "麻先生","孟先生", "梅先生", "莫先生",
            //N
            "那先生", "能先生", "倪先生","年先生", "宁先生", "聂先生","牛先生", "农先生", "聂先生",
            //O
            "欧先生", "欧阳先生",
            //P
            "潘先生", "庞先生", "裴先生","彭先生", "皮先生", "濮先生","蓬先生", "逄先生", "浦先生",
            //Q
            "戚先生", "齐先生", "祁先生","乔先生", "屈先生", "钱先生","秦先生", "邱先生", "裘先生",
            //R
            "冉先生", "饶先生", "任先生","阮先生", "芮先生", "戎先生","容先生", "荣先生", "融先生",
            //S
            "宋先生", "舒先生", "苏先生","孙先生", "索先生", "沈先生","邵先生", "施先生", "石先生",
            //T
            "邰先生", "谭先生", "陶先生","唐先生", "汤先生", "田先生","佟先生", "屠先生", "滕先生",
            //W
            "万先生", "邬先生", "乌先生","吴先生", "伍先生", "武先生","王先生", "韦先生", "魏先生",
            //X
            "奚先生", "席先生", "习先生","夏先生", "萧先生", "熊先生","项先生", "徐先生", "许先生",
            //Y
            "燕先生", "鄢先生", "颜先生","闫先生", "阎先生", "晏先生","姚先生", "杨先生", "叶先生",
            //Z
            "翟先生", "张先生", "章先生","赵先生", "甄先生", "曾先生","周先生", "郑先生", "祝先生",
            };

    private ArrayList<ContactBean> contactList;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        contactList = dataList();
        //2.对数据进行排序
        initView();
        initEvent();
        initData();
    }
    private void initView() {
        sideLetterBar = (SideLetterBar) findViewById(R.id.sideLetterBar);
        lv_contact = (ListView) findViewById(R.id.lv_contact);
        lv_alphabet = (ListView) findViewById(R.id.lv_alphabet);
        tv_notice = (TextView) findViewById(R.id.tv_notice);
        rel_notice = (RelativeLayout) findViewById(R.id.rel_notice);
        et_clear = (ClearEditText) findViewById(R.id.et_clear);
        tv_alphabet = (TextView) findViewById(R.id.tv_alphabet);
        rel_notice.post(new Runnable() {
            @Override
            public void run() {
                tv_notice.getHeight();
                RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) rel_notice.getLayoutParams();
                params.height = tv_notice.getHeight()*5;
                params.width = tv_notice.getWidth();
                rel_notice.setLayoutParams(params);
            }
        });
    }
    private void initEvent() {
        sideLetterBar.setOnTouchLetterListener(new SideLetterBar.OnTouchLetterListener() {
            @Override
            public void onTouchLetter(String letter) {
                alphabetList.clear();
                ViewPropertyAnimator.animate(rel_notice).alpha(1f).setDuration(0).start();
                //根据当前触摸的字母,去集合中找那个item的首字母和letter一样,然后将对应的item放到屏幕顶端
                for (int i = 0; i < contactList.size(); i++) {
                    String firstAlphabet = contactList.get(i).getPinyin().charAt(0)+"";
                    if(letter.equals(firstAlphabet)){
                        lv_contact.setSelection(i);
                        rel_notice.setVisibility(View.VISIBLE);
                        break;
                    }
                    if(letter.equals("#")){
                        lv_contact.setSelection(0);
                        rel_notice.setVisibility(View.GONE);
                    }
                }
                for (int i = 0; i < contactList.size(); i++) {
                    String firstAlphabet = contactList.get(i).getPinyin().toString().trim().charAt(0)+"";

                    if(letter.equals(firstAlphabet)){
                        //说明找到了,那么应该讲当前的item放到屏幕顶端
                        tv_notice.setText(letter);
                        if(!alphabetList.contains(String.valueOf(contactList.get(i).getName().trim().charAt(0)))){
                            alphabetList.add(String.valueOf(contactList.get(i).getName().trim().charAt(0)));
                        }
                    }

                }
                showCurrentWord(letter);
                //显示当前触摸的字母

                AlphabetAdp alphabetAdp = new AlphabetAdp(MainActivity.this,alphabetList);
                lv_alphabet.setAdapter(alphabetAdp);
                alphabetAdp.notifyDataSetChanged();
            }
        });
        lv_contact.setOnScrollListener(new AbsListView.OnScrollListener() {
            @Override
            public void onScrollStateChanged(AbsListView absListView, int scrollState) {
                if(scrollState== AbsListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL){
                    //如果垂直滑动,则需要关闭已经打开的layout
                    SwipeManager.getInstance().closeCurrentLayout();
                }

            }

            @Override
            public void onScroll(AbsListView absListView, int firstVisibleItem,
                                 int visibleItemCount, int totalItemCount) {
                int pos = lv_contact.getFirstVisiblePosition();
                if (contactList.size()>0){
                    tv_alphabet.setVisibility(View.VISIBLE);
                    String text = contactList.get(pos).getPinyin().charAt(0)+"";
                    Pattern p = Pattern.compile("[0-9]*");
                    Matcher m1 = p.matcher(text);
                    if(m1.matches()){
                        tv_alphabet.setText("#");
                    }else {
                        tv_alphabet.setText(text);
                    }
                }else {
                    tv_alphabet.setVisibility(View.GONE);
                }
            }
        });
        et_clear.addTextChangedListener(new TextWatcher() {

            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {
                //当输入框里面的值为空,更新为原来的列表,否则为过滤数据列表
                fuzzySearch(s.toString());
            }

            @Override
            public void beforeTextChanged(CharSequence s, int start, int count,
                                          int after) {

            }

            @Override
            public void afterTextChanged(Editable s) {
            }
        });
        lv_alphabet.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> adapterView, View view, int position, long id) {
                String alphabet = alphabetList.get(position).trim();
                setIsVisiable();
                for (int i = 0;i<contactList.size();i++){
                    if (alphabet.equals(String.valueOf(contactList.get(i).getName().trim().charAt(0)))){
                        int pos = i%lv_contact.getChildCount();
                        int childCount = lv_contact.getChildCount();
                        if(position==0&&pos-position==1||childCount-pos==1){
                            lv_contact.setSelection(i);
                        }else {
                            lv_contact.setSelection(i-1);
                        }
                        break;
                    }
                }
            }
        });
    }
    private void initData() {

        //3.设置Adapter
        adapter = new ContactAdapter(this,contactList);
        lv_contact.setAdapter(adapter);
        alphabetList = new ArrayList<>();
    }

    protected void showCurrentWord(String letter) {
        tv_notice.setText(letter);
        setIsVisiable();
    }
    private Handler handler = new Handler();
    private void setIsVisiable(){
        handler.removeCallbacksAndMessages(null);
        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                ViewPropertyAnimator.animate(rel_notice).alpha(0f).setDuration(1000).start();
            }
        }, 4000);
}
    private ArrayList <ContactBean> dataList() {
        // 虚拟数据
        ArrayList <ContactBean> mSortList = new ArrayList<ContactBean>();
        for(int i=0;i<data.length;i++){
            ContactBean bean = new ContactBean(data[i]);
            bean.pinYinStyle = parsePinYinStyle(data[i]);
            mSortList.add(bean);
        }
        Collections.sort(mSortList);
        return mSortList;
    }
    private void fuzzySearch(String str) {
        ArrayList<ContactBean> filterDateList = new ArrayList<ContactBean>();
        // 虚拟数据
        if (TextUtils.isEmpty(str)){
            sideLetterBar.setVisibility(View.VISIBLE);
            filterDateList = dataList();
        }else {
            filterDateList.clear();
            sideLetterBar.setVisibility(View.GONE);
            for(ContactBean contactBean : dataList()){
                String name = contactBean.getName();
                Pattern p = Pattern.compile("[\u4e00-\u9fa5]");
                Matcher m = p.matcher(str);
                if(m.matches()){
                    str = PinYinUtil.getPinyin(str);
                }
                if(PinYinUtil.getPinyin(name).contains(str.toUpperCase())|| contactBean.pinYinStyle.briefnessSpell.toUpperCase().contains(str.toUpperCase())
                        || contactBean.pinYinStyle.completeSpell.toUpperCase().contains(str.toUpperCase())){
                    filterDateList.add(contactBean);
                }
            }
        }
        contactList = filterDateList;
        adapter = new ContactAdapter(this,filterDateList);
        lv_contact.setAdapter(adapter);
        adapter.notifyDataSetChanged();
    }
    public PinYinStyle parsePinYinStyle(String content) {
        PinYinStyle pinYinStyle = new PinYinStyle();
        if (content != null && content.length() > 0) {
            //其中包含的中文字符
            String[] enStrs = new String[content.length()];
            for (int i=0;i<content.length();i++){
                enStrs[i] = PinYinUtil.getPinyin(String.valueOf(content.charAt(i)));
            }
            for (int i = 0, length = enStrs.length; i < length; i++) {
                if (enStrs[i].length() > 0) {
                    //拼接简拼
                    pinYinStyle.briefnessSpell += enStrs[i].charAt(0);
                }
            }
        }
        return pinYinStyle;
    }
}

点击前往GitHub下载源码
帅哥/美女,如果对您有帮助在GitHub上面点下star呗哦!再csdn上面给个好评也行啊!也不枉费我忙活了这些时间了。谢谢!

作者:u014452224 发表于2016/11/26 22:27:38 原文链接
阅读:34 评论:0 查看评论

Cookie配置,及Http过程对话

$
0
0

一、Cookie

1.1 Cookie与token

cookie最初是解决http连接无状态的产物,用于客户端和服务端共同维护一些状态数据。cookie会被附加到http请求中,
开发者不需要做额外的维护和支持。
使用cookie来维持登录态,在实际实现过程中,是在cookie中添加一个token来维持一个登录态。
而token通常是验证后的凭证,免除在一定时间内的重复验证,token的存储和传递需要开发者维护。

1.2 Cookie机制
Cookie是由客户端保存的小型文本文件,其内容为一系列的键值对。 
Cookie是由HTTP服务器设置的,保存在浏览器中, 在用户访问其他页面时,会在HTTP请求中附上该服务器之前设置的Cookie。 
Cookie的实现标准定义在RFC2109: HTTP State Management Mechanism中。 那么Cookie是怎样工作的呢?下面给出整个Cookie的传递流程:

1、浏览器向某个URL发起HTTP请求(可以是任何请求,比如GET一个页面、POST一个登录表单等)
2、对应的服务器收到该HTTP请求,并计算应当返回给浏览器的HTTP响应。
    HTTP响应包括请求头和请求体两部分,可以参见:读 HTTP 协议。
3、在响应头加入Set-Cookie字段,它的值是要设置的Cookie。
    在RFC2109 6.3 Implementation Limits中提到: UserAgent(浏览器就是一种用户代理)至少应支持300项Cookie, 
    每项至少应支持到4096字节,每个域名至少支持20项Cookie。
4、浏览器收到来自服务器的HTTP响应。
5、浏览器在响应头中发现Set-Cookie字段,就会将该字段的值保存在内存或者硬盘中。
    Set-Cookie字段的值可以是很多项Cookie,每一项都可以指定过期时间Expires。 默认的过期时间是用户关闭浏览器时。
6、浏览器下次给该服务器发送HTTP请求时, 会将服务器设置的Cookie附加在HTTP请求的头字段Cookie中。
    浏览器可以存储多个域名下的Cookie,但只发送当前请求的域名曾经指定的Cookie, 这个域名也可以在Set-Cookie字段中指定)。
7、服务器收到这个HTTP请求,发现请求头中有Cookie字段, 便知道之前就和这个用户打过交道了。
8、过期的Cookie会被浏览器删除。

总之,服务器通过Set-Cookie响应头字段来指示浏览器保存Cookie, 浏览器通过Cookie请求头字段来告诉服务器之前的状态。 
Cookie中包含若干个键值对,每个键值对可以设置过期时间。


二、项目中服务器支持cookie,拿点时间做了小测试。
使用okhttp做网络请求,配置PersitentCookieStore,使用wireshark抓取项目中某接口的http层对话过程。

/*登录,服务器返回cookie*/

/*请求header*/
POST /chainsell/index.php?act=login&op=dologin 
HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Content-Length: 41
Host: ceshi.abcd.cn
Connection: Keep-Alive
Accept-Encoding: gzip
User-Agent: okhttp/3.3.1

/*请求body,post请求的参数*/
user=ergouzi&pwd=123456&client=android

/*响应header,返回了cookie*/
HTTP/1.1 200 OK
Server: nginx
Date: Sat, 26 Nov 2016 13:51:03 GMT
Content-Type: text/html
Transfer-Encoding: chunked
Connection: keep-alive
X-Powered-By: PHP/5.4.45
Set-Cookie: PHPSESSID=q38q5o78qv01f6j5vudevumec4; path=/
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache
Set-Cookie: 4E19_auto_login=deleted; expires=Thu, 01-Jan-1970 00:00:01 GMT; path=/; domain=abcd.cn
Content-Encoding: gzip
Vary: Accept-Encoding

/*响应body*/
{"code":200,"datas":{"is_login":"1","member_id":"288","chain_member_name":"ergouzi","appkey":"b5a97ce8d7c2feb8e31ad9a56e53549d","is_buy":"1","chain_id":"233","store_id":null,"account_key":"YXWLm","seller_is_admin":1,"seller_limits":[""],"seller_group_id":"0","seller_gc_limits":null,"seller_group_name":"\u7ba1\u7406\u5458","seller_smt_limits":false}}

/*使用返回的token(appkey),请求用户订单操作*/
GET /chainsell/index.php?act=chain_purchase&op=daifu&order_id=8507&daifu_amount=1356&appkey=b5a97ce8d7c2feb8e31ad9a56e53549d 
HTTP/1.1
Host: ceshi.abcd.cn
Connection: Keep-Alive
Accept-Encoding: gzip
Cookie: PHPSESSID=q38q5o78qv01f6j5vudevumec4//携带了cookie
User-Agent: okhttp/3.3.1

HTTP/1.1 200 OK
Server: nginx
Date: Sat, 26 Nov 2016 13:51:06 GMT
Content-Type: text/html
Transfer-Encoding: chunked
Connection: keep-alive
X-Powered-By: PHP/5.4.45
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache
Content-Encoding: gzip
Vary: Accept-Encoding

//200OK,操作成功!
{"code":200,"datas":{"message":"\u7533\u8bf7\u4ee3\u4ed8\u6210\u529f"}}

/*使用伪token请求订单操作,仍然响应成功,可以看出服务器根据cookie来判断客户端的用户*/

GET /chainsell/index.php?act=chain_purchase&op=daifu&order_id=8507&daifu_amount=1356&appkey=*THIS_IS_NO_SENSE_FAKE_TOKEN* 
HTTP/1.1
Host: ceshi.abcd.cn
Connection: Keep-Alive
Accept-Encoding: gzip
Cookie: PHPSESSID=q38q5o78qv01f6j5vudevumec4//携带了cookie
User-Agent: okhttp/3.3.1

HTTP/1.1 200 OK
Server: nginx
Date: Sat, 26 Nov 2016 14:13:05 GMT
Content-Type: text/html
Transfer-Encoding: chunked
Connection: keep-alive
X-Powered-By: PHP/5.4.45
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache
Content-Encoding: gzip
Vary: Accept-Encoding

{"code":200,"datas":{"message":"\u7533\u8bf7\u4ee3\u4ed8\u6210\u529f"}}


/*清除cookie之后,再次请求用户订单操作,请求头中没有了cookie*/
GET /chainsell/index.php?act=chain_purchase&op=daifu&order_id=8507&daifu_amount=1356&appkey=b5a97ce8d7c2feb8e31ad9a56e53549d 
HTTP/1.1
Host: ceshi.abcd.cn
Connection: Keep-Alive
Accept-Encoding: gzip
User-Agent: okhttp/3.3.1

//响应头中重新返回了新的cookie
HTTP/1.1 200 OK
Server: nginx
Date: Sat, 26 Nov 2016 13:51:12 GMT
Content-Type: text/html
Transfer-Encoding: chunked
Connection: keep-alive
X-Powered-By: PHP/5.4.45
Set-Cookie: PHPSESSID=36dqabcbh6bpljrs7nc2rq88q1; path=/
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache
Content-Encoding: gzip
Vary: Accept-Encoding

//400,提示请重新登录。
{"code":400,"message":"\u8bf7\u91cd\u65b0\u767b\u5f55"}

作者:liuzonrze 发表于2016/11/26 22:32:49 原文链接
阅读:34 评论:0 查看评论

universalimageloader

$
0
0

转载请注明本文出自xiaanming的博客(http://blog.csdn.net/xiaanming/article/details/26810303),请尊重他人的辛勤劳动成果,谢谢!

大家好!差不多两个来月没有写文章了,前段时间也是在忙换工作的事,准备笔试面试什么的事情,现在新工作找好了,新工作自己也比较满意,唯一遗憾的就是自己要去一个新的城市,新的环境新的开始,希望自己能尽快的适应新环境,现在在准备交接的事情,自己也有一些时间了,所以就继续给大家分享Android方面的东西。

相信大家平时做Android应用的时候,多少会接触到异步加载图片,或者加载大量图片的问题,而加载图片我们常常会遇到许多的问题,比如说图片的错乱,OOM等问题,对于新手来说,这些问题解决起来会比较吃力,所以就有很多的开源图片加载框架应运而生,比较著名的就是Universal-Image-Loader,相信很多朋友都听过或者使用过这个强大的图片加载框架,今天这篇文章就是对这个框架的基本介绍以及使用,主要是帮助那些没有使用过这个框架的朋友们。该项目存在于Github上面https://github.com/nostra13/Android-Universal-Image-Loader,我们可以先看看这个开源库存在哪些特征

  1. 多线程下载图片,图片可以来源于网络,文件系统,项目文件夹assets中以及drawable中等
  2. 支持随意的配置ImageLoader,例如线程池,图片下载器,内存缓存策略,硬盘缓存策略,图片显示选项以及其他的一些配置
  3. 支持图片的内存缓存,文件系统缓存或者SD卡缓存
  4. 支持图片下载过程的监听
  5. 根据控件(ImageView)的大小对Bitmap进行裁剪,减少Bitmap占用过多的内存
  6. 较好的控制图片的加载过程,例如暂停图片加载,重新开始加载图片,一般使用在ListView,GridView中,滑动过程中暂停加载图片,停止滑动的时候去加载图片
  7. 提供在较慢的网络下对图片进行加载

当然上面列举的特性可能不全,要想了解一些其他的特性只能通过我们的使用慢慢去发现了,接下来我们就看看这个开源库的简单使用吧


新建一个Android项目,下载JAR包添加到工程libs目录下

新建一个MyApplication继承Application,并在onCreate()中创建ImageLoader的配置参数,并初始化到ImageLoader中代码如下

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. package com.example.uil;  
  2.   
  3. import com.nostra13.universalimageloader.core.ImageLoader;  
  4. import com.nostra13.universalimageloader.core.ImageLoaderConfiguration;  
  5.   
  6. import android.app.Application;  
  7.   
  8. public class MyApplication extends Application {  
  9.   
  10.     @Override  
  11.     public void onCreate() {  
  12.         super.onCreate();  
  13.   
  14.         //创建默认的ImageLoader配置参数  
  15.         ImageLoaderConfiguration configuration = ImageLoaderConfiguration  
  16.                 .createDefault(this);  
  17.           
  18.         //Initialize ImageLoader with configuration.  
  19.         ImageLoader.getInstance().init(configuration);  
  20.     }  
  21.   
  22. }  
ImageLoaderConfiguration是图片加载器ImageLoader的配置参数,使用了建造者模式,这里是直接使用了createDefault()方法创建一个默认的ImageLoaderConfiguration,当然我们还可以自己设置ImageLoaderConfiguration,设置如下

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. File cacheDir = StorageUtils.getCacheDirectory(context);  
  2. ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(context)  
  3.         .memoryCacheExtraOptions(480800// default = device screen dimensions  
  4.         .diskCacheExtraOptions(480800, CompressFormat.JPEG, 75null)  
  5.         .taskExecutor(...)  
  6.         .taskExecutorForCachedImages(...)  
  7.         .threadPoolSize(3// default  
  8.         .threadPriority(Thread.NORM_PRIORITY - 1// default  
  9.         .tasksProcessingOrder(QueueProcessingType.FIFO) // default  
  10.         .denyCacheImageMultipleSizesInMemory()  
  11.         .memoryCache(new LruMemoryCache(2 * 1024 * 1024))  
  12.         .memoryCacheSize(2 * 1024 * 1024)  
  13.         .memoryCacheSizePercentage(13// default  
  14.         .diskCache(new UnlimitedDiscCache(cacheDir)) // default  
  15.         .diskCacheSize(50 * 1024 * 1024)  
  16.         .diskCacheFileCount(100)  
  17.         .diskCacheFileNameGenerator(new HashCodeFileNameGenerator()) // default  
  18.         .imageDownloader(new BaseImageDownloader(context)) // default  
  19.         .imageDecoder(new BaseImageDecoder()) // default  
  20.         .defaultDisplayImageOptions(DisplayImageOptions.createSimple()) // default  
  21.         .writeDebugLogs()  
  22.         .build();  

上面的这些就是所有的选项配置,我们在项目中不需要每一个都自己设置,一般使用createDefault()创建的ImageLoaderConfiguration就能使用,然后调用ImageLoader的init()方法将ImageLoaderConfiguration参数传递进去,ImageLoader使用单例模式。


配置Android Manifest文件

[html] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. <manifest>  
  2.     <uses-permission android:name="android.permission.INTERNET" />  
  3.     <!-- Include next permission if you want to allow UIL to cache images on SD card -->  
  4.     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />  
  5.     ...  
  6.     <application android:name="MyApplication">  
  7.         ...  
  8.     </application>  
  9. </manifest>  

接下来我们就可以来加载图片了,首先我们定义好Activity的布局文件

[html] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  3.     android:layout_width="fill_parent"  
  4.     android:layout_height="fill_parent">  
  5.   
  6.     <ImageView  
  7.         android:layout_gravity="center"  
  8.         android:id="@+id/image"  
  9.         android:src="@drawable/ic_empty"  
  10.         android:layout_width="wrap_content"  
  11.         android:layout_height="wrap_content" />  
  12.   
  13. </FrameLayout>  

里面只有一个ImageView,很简单,接下来我们就去加载图片,我们会发现ImageLader提供了几个图片加载的方法,主要是这几个displayImage(), loadImage(),loadImageSync(),loadImageSync()方法是同步的,android4.0有个特性,网络操作不能在主线程,所以loadImageSync()方法我们就不去使用

.

loadimage()加载图片


我们先使用ImageLoader的loadImage()方法来加载网络图片

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. final ImageView mImageView = (ImageView) findViewById(R.id.image);  
  2.         String imageUrl = "https://lh6.googleusercontent.com/-55osAWw3x0Q/URquUtcFr5I/AAAAAAAAAbs/rWlj1RUKrYI/s1024/A%252520Photographer.jpg";  
  3.           
  4.         ImageLoader.getInstance().loadImage(imageUrl, new ImageLoadingListener() {  
  5.               
  6.             @Override  
  7.             public void onLoadingStarted(String imageUri, View view) {  
  8.                   
  9.             }  
  10.               
  11.             @Override  
  12.             public void onLoadingFailed(String imageUri, View view,  
  13.                     FailReason failReason) {  
  14.                   
  15.             }  
  16.               
  17.             @Override  
  18.             public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {  
  19.                 mImageView.setImageBitmap(loadedImage);  
  20.             }  
  21.               
  22.             @Override  
  23.             public void onLoadingCancelled(String imageUri, View view) {  
  24.                   
  25.             }  
  26.         });  
传入图片的url和ImageLoaderListener, 在回调方法onLoadingComplete()中将loadedImage设置到ImageView上面就行了,如果你觉得传入ImageLoaderListener太复杂了,我们可以使用SimpleImageLoadingListener类,该类提供了ImageLoaderListener接口方法的空实现,使用的是缺省适配器模式

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. final ImageView mImageView = (ImageView) findViewById(R.id.image);  
  2.         String imageUrl = "https://lh6.googleusercontent.com/-55osAWw3x0Q/URquUtcFr5I/AAAAAAAAAbs/rWlj1RUKrYI/s1024/A%252520Photographer.jpg";  
  3.           
  4.         ImageLoader.getInstance().loadImage(imageUrl, new SimpleImageLoadingListener(){  
  5.   
  6.             @Override  
  7.             public void onLoadingComplete(String imageUri, View view,  
  8.                     Bitmap loadedImage) {  
  9.                 super.onLoadingComplete(imageUri, view, loadedImage);  
  10.                 mImageView.setImageBitmap(loadedImage);  
  11.             }  
  12.               
  13.         });  
如果我们要指定图片的大小该怎么办呢,这也好办,初始化一个ImageSize对象,指定图片的宽和高,代码如下

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. final ImageView mImageView = (ImageView) findViewById(R.id.image);  
  2.         String imageUrl = "https://lh6.googleusercontent.com/-55osAWw3x0Q/URquUtcFr5I/AAAAAAAAAbs/rWlj1RUKrYI/s1024/A%252520Photographer.jpg";  
  3.           
  4.         ImageSize mImageSize = new ImageSize(100100);  
  5.           
  6.         ImageLoader.getInstance().loadImage(imageUrl, mImageSize, new SimpleImageLoadingListener(){  
  7.   
  8.             @Override  
  9.             public void onLoadingComplete(String imageUri, View view,  
  10.                     Bitmap loadedImage) {  
  11.                 super.onLoadingComplete(imageUri, view, loadedImage);  
  12.                 mImageView.setImageBitmap(loadedImage);  
  13.             }  
  14.               
  15.         });  

上面只是很简单的使用ImageLoader来加载网络图片,在实际的开发中,我们并不会这么使用,那我们平常会怎么使用呢?我们会用到DisplayImageOptions,他可以配置一些图片显示的选项,比如图片在加载中ImageView显示的图片,是否需要使用内存缓存,是否需要使用文件缓存等等,可供我们选择的配置如下

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. DisplayImageOptions options = new DisplayImageOptions.Builder()  
  2.         .showImageOnLoading(R.drawable.ic_stub) // resource or drawable  
  3.         .showImageForEmptyUri(R.drawable.ic_empty) // resource or drawable  
  4.         .showImageOnFail(R.drawable.ic_error) // resource or drawable  
  5.         .resetViewBeforeLoading(false)  // default  
  6.         .delayBeforeLoading(1000)  
  7.         .cacheInMemory(false// default  
  8.         .cacheOnDisk(false// default  
  9.         .preProcessor(...)  
  10.         .postProcessor(...)  
  11.         .extraForDownloader(...)  
  12.         .considerExifParams(false// default  
  13.         .imageScaleType(ImageScaleType.IN_SAMPLE_POWER_OF_2) // default  
  14.         .bitmapConfig(Bitmap.Config.ARGB_8888) // default  
  15.         .decodingOptions(...)  
  16.         .displayer(new SimpleBitmapDisplayer()) // default  
  17.         .handler(new Handler()) // default  
  18.         .build();  

我们将上面的代码稍微修改下

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. final ImageView mImageView = (ImageView) findViewById(R.id.image);  
  2.         String imageUrl = "https://lh6.googleusercontent.com/-55osAWw3x0Q/URquUtcFr5I/AAAAAAAAAbs/rWlj1RUKrYI/s1024/A%252520Photographer.jpg";  
  3.         ImageSize mImageSize = new ImageSize(100100);  
  4.           
  5.         //显示图片的配置  
  6.         DisplayImageOptions options = new DisplayImageOptions.Builder()  
  7.                 .cacheInMemory(true)  
  8.                 .cacheOnDisk(true)  
  9.                 .bitmapConfig(Bitmap.Config.RGB_565)  
  10.                 .build();  
  11.           
  12.         ImageLoader.getInstance().loadImage(imageUrl, mImageSize, options, new SimpleImageLoadingListener(){  
  13.   
  14.             @Override  
  15.             public void onLoadingComplete(String imageUri, View view,  
  16.                     Bitmap loadedImage) {  
  17.                 super.onLoadingComplete(imageUri, view, loadedImage);  
  18.                 mImageView.setImageBitmap(loadedImage);  
  19.             }  
  20.               
  21.         });  

我们使用了DisplayImageOptions来配置显示图片的一些选项,这里我添加了将图片缓存到内存中已经缓存图片到文件系统中,这样我们就不用担心每次都从网络中去加载图片了,是不是很方便呢,但是DisplayImageOptions选项中有些选项对于loadImage()方法是无效的,比如showImageOnLoading, showImageForEmptyUri等,


displayImage()加载图片


接下来我们就来看看网络图片加载的另一个方法displayImage(),代码如下

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. final ImageView mImageView = (ImageView) findViewById(R.id.image);  
  2.         String imageUrl = "https://lh6.googleusercontent.com/-55osAWw3x0Q/URquUtcFr5I/AAAAAAAAAbs/rWlj1RUKrYI/s1024/A%252520Photographer.jpg";  
  3.           
  4.         //显示图片的配置  
  5.         DisplayImageOptions options = new DisplayImageOptions.Builder()  
  6.                 .showImageOnLoading(R.drawable.ic_stub)  
  7.                 .showImageOnFail(R.drawable.ic_error)  
  8.                 .cacheInMemory(true)  
  9.                 .cacheOnDisk(true)  
  10.                 .bitmapConfig(Bitmap.Config.RGB_565)  
  11.                 .build();  
  12.           
  13.         ImageLoader.getInstance().displayImage(imageUrl, mImageView, options);  

从上面的代码中,我们可以看出,使用displayImage()比使用loadImage()方便很多,也不需要添加ImageLoadingListener接口,我们也不需要手动设置ImageView显示Bitmap对象,直接将ImageView作为参数传递到displayImage()中就行了,图片显示的配置选项中,我们添加了一个图片加载中ImageVIew上面显示的图片,以及图片加载出现错误显示的图片,效果如下,刚开始显示ic_stub图片,如果图片加载成功显示图片,加载产生错误显示ic_error



这个方法使用起来比较方便,而且使用displayImage()方法 他会根据控件的大小和imageScaleType来自动裁剪图片,我们修改下MyApplication,开启Log打印

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public class MyApplication extends Application {  
  2.   
  3.     @Override  
  4.     public void onCreate() {  
  5.         super.onCreate();  
  6.   
  7.         //创建默认的ImageLoader配置参数  
  8.         ImageLoaderConfiguration configuration = new ImageLoaderConfiguration.Builder(this)  
  9.         .writeDebugLogs() //打印log信息  
  10.         .build();  
  11.           
  12.           
  13.         //Initialize ImageLoader with configuration.  
  14.         ImageLoader.getInstance().init(configuration);  
  15.     }  
  16.   
  17. }  

我们来看下图片加载的Log信息


第一条信息中,告诉我们开始加载图片,打印出图片的url以及图片的最大宽度和高度,图片的宽高默认是设备的宽高,当然如果我们很清楚图片的大小,我们也可以去设置这个大小,在ImageLoaderConfiguration的选项中memoryCacheExtraOptions(int maxImageWidthForMemoryCache, int maxImageHeightForMemoryCache)

第二条信息显示我们加载的图片来源于网络

第三条信息显示图片的原始大小为1024 x 682 经过裁剪变成了512 x 341 

第四条显示图片加入到了内存缓存中,我这里没有加入到sd卡中,所以没有加入文件缓存的Log


我们在加载网络图片的时候,经常有需要显示图片下载进度的需求,Universal-Image-Loader当然也提供这样的功能,只需要在displayImage()方法中传入ImageLoadingProgressListener接口就行了,代码如下

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. imageLoader.displayImage(imageUrl, mImageView, options, new SimpleImageLoadingListener(), new ImageLoadingProgressListener() {  
  2.               
  3.             @Override  
  4.             public void onProgressUpdate(String imageUri, View view, int current,  
  5.                     int total) {  
  6.                   
  7.             }  
  8.         });  
由于displayImage()方法中带ImageLoadingProgressListener参数的方法都有带ImageLoadingListener参数,所以我这里直接new 一个SimpleImageLoadingListener,然后我们就可以在回调方法onProgressUpdate()得到图片的加载进度。


加载其他来源的图片


使用Universal-Image-Loader框架不仅可以加载网络图片,还可以加载sd卡中的图片,Content provider等,使用也很简单,只是将图片的url稍加的改变下就行了,下面是加载文件系统的图片

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. //显示图片的配置  
  2.         DisplayImageOptions options = new DisplayImageOptions.Builder()  
  3.                 .showImageOnLoading(R.drawable.ic_stub)  
  4.                 .showImageOnFail(R.drawable.ic_error)  
  5.                 .cacheInMemory(true)  
  6.                 .cacheOnDisk(true)  
  7.                 .bitmapConfig(Bitmap.Config.RGB_565)  
  8.                 .build();  
  9.           
  10.         final ImageView mImageView = (ImageView) findViewById(R.id.image);  
  11.         String imagePath = "/mnt/sdcard/image.png";  
  12.         String imageUrl = Scheme.FILE.wrap(imagePath);  
  13.           
  14. //      String imageUrl = "http://img.my.csdn.net/uploads/201309/01/1378037235_7476.jpg";  
  15.           
  16.         imageLoader.displayImage(imageUrl, mImageView, options);  
当然还有来源于Content provider,drawable,assets中,使用的时候也很简单,我们只需要给每个图片来源的地方加上Scheme包裹起来(Content provider除外),然后当做图片的url传递到imageLoader中,Universal-Image-Loader框架会根据不同的Scheme获取到输入流

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. //图片来源于Content provider  
  2.         String contentprividerUrl = "content://media/external/audio/albumart/13";  
  3.           
  4.         //图片来源于assets  
  5.         String assetsUrl = Scheme.ASSETS.wrap("image.png");  
  6.           
  7.         //图片来源于  
  8.         String drawableUrl = Scheme.DRAWABLE.wrap("R.drawable.image");  


GirdView,ListView加载图片


相信大部分人都是使用GridView,ListView来显示大量的图片,而当我们快速滑动GridView,ListView,我们希望能停止图片的加载,而在GridView,ListView停止滑动的时候加载当前界面的图片,这个框架当然也提供这个功能,使用起来也很简单,它提供了PauseOnScrollListener这个类来控制ListView,GridView滑动过程中停止去加载图片,该类使用的是代理模式

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. listView.setOnScrollListener(new PauseOnScrollListener(imageLoader, pauseOnScroll, pauseOnFling));  
  2.         gridView.setOnScrollListener(new PauseOnScrollListener(imageLoader, pauseOnScroll, pauseOnFling));  
第一个参数就是我们的图片加载对象ImageLoader, 第二个是控制是否在滑动过程中暂停加载图片,如果需要暂停传true就行了,第三个参数控制猛的滑动界面的时候图片是否加载


OutOfMemoryError


虽然这个框架有很好的缓存机制,有效的避免了OOM的产生,一般的情况下产生OOM的概率比较小,但是并不能保证OutOfMemoryError永远不发生,这个框架对于OutOfMemoryError做了简单的catch,保证我们的程序遇到OOM而不被crash掉,但是如果我们使用该框架经常发生OOM,我们应该怎么去改善呢?

  • 减少线程池中线程的个数,在ImageLoaderConfiguration中的(.threadPoolSize)中配置,推荐配置1-5
  • 在DisplayImageOptions选项中配置bitmapConfig为Bitmap.Config.RGB_565,因为默认是ARGB_8888, 使用RGB_565会比使用ARGB_8888少消耗2倍的内存
  • 在ImageLoaderConfiguration中配置图片的内存缓存为memoryCache(new WeakMemoryCache()) 或者不使用内存缓存
  • 在DisplayImageOptions选项中设置.imageScaleType(ImageScaleType.IN_SAMPLE_INT)或者imageScaleType(ImageScaleType.EXACTLY)

通过上面这些,相信大家对Universal-Image-Loader框架的使用已经非常的了解了,我们在使用该框架的时候尽量的使用displayImage()方法去加载图片,loadImage()是将图片对象回调到ImageLoadingListener接口的onLoadingComplete()方法中,需要我们手动去设置到ImageView上面,displayImage()方法中,对ImageView对象使用的是Weak references,方便垃圾回收器回收ImageView对象,如果我们要加载固定大小的图片的时候,使用loadImage()方法需要传递一个ImageSize对象,而displayImage()方法会根据ImageView对象的测量值,或者android:layout_width and android:layout_height设定的值,或者android:maxWidth and/or android:maxHeight设定的值来裁剪图片

作者:tianhongfan10106 发表于2016/11/26 22:54:36 原文链接
阅读:17 评论:0 查看评论

Swift3.0学习笔记-Protocols

$
0
0

https://developer.apple.com/library/prerelease/content/documentation/Swift/Conceptual/Swift_Programming_Language/Protocols.html#//apple_ref/doc/uid/TP40014097-CH25-ID267


       Protocol在Swift中的作用就是接口, Protocol中可以声明若干个方法,但没有函数体; protocol跟Java interface关键字的作用是完全一样的。 Swift protocol跟Java interface的区别是interface可以给成员变量赋值、而protocol对成员变量只能声明set/get方法。

       前文提到Swift只支持单继承, 但可以实现若干个protocol


Swift声明protocol(即接口)的语法:

protocol SomeProtocol {
    //声明函数
}


结构体可以实现一个或多个protocol(即接口):

struct SomeStructure: FirstProtocol, AnotherProtocol {
    // 结构体实现protocol
}

类同样可以实现一个或多个protocol, 跟Java的extends、implements关键字不同的是, Swift在继承类实现接口时只用逗号分隔,语法如下:

class SomeClass: SomeSuperclass, FirstProtocol, AnotherProtocol {
    // 类定义
}
    SomeClass继承于SomeSuperclass,并实现了FirstProtocol和AnotherProtocol。


成员属性:

       protocol可以声明成员变量或者静态成员变量,但不能对成员变量赋值, 只能声明其get或set方法(注意仅仅声明,没有实现)。 成员变量可以声明set/get方法或者get方法, 但不能只声明set方法;即成员变量是可读写或者只读变量。PS:Xcode会给出提示。
protocol SomeProtocol {
    var mustBeSettable: Int { get set }   //可读写
    var doesNotNeedToBeSettable: Int { get }  //只读
}

       对于静态变量,当类去实现protocol时可以用class或者static声明为静态变量。
protocol AnotherProtocol {
    static var someTypeProperty: Int { get set }  //声明为可读写静态属性
}

protocol FullyNamed {
    var fullName: String { get }  //声明为只读属性,属性类型为String
}
注意: 使用结构体实现protocol时,Xcode会自动加入其属性; 使用类型实现protocol时,Xcode会自动加入该属性并赋初值。
struct Person: FullyNamed {
    var fullName: String
}

class PersonExt: FullyNamed {
    var fullName: String = ""
}
     在实现protocol FullyNamed后, 结构体Person和类PersonExt必须包含protocol声明的成员属性。

class StarShip: FullyNamed {
    var prefix: String?   //Optional类型
    var name: String   //字符串类
    init(name: String, prefix: String? = nil) {  //跟其它语言一样当后面参数有默认值时,在调用时可以省略
        self.name = name
        self.prefix = prefix
    }
    
    var fullName: String {  //protocol要求返回fullName的值
        return (prefix != nil ? prefix! + " " : "") + name  //三目运算符跟Java的用法一致!
    }
}

var objNc = StarShip(name: "Enterprise", prefix: "USS")
var objNc1 = StarShip(name: "Company")
类StarShip实现了FullyNamed, 即要对fullName返回String类型的值, 声明了成员变量prefix、name和构造函数init。 因为init对参数prefix赋了初值nil,所有在调用时可以省略。(注意:Swift的String肯定有值,String?可能为nil或者字符串; 所以String?类似于Java的String)


成员方法: Swift同Java一样对方法的访问权限设置了关键字: public/internal/private,  作用类似于Java修饰方法的public/protected/private。在Swift语法中,函数访问级别默认为internal, 声明函数时可以省略internal关键字。
protocol SomeProtocol {
    static func someTypeMethod()
}
protocol RandomNumberGenerator {
    func random() -> Double
}
      跟Java interface一样, Swift声明函数但不带函数体, 在实现类中定义函数功能。
protocol FullyNamed {
    var fullName: String { get }
    
    func getName() -> String
    
    static func getFullName() -> String
}

class PersonExt: FullyNamed {
    static func getFullName() -> String {  //函数访问权限都是internal,所有可以省略internal
        return "PersonExt full"
    }
    
    internal func getName() -> String {  // 访问权限public/internal/private中默认的internal
        return "personExt name"
    }
    var fullName: String = ""  //等号后是字符串,就是fullName的get方法实现。
    
}

var person = PersonExt()
print(person.getName())  //通过实例调用方法
print(PersonExt.getFullName())  //通过类调用
输出:

personExt name

PersonExt full



修改成员属性的接口函数:
在《Swift3.0学习笔记-Functions》中提到Swift的函数体默认不能修改成员属性, 声明函数时必须添加前缀mutating关键字才可以, 该特性同样适用于protocol。  如果想在接口函数中修改成员属性, 那么该接口函数一定要添加mutating前缀
    注意: 如果类实现protocol, 那么函数不用带mutating前缀, 默认可以修改成员属性; 结构体和枚举类型如果要在接口函数中修改成员属性,那么必须添加mutating前缀。基本语法如下:
protocol Togglable {
    mutating func toggle()    //如果结构体和枚举要实现Togglable,那么必须添加mutating前缀
}

示例代码:Shop的buySomeThing方法没有mutating前缀,但可以修改DoShop类的amount属性;Togglable的toggle方法带有mutating前缀,可以修改name属性。
protocol Togglable {
    mutating func toggle()  //测试修改成员属性
}

struct TestStruct: Togglable {
    mutating internal func toggle() {
        name = "TestStruct modify success"
    }
    var name: String
}

protocol Shop {
    func buySomeThing()
}

class DoShop: Shop {
    func buySomeThing() {
        amount -= 50    //测试修改成员属性amount
    }

    var amount: Int = 100
    
}

var objStruct = TestStruct(name: "TestStruct")
var objShop = DoShop()
objStruct.toggle()  //修改成员属性name的值
print(objStruct.name)
objShop.buySomeThing()  //修改成员变量amount
print("amount: \(objShop.amount)")
输出:

TestStruct modify success

amount: 50



声明构造函数接口:在protocol声明若干个init方法, 在类实现Protocol时定义函数体。 
protocol SomeProtocol {
    init(someParameter: Int)
}

class SomeClass: SomeProtocol {
    required init(someParameter: Int) {
        // initializer implementation goes here
    }
}
    在类实现构造函数时必须添加required关键字;仅仅在类被声明为final类型, 那么可以省略required关键字。

如果基类、Protocol声明了一模一样的构造函数, 派生类在继承基类并实现接口时会怎样? PS:同样场景在Java里寻址会找到接口函数。

protocol SomeProtocol {
    init()
}
 
class SomeSuperClass {
    init() {
        // initializer implementation goes here
    }
}
 
class SomeSubClass: SomeSuperClass, SomeProtocol {
    // "required" from SomeProtocol conformance; "override" from SomeSuperClass
    required override init() {
        // initializer implementation goes here
    }
}
说明:感觉Swift的这个语法就是和稀泥, 基类和protocol的init都要兼顾, 在派生类里构造函数添加前缀required override。

现在是重点了, 我们知道Java里的interface是将一个接口的引用作为参数传给一个对象。  在Swift里,protocol可以是类成员变量的类型。

class DoShop: Shop {
    required init(amount: Int) {
        self.amount = amount
    }

    func buySomeThing() {
        amount -= 50    //测试修改成员属性amount
    }

    var toggle: Togglable   //protocol类型
    var amount: Int = 100
}

扩展实现接口, 即Extension实现protocol。

protocol TextRepresentable {
    var textualDescription: String { get }
}

extension Dice: TextRepresentable {   //删掉protocol名称并实现接口函数也可以!
    var textualDescription: String {
        return "A \(sides)-sided dice"
    }
}

Protocol支持多继承, 类要实现所有protocol里的函数。语法和示例代码:

protocol InheritingProtocol: SomeProtocol, AnotherProtocol {
    // protocol definition goes here
}
protocol A {
    func methodA()
}
protocol B {
    func methodB()
}
protocol C: A,B {
    func methodC()
}
class MulTest: C {
    internal func methodB() {
        ...
    }

    internal func methodA() {
       ...
    }

    internal func methodC() {
        ...
    }
}


前面提到protocol适用于类、结构体和枚举, 那么如果只想给类用该怎么办呢?

Swift支持仅适用于类的Protocol, 语法如下:

protocol SomeClassOnlyProtocol: class, SomeInheritedProtocol {
    // class-only protocol definition goes here
}
示例代码:

protocol A {
    func methodA()
}
protocol B: class {
    func methodB()
}
protocol C: class,A,B {
    func methodC()
}
class MulTest: C {
    internal func methodB() {
        ...
    }

    internal func methodA() {
       ...
    }

    internal func methodC() {
       ...
    }
}


复合Protocol, 因为protocol可以继承若干个protocol, 那么如何判断它到底继承于哪些protocol呢?  Swift提供了关键字&。

protocol Named {
    var name: String { get }
}
protocol Aged {
    var age: Int { get }
}
struct Person: Named, Aged {
    var name: String
    var age: Int
}
func wishHappyBirthday(to celebrator: Named & Aged) { //参数是  Named & Aged 类型
    print("Happy birthday, \(celebrator.name), you're \(celebrator.age)!")
}
let birthdayPerson = Person(name: "Malcolm", age: 21) //Person类实现了Named和Aged的方法
wishHappyBirthday(to: birthdayPerson)
// 输出 "Happy birthday, Malcolm, you're 21!"
说明:语法跟Java类似, 函数参数类型为接口, 但在调用函数时传递的是对象引用(实现了接口)。


类型判断: is、as?和as!关键字同样适用于Protocol,语法跟判断类是一样的。


小结:

Swift的protocol可以理解为接口类, 它主要作用就是声明它的能力,即声明若干个函数, 作用类似于Java的interface关键字。

1、 Protocol默认适用于类、结构体和枚举, 添加后缀class后只给类用;

2、类可以实现若干个protocol;

3、protocol可以作为类成员变量的参数类型(同Java的回调用法);

4、Swift支持多个protocol作为参数类型;

5、protocol声明init函数后,在类中实现该init函数时必须带required前缀。




作者:brycegao321 发表于2016/11/26 23:14:15 原文链接
阅读:25 评论:0 查看评论
Viewing all 5930 articles
Browse latest View live


<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>