精华内容
下载资源
问答
  • “刷脸”,又叫人脸识别解屏,使用步骤:安装完这款软件之后,在这款软件的主界面点击设置,设置您本人的人脸照片,自己照一张照片保存进去,设置成功后,让其后台运行,手机开屏的话就会出现比对界面,再次拍一下你...
  • Android开发实现人脸识别,能够识别出人脸的性别、年龄、肤色、颜值以及笑容
  • Android 人脸识别功能源码
  • 冰淇淋三明治Android4.0界面截图赏析Android 4.0刚刚和谷歌新一代手机一共面世了,那么究竟Android 4.0界面有什么变化呢?下面就让我们来看一下新鲜出炉的系统截图吧。[详细]...

    5224d480f1c8a1bbae0be8c9e536c6b6.png

    冰淇淋三明治Android4.0界面截图赏析

    Android 4.0刚刚和谷歌新一代手机一共面世了,那么究竟Android 4.0界面有什么变化呢?下面就让我们来看一下新鲜出炉的系统截图吧。[详细]

    http://img4.cache.netease.com/photo/0011/2011-10-19/7GOB6DB92ERI0011.jpg

    http://img3.cache.netease.com/photo/0011/2011-10-19/t_7GOB6DB92ERI0011.jpg

    fa62810e4ee419cfedb570d6a5c0ac67.png

    冰淇淋三明治Android4.0界面截图赏析

    Android 4.0刚刚和谷歌新一代手机一共面世了,那么究竟Android 4.0界面有什么变化呢?下面就让我们来看一下新鲜出炉的系统截图吧。[详细]

    http://img3.cache.netease.com/photo/0011/2011-10-19/7GOB6DN52ERI0011.jpg

    http://img3.cache.netease.com/photo/0011/2011-10-19/t_7GOB6DN52ERI0011.jpg

    30108c99ad34313bab948890987c9f55.png

    冰淇淋三明治Android4.0界面截图赏析

    Android 4.0刚刚和谷歌新一代手机一共面世了,那么究竟Android 4.0界面有什么变化呢?下面就让我们来看一下新鲜出炉的系统截图吧。[详细]

    http://img4.cache.netease.com/photo/0011/2011-10-19/7GOB6E912ERI0011.jpg

    http://img3.cache.netease.com/photo/0011/2011-10-19/t_7GOB6E912ERI0011.jpg

    0e368bd03c1848f7c7e47cee125652f3.png

    冰淇淋三明治Android4.0界面截图赏析

    Android 4.0刚刚和谷歌新一代手机一共面世了,那么究竟Android 4.0界面有什么变化呢?下面就让我们来看一下新鲜出炉的系统截图吧。[详细]

    http://img4.cache.netease.com/photo/0011/2011-10-19/7GOB6EMA2ERI0011.jpg

    http://img3.cache.netease.com/photo/0011/2011-10-19/t_7GOB6EMA2ERI0011.jpg

    f6a9e2453caa065ee6236428b5c34994.png

    冰淇淋三明治Android4.0界面截图赏析

    Android 4.0刚刚和谷歌新一代手机一共面世了,那么究竟Android 4.0界面有什么变化呢?下面就让我们来看一下新鲜出炉的系统截图吧。[详细]

    http://img4.cache.netease.com/photo/0011/2011-10-19/7GOB6F4L2ERI0011.jpg

    http://img4.cache.netease.com/photo/0011/2011-10-19/t_7GOB6F4L2ERI0011.jpg

    532738ba28ea10c4a22db71d599b957f.png

    冰淇淋三明治Android4.0界面截图赏析

    Android 4.0刚刚和谷歌新一代手机一共面世了,那么究竟Android 4.0界面有什么变化呢?下面就让我们来看一下新鲜出炉的系统截图吧。[详细]

    http://img4.cache.netease.com/photo/0011/2011-10-19/7GOB6FNB2ERI0011.jpg

    http://img3.cache.netease.com/photo/0011/2011-10-19/t_7GOB6FNB2ERI0011.jpg

    7a6ce8ad795db742e27058a0e901200c.png

    冰淇淋三明治Android4.0界面截图赏析

    Android 4.0刚刚和谷歌新一代手机一共面世了,那么究竟Android 4.0界面有什么变化呢?下面就让我们来看一下新鲜出炉的系统截图吧。[详细]

    http://img4.cache.netease.com/photo/0011/2011-10-19/7GOB6G4J2ERI0011.jpg

    http://img4.cache.netease.com/photo/0011/2011-10-19/t_7GOB6G4J2ERI0011.jpg

    11ca438211aab66273d172865ff255f8.png

    冰淇淋三明治Android4.0界面截图赏析

    Android 4.0刚刚和谷歌新一代手机一共面世了,那么究竟Android 4.0界面有什么变化呢?下面就让我们来看一下新鲜出炉的系统截图吧。[详细]

    http://img3.cache.netease.com/photo/0011/2011-10-19/7GOB6GJK2ERI0011.jpg

    http://img4.cache.netease.com/photo/0011/2011-10-19/t_7GOB6GJK2ERI0011.jpg

    fc33e47459c21101bec6b7267a5812f5.png

    冰淇淋三明治Android4.0界面截图赏析

    Android 4.0刚刚和谷歌新一代手机一共面世了,那么究竟Android 4.0界面有什么变化呢?下面就让我们来看一下新鲜出炉的系统截图吧。[详细]

    http://img3.cache.netease.com/photo/0011/2011-10-19/7GOB6H5N2ERI0011.jpg

    http://img4.cache.netease.com/photo/0011/2011-10-19/t_7GOB6H5N2ERI0011.jpg

    b451b8cbc24f28ecf95027b581a82cfb.png

    冰淇淋三明治Android4.0界面截图赏析

    Android 4.0刚刚和谷歌新一代手机一共面世了,那么究竟Android 4.0界面有什么变化呢?下面就让我们来看一下新鲜出炉的系统截图吧。[详细]

    http://img4.cache.netease.com/photo/0011/2011-10-19/7GOB6HKB2ERI0011.jpg

    http://img4.cache.netease.com/photo/0011/2011-10-19/t_7GOB6HKB2ERI0011.jpg

    381fb620b01e66531503443502d6679c.png

    冰淇淋三明治Android4.0界面截图赏析

    Android 4.0刚刚和谷歌新一代手机一共面世了,那么究竟Android 4.0界面有什么变化呢?下面就让我们来看一下新鲜出炉的系统截图吧。[详细]

    http://img3.cache.netease.com/photo/0011/2011-10-19/7GOB6I3B2ERI0011.jpg

    http://img4.cache.netease.com/photo/0011/2011-10-19/t_7GOB6I3B2ERI0011.jpg

    512de642e1f2178a06e56d7d9da039ba.png

    冰淇淋三明治Android4.0界面截图赏析

    Android 4.0刚刚和谷歌新一代手机一共面世了,那么究竟Android 4.0界面有什么变化呢?下面就让我们来看一下新鲜出炉的系统截图吧。[详细]

    http://img4.cache.netease.com/photo/0011/2011-10-19/7GOB6IKU2ERI0011.jpg

    http://img3.cache.netease.com/photo/0011/2011-10-19/t_7GOB6IKU2ERI0011.jpg

    310e4cdf6a24a84e465f7d79fdabc25c.png

    冰淇淋三明治Android4.0界面截图赏析

    Android 4.0刚刚和谷歌新一代手机一共面世了,那么究竟Android 4.0界面有什么变化呢?下面就让我们来看一下新鲜出炉的系统截图吧。[详细]

    http://img3.cache.netease.com/photo/0011/2011-10-19/7GOB6J2U2ERI0011.jpg

    http://img3.cache.netease.com/photo/0011/2011-10-19/t_7GOB6J2U2ERI0011.jpg

    35e5d45c51317aa4bd2a9d42f6ba713c.png

    冰淇淋三明治Android4.0界面截图赏析

    Android 4.0刚刚和谷歌新一代手机一共面世了,那么究竟Android 4.0界面有什么变化呢?下面就让我们来看一下新鲜出炉的系统截图吧。[详细]

    http://img4.cache.netease.com/photo/0011/2011-10-19/7GOB6JJ72ERI0011.jpg

    http://img3.cache.netease.com/photo/0011/2011-10-19/t_7GOB6JJ72ERI0011.jpg

    abae192a1b295e77c649b2c76926ed11.png

    冰淇淋三明治Android4.0界面截图赏析

    Android 4.0刚刚和谷歌新一代手机一共面世了,那么究竟Android 4.0界面有什么变化呢?下面就让我们来看一下新鲜出炉的系统截图吧。[详细]

    http://img4.cache.netease.com/photo/0011/2011-10-19/7GOB6K4R2ERI0011.jpg

    http://img4.cache.netease.com/photo/0011/2011-10-19/t_7GOB6K4R2ERI0011.jpg

    c8ce24c8316a6167947896d02f4eea85.png

    冰淇淋三明治Android4.0界面截图赏析

    Android 4.0刚刚和谷歌新一代手机一共面世了,那么究竟Android 4.0界面有什么变化呢?下面就让我们来看一下新鲜出炉的系统截图吧。[详细]

    http://img3.cache.netease.com/photo/0011/2011-10-19/7GOB6KJC2ERI0011.jpg

    http://img3.cache.netease.com/photo/0011/2011-10-19/t_7GOB6KJC2ERI0011.jpg

    f1e61b19aea34bbf93a023a060a8a1e2.png

    冰淇淋三明治Android4.0界面截图赏析

    Android 4.0刚刚和谷歌新一代手机一共面世了,那么究竟Android 4.0界面有什么变化呢?下面就让我们来看一下新鲜出炉的系统截图吧。[详细]

    http://img3.cache.netease.com/photo/0011/2011-10-19/7GOB6L3T2ERI0011.jpg

    http://img4.cache.netease.com/photo/0011/2011-10-19/t_7GOB6L3T2ERI0011.jpg

    d08a9586a8a9f8d46995b6792b8d12ff.png

    冰淇淋三明治Android4.0界面截图赏析

    Android 4.0刚刚和谷歌新一代手机一共面世了,那么究竟Android 4.0界面有什么变化呢?下面就让我们来看一下新鲜出炉的系统截图吧。[详细]

    http://img4.cache.netease.com/photo/0011/2011-10-19/7GOB6LJO2ERI0011.jpg

    http://img3.cache.netease.com/photo/0011/2011-10-19/t_7GOB6LJO2ERI0011.jpg

    3b76dd2363fc6fecfaa42be376d9567c.png

    冰淇淋三明治Android4.0界面截图赏析

    Android 4.0刚刚和谷歌新一代手机一共面世了,那么究竟Android 4.0界面有什么变化呢?下面就让我们来看一下新鲜出炉的系统截图吧。[详细]

    http://img4.cache.netease.com/photo/0011/2011-10-19/7GOB6MHC2ERI0011.jpg

    http://img3.cache.netease.com/photo/0011/2011-10-19/t_7GOB6MHC2ERI0011.jpg

    ad1f8a7987fdbeb0a81a048709ab88c9.png

    冰淇淋三明治Android4.0界面截图赏析

    Android 4.0刚刚和谷歌新一代手机一共面世了,那么究竟Android 4.0界面有什么变化呢?下面就让我们来看一下新鲜出炉的系统截图吧。[详细]

    http://img3.cache.netease.com/photo/0011/2011-10-19/7GOB6N1E2ERI0011.jpg

    http://img3.cache.netease.com/photo/0011/2011-10-19/t_7GOB6N1E2ERI0011.jpg

    7abc99d1913fde2eeafd057b72986483.png

    冰淇淋三明治Android4.0界面截图赏析

    Android 4.0刚刚和谷歌新一代手机一共面世了,那么究竟Android 4.0界面有什么变化呢?下面就让我们来看一下新鲜出炉的系统截图吧。[详细]

    http://img4.cache.netease.com/photo/0011/2011-10-19/7GOB6NJT2ERI0011.jpg

    http://img4.cache.netease.com/photo/0011/2011-10-19/t_7GOB6NJT2ERI0011.jpg

    2ed182de05a3457d06230f6277c546f6.png

    冰淇淋三明治Android4.0界面截图赏析

    Android 4.0刚刚和谷歌新一代手机一共面世了,那么究竟Android 4.0界面有什么变化呢?下面就让我们来看一下新鲜出炉的系统截图吧。[详细]

    http://img3.cache.netease.com/photo/0011/2011-10-19/7GOB6O5M2ERI0011.jpg

    http://img4.cache.netease.com/photo/0011/2011-10-19/t_7GOB6O5M2ERI0011.jpg

    729c93f4bd73b2a35ec6caf6f3d6fdf1.png

    冰淇淋三明治Android4.0界面截图赏析

    Android 4.0刚刚和谷歌新一代手机一共面世了,那么究竟Android 4.0界面有什么变化呢?下面就让我们来看一下新鲜出炉的系统截图吧。[详细]

    http://img4.cache.netease.com/photo/0011/2011-10-19/7GOB6OK72ERI0011.jpg

    http://img3.cache.netease.com/photo/0011/2011-10-19/t_7GOB6OK72ERI0011.jpg

    de98b9c5ebf6d3d0d85fe56d2a8b4402.png

    冰淇淋三明治Android4.0界面截图赏析

    Android 4.0刚刚和谷歌新一代手机一共面世了,那么究竟Android 4.0界面有什么变化呢?下面就让我们来看一下新鲜出炉的系统截图吧。[详细]

    http://img3.cache.netease.com/photo/0011/2011-10-19/7GOB6P622ERI0011.jpg

    http://img3.cache.netease.com/photo/0011/2011-10-19/t_7GOB6P622ERI0011.jpg

    dc46ca1c032015307a882f155dee80cf.png

    冰淇淋三明治Android4.0界面截图赏析

    Android 4.0刚刚和谷歌新一代手机一共面世了,那么究竟Android 4.0界面有什么变化呢?下面就让我们来看一下新鲜出炉的系统截图吧。[详细]

    http://img3.cache.netease.com/photo/0011/2011-10-19/7GOB6PN92ERI0011.jpg

    http://img3.cache.netease.com/photo/0011/2011-10-19/t_7GOB6PN92ERI0011.jpg

    1c70f369cefbcc16a6813750a93cb105.png

    冰淇淋三明治Android4.0界面截图赏析

    Android 4.0刚刚和谷歌新一代手机一共面世了,那么究竟Android 4.0界面有什么变化呢?下面就让我们来看一下新鲜出炉的系统截图吧。[详细]

    http://img4.cache.netease.com/photo/0011/2011-10-19/7GOB6Q342ERI0011.jpg

    http://img3.cache.netease.com/photo/0011/2011-10-19/t_7GOB6Q342ERI0011.jpg

    73b26ce176c3ecdb36e9bf63c3c90b39.png

    冰淇淋三明治Android4.0界面截图赏析

    Android 4.0刚刚和谷歌新一代手机一共面世了,那么究竟Android 4.0界面有什么变化呢?下面就让我们来看一下新鲜出炉的系统截图吧。[详细]

    http://img4.cache.netease.com/photo/0011/2011-10-19/7GOB6QJG2ERI0011.jpg

    http://img4.cache.netease.com/photo/0011/2011-10-19/t_7GOB6QJG2ERI0011.jpg

    4bad5fab57981e1322e258e9265e9781.png

    冰淇淋三明治Android4.0界面截图赏析

    Android 4.0刚刚和谷歌新一代手机一共面世了,那么究竟Android 4.0界面有什么变化呢?下面就让我们来看一下新鲜出炉的系统截图吧。[详细]

    http://img3.cache.netease.com/photo/0011/2011-10-19/7GOB6R1P2ERI0011.jpg

    http://img4.cache.netease.com/photo/0011/2011-10-19/t_7GOB6R1P2ERI0011.jpg

    dc846ffb1a96709a39ea08ae79d34b60.png

    冰淇淋三明治Android4.0界面截图赏析

    Android 4.0刚刚和谷歌新一代手机一共面世了,那么究竟Android 4.0界面有什么变化呢?下面就让我们来看一下新鲜出炉的系统截图吧。[详细]

    http://img3.cache.netease.com/photo/0011/2011-10-19/7GOB6S8M2ERI0011.jpg

    http://img3.cache.netease.com/photo/0011/2011-10-19/t_7GOB6S8M2ERI0011.jpg

    015a19bee3686ee02932aab8100c3542.png

    冰淇淋三明治Android4.0界面截图赏析

    Android 4.0刚刚和谷歌新一代手机一共面世了,那么究竟Android 4.0界面有什么变化呢?下面就让我们来看一下新鲜出炉的系统截图吧。[详细]

    http://img4.cache.netease.com/photo/0011/2011-10-19/7GOB6SPA2ERI0011.jpg

    http://img3.cache.netease.com/photo/0011/2011-10-19/t_7GOB6SPA2ERI0011.jpg

    9453cc322eb77492c6465ebcbf0ec61f.png

    冰淇淋三明治Android4.0界面截图赏析

    Android 4.0刚刚和谷歌新一代手机一共面世了,那么究竟Android 4.0界面有什么变化呢?下面就让我们来看一下新鲜出炉的系统截图吧。[详细]

    http://img3.cache.netease.com/photo/0011/2011-10-19/7GOB6T672ERI0011.jpg

    http://img3.cache.netease.com/photo/0011/2011-10-19/t_7GOB6T672ERI0011.jpg

    0428c69fcc5f50edd002e9604bf8c6eb.png

    冰淇淋三明治Android4.0界面截图赏析

    Android 4.0刚刚和谷歌新一代手机一共面世了,那么究竟Android 4.0界面有什么变化呢?下面就让我们来看一下新鲜出炉的系统截图吧。[详细]

    http://img4.cache.netease.com/photo/0011/2011-10-19/7GOB6TLS2ERI0011.jpg

    http://img4.cache.netease.com/photo/0011/2011-10-19/t_7GOB6TLS2ERI0011.jpg

    8e3de61b659e8abc041c1d0cff8251b8.png

    冰淇淋三明治Android4.0界面截图赏析

    Android 4.0刚刚和谷歌新一代手机一共面世了,那么究竟Android 4.0界面有什么变化呢?下面就让我们来看一下新鲜出炉的系统截图吧。[详细]

    http://img3.cache.netease.com/photo/0011/2011-10-19/7GOB6U9L2ERI0011.jpg

    http://img4.cache.netease.com/photo/0011/2011-10-19/t_7GOB6U9L2ERI0011.jpg

    6e26bb0bdff7aa8e4f2160a6445ab637.png

    冰淇淋三明治Android4.0界面截图赏析

    Android 4.0刚刚和谷歌新一代手机一共面世了,那么究竟Android 4.0界面有什么变化呢?下面就让我们来看一下新鲜出炉的系统截图吧。[详细]

    http://img4.cache.netease.com/photo/0011/2011-10-19/7GOB6ULJ2ERI0011.jpg

    http://img3.cache.netease.com/photo/0011/2011-10-19/t_7GOB6ULJ2ERI0011.jpg

    fd188ed566490a4803e2c862901b597a.png

    冰淇淋三明治Android4.0界面截图赏析

    Android 4.0刚刚和谷歌新一代手机一共面世了,那么究竟Android 4.0界面有什么变化呢?下面就让我们来看一下新鲜出炉的系统截图吧。[详细]

    http://img4.cache.netease.com/photo/0011/2011-10-19/7GOB6V9E2ERI0011.jpg

    http://img3.cache.netease.com/photo/0011/2011-10-19/t_7GOB6V9E2ERI0011.jpg

    33442f97fe2eade9a506540f136f603b.png

    冰淇淋三明治Android4.0界面截图赏析

    Android 4.0刚刚和谷歌新一代手机一共面世了,那么究竟Android 4.0界面有什么变化呢?下面就让我们来看一下新鲜出炉的系统截图吧。[详细]

    http://img4.cache.netease.com/photo/0011/2011-10-19/7GOB6VQF2ERI0011.jpg

    http://img3.cache.netease.com/photo/0011/2011-10-19/t_7GOB6VQF2ERI0011.jpg

    2654c8004d8ca4dd9ef8b0906dbcb0d9.png

    冰淇淋三明治Android4.0界面截图赏析

    Android 4.0刚刚和谷歌新一代手机一共面世了,那么究竟Android 4.0界面有什么变化呢?下面就让我们来看一下新鲜出炉的系统截图吧。[详细]

    http://img3.cache.netease.com/photo/0011/2011-10-19/7GOB707V2ERI0011.jpg

    http://img3.cache.netease.com/photo/0011/2011-10-19/t_7GOB707V2ERI0011.jpg

    841e0256b5c6a12b35688f926c66054a.png

    冰淇淋三明治Android4.0界面截图赏析

    Android 4.0刚刚和谷歌新一代手机一共面世了,那么究竟Android 4.0界面有什么变化呢?下面就让我们来看一下新鲜出炉的系统截图吧。[详细]

    http://img4.cache.netease.com/photo/0011/2011-10-19/7GOB70O72ERI0011.jpg

    http://img3.cache.netease.com/photo/0011/2011-10-19/t_7GOB70O72ERI0011.jpg

    b57f77206c5ba63e41247ba867a96ee6.png

    冰淇淋三明治Android4.0界面截图赏析

    Android 4.0刚刚和谷歌新一代手机一共面世了,那么究竟Android 4.0界面有什么变化呢?下面就让我们来看一下新鲜出炉的系统截图吧。[详细]

    http://img3.cache.netease.com/photo/0011/2011-10-19/7GOB718I2ERI0011.jpg

    http://img3.cache.netease.com/photo/0011/2011-10-19/t_7GOB718I2ERI0011.jpg

    02891489b0bc92e6b295c224443c2e1d.png

    冰淇淋三明治Android4.0界面截图赏析

    Android 4.0刚刚和谷歌新一代手机一共面世了,那么究竟Android 4.0界面有什么变化呢?下面就让我们来看一下新鲜出炉的系统截图吧。[详细]

    http://img3.cache.netease.com/photo/0011/2011-10-19/7GOB71QN2ERI0011.jpg

    http://img4.cache.netease.com/photo/0011/2011-10-19/t_7GOB71QN2ERI0011.jpg

    9702ad4172a34839dd1f1b460659f637.png

    冰淇淋三明治Android4.0界面截图赏析

    Android 4.0刚刚和谷歌新一代手机一共面世了,那么究竟Android 4.0界面有什么变化呢?下面就让我们来看一下新鲜出炉的系统截图吧。[详细]

    http://img3.cache.netease.com/photo/0011/2011-10-19/7GOB72D02ERI0011.jpg

    http://img4.cache.netease.com/photo/0011/2011-10-19/t_7GOB72D02ERI0011.jpg

    25769fba9374d8be71c1aff21dbd9536.png

    冰淇淋三明治Android4.0界面截图赏析

    Android 4.0刚刚和谷歌新一代手机一共面世了,那么究竟Android 4.0界面有什么变化呢?下面就让我们来看一下新鲜出炉的系统截图吧。[详细]

    http://img4.cache.netease.com/photo/0011/2011-10-19/7GOB72U82ERI0011.jpg

    http://img4.cache.netease.com/photo/0011/2011-10-19/t_7GOB72U82ERI0011.jpg

    622cfdd16ff72a6438c1963fa7b9e6ee.png

    冰淇淋三明治Android4.0界面截图赏析

    Android 4.0刚刚和谷歌新一代手机一共面世了,那么究竟Android 4.0界面有什么变化呢?下面就让我们来看一下新鲜出炉的系统截图吧。[详细]

    http://img4.cache.netease.com/photo/0011/2011-10-19/7GOB73CU2ERI0011.jpg

    http://img4.cache.netease.com/photo/0011/2011-10-19/t_7GOB73CU2ERI0011.jpg

    3850d70e544195162bfb4e7e9f05b5a4.png

    冰淇淋三明治Android4.0界面截图赏析

    Android 4.0刚刚和谷歌新一代手机一共面世了,那么究竟Android 4.0界面有什么变化呢?下面就让我们来看一下新鲜出炉的系统截图吧。[详细]

    http://img3.cache.netease.com/photo/0011/2011-10-19/7GOB73RC2ERI0011.jpg

    http://img3.cache.netease.com/photo/0011/2011-10-19/t_7GOB73RC2ERI0011.jpg

    e0a14c58b29a6efbada00fea060dc994.png

    冰淇淋三明治Android4.0界面截图赏析

    Android 4.0刚刚和谷歌新一代手机一共面世了,那么究竟Android 4.0界面有什么变化呢?下面就让我们来看一下新鲜出炉的系统截图吧。[详细]

    http://img3.cache.netease.com/photo/0011/2011-10-19/7GOB74E92ERI0011.jpg

    http://img4.cache.netease.com/photo/0011/2011-10-19/t_7GOB74E92ERI0011.jpg

    6ea9fde8e5473264a958da6a7f80c301.png

    冰淇淋三明治Android4.0界面截图赏析

    Android 4.0刚刚和谷歌新一代手机一共面世了,那么究竟Android 4.0界面有什么变化呢?下面就让我们来看一下新鲜出炉的系统截图吧。[详细]

    http://img3.cache.netease.com/photo/0011/2011-10-19/7GOB74T42ERI0011.jpg

    http://img4.cache.netease.com/photo/0011/2011-10-19/t_7GOB74T42ERI0011.jpg

    32bea23be1f96c1b189244cba445b5db.png

    冰淇淋三明治Android4.0界面截图赏析

    Android 4.0刚刚和谷歌新一代手机一共面世了,那么究竟Android 4.0界面有什么变化呢?下面就让我们来看一下新鲜出炉的系统截图吧。[详细]

    http://img4.cache.netease.com/photo/0011/2011-10-19/7GOB75B02ERI0011.jpg

    http://img4.cache.netease.com/photo/0011/2011-10-19/t_7GOB75B02ERI0011.jpg

    f4965e35ca9cde2e7d96e04343b91fd7.png

    冰淇淋三明治Android4.0界面截图赏析

    Android 4.0刚刚和谷歌新一代手机一共面世了,那么究竟Android 4.0界面有什么变化呢?下面就让我们来看一下新鲜出炉的系统截图吧。[详细]

    http://img3.cache.netease.com/photo/0011/2011-10-19/7GOB75SU2ERI0011.jpg

    http://img3.cache.netease.com/photo/0011/2011-10-19/t_7GOB75SU2ERI0011.jpg

    e2e199aa0b82c26f4acb16d4c6261c2b.png

    冰淇淋三明治Android4.0界面截图赏析

    Android 4.0刚刚和谷歌新一代手机一共面世了,那么究竟Android 4.0界面有什么变化呢?下面就让我们来看一下新鲜出炉的系统截图吧。[详细]

    http://img4.cache.netease.com/photo/0011/2011-10-19/7GOB76BL2ERI0011.jpg

    http://img4.cache.netease.com/photo/0011/2011-10-19/t_7GOB76BL2ERI0011.jpg

    e149083d01e19faf6ebd326ebb9468fa.png

    冰淇淋三明治Android4.0界面截图赏析

    Android 4.0刚刚和谷歌新一代手机一共面世了,那么究竟Android 4.0界面有什么变化呢?下面就让我们来看一下新鲜出炉的系统截图吧。[详细]

    http://img4.cache.netease.com/photo/0011/2011-10-19/7GOB76S62ERI0011.jpg

    http://img4.cache.netease.com/photo/0011/2011-10-19/t_7GOB76S62ERI0011.jpg

    展开全文
  • 主要介绍了Android开发人脸识别登录功能,这个很多公司都在使用,非常流行,今天小编给大家从头到尾做一个案例分享到脚本之家平台,需要的朋友参考下吧
  • 比如说FACE++(旷世)、阿里云、百度云、科大讯飞、云从科技等等,基本上都是通过API调用返回相应的数据,经过资料查询以及体验部分第三方的API,最后我选择了Android 自带的人脸识别的SDK,亲测有效,话不多说,...
  • 公司需要双摄像头录制 demo却不太对 弄得 双摄像头切换识别人脸的demo
  • 人脸识别功能使用源码需要注意的是图片格式必须是RGB_565,其它就没什么了。  
  • 安卓扫脸程序的源代码,能实现人脸1对1,1对多的扫描对比,还有人脸年龄性别检测,无后台数据库。数据存储为手机SD卡空间。
  • 人脸识别,正确率高,99%以上.rar,太多无法一一验证是否可用,程序如果跑不起来需要自调,部分代码功能进行参考学习。
  • 下面是实现细节我们知道在android的代码中已有人脸识别的底层算法代码,而且在framework层也封了调用的API函数extern/neven 目录下是实现人脸识别的算法代码。添加获取照相时预览图片数据,可以在onPreviewFrame回...

    照相时,在预览画面上提示用户人脸的位置,并完成自动对焦等,是个错的应用; 下面是实现细节

    我们知道在android的代码中已有人脸识别的底层算法代码,而且在framework层也封了调用的API函数

    extern/neven 目录下是实现人脸识别的算法代码。

    添加获取照相时预览图片数据,可以在onPreviewFrame回调函数中得。在开始预览的地方,用mCameraDevice.setPreviewCallback(mPreviewCallback);设置预览回调函数。

    import android.media.FaceDetector;

    import android.media.FaceDetector.Face;

    //Harrison add

    private void DrawRectOnFace() {

    if (numberOfFaceDetected != 0) {

    Face mFace1 = mFace[0];

    PointF midPoint = new PointF();

    mFace1.getMidPoint(midPoint);

    if ((Math.abs(mPreMidPoint.x-midPoint.x) < 50) && (Math.abs(mPreMidPoint.y-midPoint.y) < 50)) {

    Log.i("Harrison", "not draw Rect .");

    return ;

    }

    mPreMidPoint.x = midPoint.x;

    mPreMidPoint.y = midPoint.y;

    mFindFaceView.setVisibility(View.VISIBLE);

    } else {

    mPreMidPoint.x = 0;

    mPreMidPoint.y = 0;

    mFindFaceView.clearDraw();

    mFindFaceView.setVisibility(View.GONE);

    return;

    }

    mFindFaceView.drawRects(mFace, numberOfFaceDetected);

    }

    //调用API找人脸,需要import进软件包哦!

    private void FindFacesInBitmap(Bitmap myBitmap) {

    imageWidth = myBitmap.getWidth();

    imageHeight = myBitmap.getHeight();

    Log.i("Harrison", "imageWidth="+imageWidth+",  imageHeight="+imageHeight);

    mFace = new FaceDetector.Face[numberOfFace];

    mFaceDetect = new FaceDetector(imageWidth, imageHeight, numberOfFace);

    numberOfFaceDetected = mFaceDetect.findFaces(myBitmap, mFace);

    Log.i("Harrison", "numberOfFaceDetected="+numberOfFaceDetected);

    }

    private Bitmap rotateMyBitmap(Bitmap bitmap) {

    int width = bitmap.getWidth();

    int height = bitmap.getHeight();

    Matrix matrix = new Matrix();

    matrix.postRotate(90); //椤烘椂閽熸棆杞?0搴︺€?

    Bitmap rotateBitmap = Bitmap.createBitmap(bitmap, 0, 0, width, height, matrix, true);

    return rotateBitmap;

    }

    private Bitmap scaleMyBitmap(Bitmap bitmap) {

    int width = bitmap.getWidth();

    int height = bitmap.getHeight();

    int nWidth = mFindFaceView.getFaceViewWidth();;

    int nHeight = mFindFaceView.getFaceViewHeight();

    // Log.i("Harrison", "nWidth="+nWidth+",  nHeight"+nHeight);

    float scaleWidth = ((float) nWidth)/width;

    float scaleHeight = ((float)nHeight)/height;

    Matrix matrix = new Matrix();

    matrix.postScale(scaleWidth, scaleHeight);

    Bitmap resizedBitmap = Bitmap.createBitmap(bitmap, 0, 0, width, height, matrix, true);

    return resizedBitmap;

    }

    //处理图片格式,一般摄像头抓到的数据为ImageFormat.NV21,不同的格式,需要调整的。

    private void decodeToBitMap(byte[] data, android.hardware.Camera _camera) {

    mCameraDevice.setPreviewCallback(null);

    Size size = mCameraDevice.getParameters().getPreviewSize();

    //FileOutputStream outStream = null;

    try {

    YuvImage image = new YuvImage(data, ImageFormat.NV21, size.width, size.height, null);

    if (image != null) {

    ByteArrayOutputStream stream = new ByteArrayOutputStream();

    image.compressToJpeg(new Rect(0, 0, size.width, size.height), 80, stream);

    //  outStream = new FileOutputStream(String.format("/sdcard/%d.jpg", System.currentTimeMillis()));

    //  outStream.write(stream.toByteArray());

    //  outStream.close();

    //  Log.i("Harrison", "write file to sdcard.");

    //在我的手机上有两种预览模式,发现全屏模式时需要调整图片的大小才能正确定位。

    Bitmap myBitmap=BitmapFactory.decodeByteArray(stream.toByteArray(), 0, stream.size());

    stream.close();

    if (mPreviewLayoutProxy.getFullScreenMode()) { // fullscreen mode

    Bitmap tmpScaleBmp=null;

    Bitmap tmpRotateBmp=null;

    //手机是竖屏横排是与其别的哦

    if (((mOrientation/90) == 0) || ((mOrientation/90) == 2)) {

    tmpRotateBmp = rotateMyBitmap(myBitmap);

    tmpScaleBmp = scaleMyBitmap(tmpRotateBmp);

    FindFacesInBitmap(tmpScaleBmp);

    if (tmpRotateBmp != null) {

    tmpRotateBmp.recycle();

    tmpRotateBmp = null;

    }

    } else {

    FindFacesInBitmap(scaleMyBitmap(myBitmap));

    }

    if (tmpScaleBmp != null) {

    tmpScaleBmp.recycle();

    tmpScaleBmp = null;

    }

    } else { //normal mode

    FindFacesInBitmap(myBitmap);

    }

    DrawRectOnFace();

    if (myBitmap != null) {

    myBitmap.recycle();

    myBitmap = null;

    }

    }

    } catch (Exception ex) {

    Log.e("Sys", "Error:" + ex.getMessage());

    }

    mCameraDevice.setPreviewCallback(mPreviewCallback);

    }

    private  final class PostPreviewCallback implements PreviewCallback {

    @Override

    public void onPreviewFrame(byte[] data, android.hardware.Camera camera) {

    decodeToBitMap(data, camera);

    }

    }

    我们知道,相机预览是用 SurfaceView来显示图片的;在我们画提示框时,不能直接用那个view的,会出现黑屏的状态,预览的画面也不流畅的。

    添加一个一样大小的SurfaceView来提示。

    在xml布局中添加

    android:layout_width="match_parent"

    android:layout_height="match_parent"/>

    android:id="@+id/faces_rectangle"

    android:layout_width="fill_parent"

    android:layout_height="fill_parent" />

    我的画提示框代码:

    package com.android.camera;

    import android.content.Context;

    import android.graphics.Canvas;

    import android.graphics.Bitmap;

    import android.graphics.Color;

    import android.graphics.Paint;

    import android.graphics.Rect;

    import android.graphics.Paint.Style;

    import android.graphics.PixelFormat;

    import android.graphics.PorterDuffXfermode;

    import android.graphics.PorterDuff;

    import android.util.AttributeSet;

    import android.util.Log;

    import android.view.SurfaceHolder;

    import android.view.SurfaceView;

    import android.view.View;

    import android.graphics.PointF;

    import android.media.FaceDetector;

    import android.media.FaceDetector.Face;

    public class FindFaceView extends SurfaceView implements SurfaceHolder.Callback{

    protected SurfaceHolder sh;

    private  SurfaceHolder mCameraSh;

    private int mWidth;

    private int mHeight;

    private float mEyesDistance;

    public FindFaceView(Context context, AttributeSet attrs) {

    super(context, attrs);

    // TODO Auto-generated constructor stub

    sh = getHolder();

    sh.addCallback(this);

    sh.setFormat(PixelFormat.TRANSPARENT);

    setZOrderOnTop(true);

    }

    public void surfaceChanged(SurfaceHolder arg0, int arg1, int w, int h) {

    // TODO Auto-generated method stub

    mWidth = w;

    mHeight = h;

    }

    public void surfaceCreated(SurfaceHolder arg0) {

    // TODO Auto-generated method stub

    }

    public void surfaceDestroyed(SurfaceHolder arg0) {

    // TODO Auto-generated method stub

    }

    void setCameraPreviewSurfaceHolder(SurfaceHolder  sh) {

    mCameraSh = sh;

    }

    public int getFaceViewWidth() {

    return mWidth;

    }

    public int getFaceViewHeight() {

    return mHeight;

    }

    void clearDraw() {

    Canvas canvas = sh.lockCanvas();

    Paint clipPaint = new Paint();

    clipPaint.setAntiAlias(true);

    clipPaint.setStyle(Paint.Style.STROKE);

    clipPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));

    canvas.drawPaint(clipPaint);

    sh.unlockCanvasAndPost(canvas);

    }

    public void drawRects(FaceDetector.Face[] mFace, int numberOfFaceDetected) {

    Canvas canvas = sh.lockCanvas();

    Paint clipPaint = new Paint();

    clipPaint.setAntiAlias(true);

    clipPaint.setStyle(Paint.Style.STROKE);

    clipPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));

    canvas.drawPaint(clipPaint);

    canvas.drawColor(Color.TRANSPARENT);

    Paint p = new Paint();

    p.setAntiAlias(true);

    p.setColor(Color.RED);

    p.setStyle(Style.STROKE);

    for (int i = 0; i < numberOfFaceDetected; i++) {

    //  if (0 == i) {

    //   p.setColor(Color.WHITE);

    //  } else {

    //   p.setColor(Color.GRAY);

    //  }

    Face face = mFace[i];

    PointF myMidPoint = new PointF();

    face.getMidPoint(myMidPoint);

    mEyesDistance = face.eyesDistance();

    Log.i("Harrison", "i="+i+"("+myMidPoint.x+", "+myMidPoint.y+")");

    canvas.drawRect((int)(myMidPoint.x-mEyesDistance),

    (int)(myMidPoint.y-mEyesDistance),

    (int)(myMidPoint.x+mEyesDistance),

    (int)(myMidPoint.y+mEyesDistance),

    p);

    }

    sh.unlockCanvasAndPost(canvas);

    }

    //测试两个View是否错移

    public void drawBitmap(Bitmap myBitmap) {

    Canvas canvas = sh.lockCanvas();

    canvas.drawBitmap(myBitmap, 0, 0, null);

    sh.unlockCanvasAndPost(canvas);

    // mImage.setImageBitmap(myBitmap);

    // mImage.invalidate();

    }

    public void doDraw() {

    Canvas canvas = sh.lockCanvas();

    canvas.drawColor(Color.TRANSPARENT);// 这里是绘制背景

    Paint p = new Paint(); // 笔触

    p.setAntiAlias(true); // 反锯齿

    p.setColor(Color.RED);

    p.setStyle(Style.STROKE);

    canvas.drawLine(mWidth/2 – 100, 0, mWidth/2 – 100, mHeight, p);

    canvas.drawLine(mWidth/2 + 100, 0, mWidth/2 + 100, mHeight, p);

    // ————————

    // 画边框———————

    Rect rec = canvas.getClipBounds();

    rec.bottom–;

    rec.right–;

    p.setColor(Color.GRAY);

    // 颜色

    p.setStrokeWidth(5);

    canvas.drawRect(rec, p);

    // 提交绘制

    sh.unlockCanvasAndPost(canvas);

    }

    }

    遇到一个问题,在预览时频繁的全屏普通切换,容易粗出现识别库无效。请高手指点指点。

    本文转载自:CSDN博客

    欢迎加入我爱机器学习QQ14群:336582044

    getqrcode.jpg

    微信扫一扫,关注我爱机器学习公众号

    展开全文
  • 通过android自带的人脸识别接口,调用surfaceview显示摄像头画面,并检查界面人脸功能
  • 和你一起终身学习,这里是程序员Android经典好文推荐,通过阅读本文,您将收获以下知识点:一、人脸识别身份验证HIDL二、人脸模块流程分析三、人脸录入四、人脸匹配五、人脸解锁屏幕一、人脸...

    和你一起终身学习,这里是程序员Android

    经典好文推荐,通过阅读本文,您将收获以下知识点:

    一、人脸识别身份验证HIDL
    二、人脸模块流程分析
    三、人脸录入
    四、人脸匹配
    五、人脸解锁屏幕

    一、人脸识别身份验证HIDL

    借助人脸识别身份验证功能,用户只需要将自己的面孔对准设备即可将其解锁。Android 10 增加了对一种新的人脸识别身份验证堆栈的支持,这种堆栈可安全处理摄像头帧,从而在支持的硬件上进行人脸识别身份验证时保障安全和隐私。Android 10 还提供了一种简单的安全合规实现方法,以支持通过应用集成来完成交易(例如网上银行或其他服务)。
    Android 人脸识别身份验证堆栈是Android 10中的新实现。该实现引入了 IBiometricsFace.hal、IBiometricsFaceClientCallback.hal、和type.hal接口。

    要实现Face HIDL,你必须在某个供应商专用库中实现 IBiometricsFace.hal的所有方法
    人脸识别架构
    BiometricPrompt API包括人脸识别、指纹识别和虹膜识别在内的所有生物识别身份验证方法。Face HAL会与以下组件交互:

    FaceManager

    FaceManager是一个私有接口,用于维护FaceService的之间连接。Keyguard通过该接口访问具有自定义界面的人脸识别身份验证硬件。应用无权访问FaceManager,必须改为使用BiometricPrompt。

    FaceService

    该框架实现用于管理对人脸识别身份验证硬件的访问权限。它包含基本的注册和身份验证状态机以及各种其他辅助程序(例如枚举程序)。处于稳定性和安全性方面的考虑,不允许在此进程中运行任何供应商代码。所有供应商代码都通过Face 1.0 HIDL接口访问。

    faced

    这是一个Linux可执行文件,用于实现供FaceService使用的Face 1.0 HIDL 接口。它会将自身注册为 IBiometricsFace@1.0以便FaceService能够找到它。

    二、人脸模块流程分析

    要实现Face HIDL,你必须在某个供应商专用库中实现 IBiometricsFace.hal的所有方法

    IBiometricsFace.hal中主要包括以下主要方法:setCallback(); setActiveUser(); revokeChallenge(); enroll(); cancel(); enumerate(); remove(); authenticate(); userActivity; resetLockout(); 其余的四个都是同步方法,应将其阻塞时间缩至最短以免拖延框架。它们分别是generateChallenge(); setFeature(); getFeature; getAuthentitorId()

    人脸模块中的录入,匹配,移除是三个大部分;

    三、人脸录入

    人脸录入的入口在Settings中的FaceEnrollEnrolling.java中

    在这个类中没有看到明显的录入的方法,只有一些UI的加载和一些录入动画的逻辑。

    在此类中的on EnrollmentProgressChange(int steps, int remaining)更新录入进度的方法中用通过传递进来的remaining来获取实际的进度;当remaining = 0 时打开录入结束界面launchFinish(mToken);

    packages/apps/Settings/src/com/android/settings/biometrics/face/FaceEnrollEnrolling.java

    
        @Override
        public void onEnrollmentProgressChange(int steps, int remaining) {
            if (DEBUG) {
                Log.v(TAG, "Steps: " + steps + " Remaining: " + remaining);
            }
            /*重点关注*/
            mPreviewFragment.onEnrollmentProgressChange(steps, remaining);
    
            // TODO: Update the actual animation
            showError("Steps: " + steps + " Remaining: " + remaining);
    
            // TODO: Have this match any animations that UX comes up with
            if (remaining == 0) {
                launchFinish(mToken);
            }
        
    

    packages/apps/Settings/src/com/android/settings/biometrics/face/FaceEnrollPreviewFragment.java

        @Override
        public void onEnrollmentProgressChange(int steps, int remaining) {
            /*重点关注*/
            mAnimationDrawable.onEnrollmentProgressChange(steps, remaining);
        }
    
    

    packages/apps/Settings/src/com/android/settings/biometrics/face/FaceEnrollAnimationDrawable.java

    
        @Override
        public void onEnrollmentProgressChange(int steps, int remaining) {
            /*重点关注*/
            mParticleCollection.onEnrollmentProgressChange(steps, remaining);
        }
    
    

    packages/apps/Settings/src/com/android/settings/biometrics/face/ParticleCollection.java

    /*重点关注*/
    public class ParticleCollection implements BiometricEnrollSidecar.Listener {
        ......
        @Override
        public void onEnrollmentProgressChange(int steps, int remaining) {
            if (remaining == 0) {
                updateState(STATE_COMPLETE);
            }
        }
    }
    
    

    由此可以看出此类是实现了BiometricEnrollSidecar.Listener从而调用onEnrollmentProgressChange通过传入的remaining值对录入人脸的进度条进行更新的

    packages/apps/Settings/src/com/android/settings/biometrics/BiometricEnrollSidecar.java

    
    public abstract class BiometricEnrollSidecar extends InstrumentedFragment {
    
        public interface Listener {
            void onEnrollmentHelp(int helpMsgId, CharSequence helpString);
            void onEnrollmentError(int errMsgId, CharSequence errString);
            /*重点关注*/
            void onEnrollmentProgressChange(int steps, int remaining);
        }
    
    
    

    onEnrollmentProgressChange

        protected void onEnrollmentProgress(int remaining) {
            if (mEnrollmentSteps == -1) {
                mEnrollmentSteps = remaining;
            }
            mEnrollmentRemaining = remaining;
            mDone = remaining == 0;
            if (mListener != null) {
                /*重点关注*/
                mListener.onEnrollmentProgressChange(mEnrollmentSteps, remaining);
            } else {
                mQueuedEvents.add(new QueuedEnrollmentProgress(mEnrollmentSteps, remaining));
            }
        }
    
    

    底层在录制人脸的时候会在FaceManager中调用onEnrollmentProgress方法,并将进度remainiing返回过来,BiometricEnrollSidecar内部写有Listener,在使用Listener的对象将onEnrollmentProgress的值传递进去,使更多实现Listener接口的类可以接收到

    packages/apps/Settings/src/com/android/settings/biometrics/face/FaceEnrollEnrolling.java
    我们再回到这个类中去看看startEnrollment录入的方法

    
        @Override
        public void startEnrollment() {
            /*重点关注*/
            super.startEnrollment();
            mPreviewFragment = (FaceEnrollPreviewFragment) getSupportFragmentManager()
                    .findFragmentByTag(TAG_FACE_PREVIEW);
            if (mPreviewFragment == null) {
                mPreviewFragment = new FaceEnrollPreviewFragment();
                getSupportFragmentManager().beginTransaction().add(mPreviewFragment, TAG_FACE_PREVIEW)
                        .commitAllowingStateLoss();
            }
            mPreviewFragment.setListener(mListener);
        }
    
    

    此方法中没有明显录入的方法,可见录入方法存在于他的父类中

    packages/apps/Settings/src/com/android/settings/biometrics/BiometricsEnrollEnrolling.java

        public void startEnrollment() {
            mSidecar = (BiometricEnrollSidecar) getSupportFragmentManager()
                    .findFragmentByTag(TAG_SIDECAR);
            if (mSidecar == null) {
                mSidecar = getSidecar();
                getSupportFragmentManager().beginTransaction().add(mSidecar, TAG_SIDECAR)
                        .commitAllowingStateLoss();
            }
            /*重点关注*/
            mSidecar.setListener(this);
        }
    
    
    

    由此可知是通过给mSidecar设置setListener监听传入变化而开始录入的

    packages/apps/Settings/src/com/android/settings/biometrics/face/FaceEnrollEnrolling.java

        @Override
        public void startEnrollment() {
            super.startEnrollment();
            if (mUserId != UserHandle.USER_NULL) {
                mFaceManager.setActiveUser(mUserId);
            }
            /*重点关注*/
            mFaceManager.enroll(mToken, mEnrollmentCancel,
                    mEnrollmentCallback, mDisabledFeatures);
        }
    
    

    frameworks/base/core/java/android/hardware/face/FaceManager.java

        public void enroll(byte[] token, CancellationSignal cancel,
                EnrollmentCallback callback, int[] disabledFeatures) {
            if (callback == null) {
                throw new IllegalArgumentException("Must supply an enrollment callback");
            }
    
            if (cancel != null) {
                if (cancel.isCanceled()) {
                    Log.w(TAG, "enrollment already canceled");
                    return;
                } else {
                    cancel.setOnCancelListener(new OnEnrollCancelListener());
                }
            }
    
            if (mService != null) {
                try {
                    mEnrollmentCallback = callback;
                    Trace.beginSection("FaceManager#enroll");
                    /*重点关注*/
                    mService.enroll(mToken, token, mServiceReceiver,
                            mContext.getOpPackageName(), disabledFeatures);
                } catch (RemoteException e) {
                    Log.w(TAG, "Remote exception in enroll: ", e);
                    if (callback != null) {
                        // Though this may not be a hardware issue, it will cause apps to give up or
                        // try again later.
                        callback.onEnrollmentError(FACE_ERROR_HW_UNAVAILABLE,
                                getErrorString(mContext, FACE_ERROR_HW_UNAVAILABLE,
                                    0 /* vendorCode */));
                    }
                } finally {
                    Trace.endSection();
                }
            }
        }
        
    
    

    frameworks/base/services/core/java/com/android/server/biometrics/face/FaceService.java

            public void enroll(final IBinder token, final byte[] cryptoToken,
                    final IFaceServiceReceiver receiver, final String opPackageName,
                    final int[] disabledFeatures) {
                checkPermission(MANAGE_BIOMETRIC);
    
                final boolean restricted = isRestricted();
                final EnrollClientImpl client = new EnrollClientImpl(getContext(), mDaemonWrapper,
                        mHalDeviceId, token, new ServiceListenerImpl(receiver), mCurrentUserId,
                        0 /* groupId */, cryptoToken, restricted, opPackageName, disabledFeatures) {
    
                    @Override
                    public int[] getAcquireIgnorelist() {
                        return mEnrollIgnoreList;
                    }
    
                    @Override
                    public int[] getAcquireVendorIgnorelist() {
                        return mEnrollIgnoreListVendor;
                    }
    
                    @Override
                    public boolean shouldVibrate() {
                        return false;
                    }
    
                    @Override
                    protected int statsModality() {
                        return FaceService.this.statsModality();
                    }
                };
                /*重点关注*/
                enrollInternal(client, mCurrentUserId);
            }
    
    

    frameworks/base/services/core/java/com/android/server/biometrics/BiometricServiceBase.java

            if (hasReachedEnrollmentLimit(userId)) {
                return;
            }
    
            // Group ID is arbitrarily set to parent profile user ID. It just represents
            // the default biometrics for the user.
            if (!isCurrentUserOrProfile(userId)) {
                return;
            }
    
            mHandler.post(() -> {
                /*重点关注*/
                startClient(client, true /* initiatedByClient */);
            });
        }
    
    

    startClient(client, true /* initiatedByClient */);

            ClientMonitor currentClient = mCurrentClient;
            if (currentClient != null) {
                if (DEBUG) Slog.v(getTag(), "request stop current client " +
                        currentClient.getOwnerString());
                // This check only matters for FingerprintService, since enumerate may call back
                // multiple times.
                if (currentClient instanceof InternalEnumerateClient
                        || currentClient instanceof InternalRemovalClient) {
                    // This condition means we're currently running internal diagnostics to
                    // remove extra templates in the hardware and/or the software
                    // TODO: design an escape hatch in case client never finishes
                    if (newClient != null) {
                        Slog.w(getTag(), "Internal cleanup in progress but trying to start client "
                                + newClient.getClass().getSuperclass().getSimpleName()
                                + "(" + newClient.getOwnerString() + ")"
                                + ", initiatedByClient = " + initiatedByClient);
                    }
                } else {
                    currentClient.stop(initiatedByClient);
    
                    // Only post the reset runnable for non-cleanup clients. Cleanup clients should
                    // never be forcibly stopped since they ensure synchronization between HAL and
                    // framework. Thus, we should instead just start the pending client once cleanup
                    // finishes instead of using the reset runnable.
                    mHandler.removeCallbacks(mResetClientState);
                    mHandler.postDelayed(mResetClientState, CANCEL_TIMEOUT_LIMIT);
                }
                mPendingClient = newClient;
            } else if (newClient != null) {
                // For BiometricPrompt clients, do not start until
                // <Biometric>Service#startPreparedClient is called. BiometricService waits until all
                // modalities are ready before initiating authentication.
                if (newClient instanceof AuthenticationClient) {
                    AuthenticationClient client = (AuthenticationClient) newClient;
                    if (client.isBiometricPrompt()) {
                        if (DEBUG) Slog.v(getTag(), "Returning cookie: " + client.getCookie());
                        mCurrentClient = newClient;
                        if (mBiometricService == null) {
                            mBiometricService = IBiometricService.Stub.asInterface(
                                    ServiceManager.getService(Context.BIOMETRIC_SERVICE));
                        }
                        try {
                            mBiometricService.onReadyForAuthentication(client.getCookie(),
                                    client.getRequireConfirmation(), client.getTargetUserId());
                        } catch (RemoteException e) {
                            Slog.e(getTag(), "Remote exception", e);
                        }
                        return;
                    }
                }
    
                // We are not a BiometricPrompt client, start the client immediately
                mCurrentClient = newClient;
                 /*重点关注*/
                startCurrentClient(mCurrentClient.getCookie());
    
            
            }
        }
        
    
    

    在此将EnrollClient的对象传进去

    startCurrentClient(mCurrentClient.getCookie());

        protected void startCurrentClient(int cookie) {
            if (mCurrentClient == null) {
                Slog.e(getTag(), "Trying to start null client!");
                return;
            }
            if (DEBUG) Slog.v(getTag(), "starting client "
                    + mCurrentClient.getClass().getSuperclass().getSimpleName()
                    + "(" + mCurrentClient.getOwnerString() + ")"
                    + " cookie: " + cookie + "/" + mCurrentClient.getCookie());
            if (cookie != mCurrentClient.getCookie()) {
                Slog.e(getTag(), "Mismatched cookie");
                return;
            }
            notifyClientActiveCallbacks(true);
            /*重点关注*/
            mCurrentClient.start();
        }
       
    
    

    frameworks/base/services/core/java/com/android/server/biometrics/EnrollClient.java

        @Override
        public int start() {
            mEnrollmentStartTimeMs = System.currentTimeMillis();
            final int timeout = (int) (ENROLLMENT_TIMEOUT_MS / MS_PER_SEC);
            try {
                final ArrayList<Integer> disabledFeatures = new ArrayList<>();
                for (int i = 0; i < mDisabledFeatures.length; i++) {
                    disabledFeatures.add(mDisabledFeatures[i]);
                }
                /*重点关注*/
                final int result = getDaemonWrapper().enroll(mCryptoToken, getGroupId(), timeout,
                        disabledFeatures);
                if (result != 0) {
                    Slog.w(getLogTag(), "startEnroll failed, result=" + result);
                    mMetricsLogger.histogram(mConstants.tagEnrollStartError(), result);
                    onError(getHalDeviceId(), BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE,
                            0 /* vendorCode */);
                    return result;
                }
            } catch (RemoteException e) {
                Slog.e(getLogTag(), "startEnroll failed", e);
            }
            return 0; // success
        }
    
    

    start 方法会调用faced,调用底层的人脸库,底层库返回结果后会调用onEnrollResult来反馈结果receiver,再往上层反馈。这就是人脸的录制流程。在onEnrollResult中当remaining等于0的时候完成录制,调用addBiometricForUser。

    FaceManager.java中注册了IFaceServiceReceiver,实现onEnrollResult方法发送 MSG_ENROLL_RESULT
    frameworks/base/core/java/android/hardware/face/FaceManager.java

        private IFaceServiceReceiver mServiceReceiver = new IFaceServiceReceiver.Stub() {
    
            @Override // binder call
            public void onEnrollResult(long deviceId, int faceId, int remaining) {
                /*重点关注*/
                mHandler.obtainMessage(MSG_ENROLL_RESULT, remaining, 0,
                        new Face(null, faceId, deviceId)).sendToTarget();
            }
            ...
    
    

    MSG_ENROLL_RESULT

            public void handleMessage(android.os.Message msg) {
                Trace.beginSection("FaceManager#handleMessage: " + Integer.toString(msg.what));
                switch (msg.what) {
                    case MSG_ENROLL_RESULT:
                        /*重点关注*/
                        sendEnrollResult((Face) msg.obj, msg.arg1 /* remaining */);
                        break;
                        ...
    
    

    sendEnrollResult

    private void sendEnrollResult(Face face, int remaining) {
            if (mEnrollmentCallback != null) {
                mEnrollmentCallback.onEnrollmentProgress(remaining);
            }
        }
    
    

    四、人脸匹配

    人脸解锁的入口在Keyguard中

    系统灭屏之后会调用PhoneWindowManager的startedGoingToSleep方法,继而调用KeyguardDelegate.onStartedGoingToSleep方法。

    继而又会调用KeyguardServiceWrapper.java ==》onStartedGoingToSleep()方法;再调用KeyguardService.java ==》onStartedGoingToSleep()方法;并最终在KeyguardViewMediator.java ==》dispatchStartedGoingToSleep() 达到对GoingToSleep事件的监听

    frameworks/base/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java

    public void dispatchStartedGoingToSleep(int why) {
            /*重点关注*/
            mHandler.sendMessage(mHandler.obtainMessage(MSG_STARTED_GOING_TO_SLEEP, why, 0));
        }
    
    

    MSG_STARTED_GOING_TO_SLEEP

    case MSG_STARTED_GOING_TO_SLEEP:
                        /*重点关注*/
                        handleStartedGoingToSleep(msg.arg1);
                        break;
    

    handleStartedGoingToSleep(msg.arg1);

        protected void handleStartedGoingToSleep(int arg1) {
            clearBiometricRecognized();
            final int count = mCallbacks.size();
            for (int i = 0; i < count; i++) {
                KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
                if (cb != null) {
                    cb.onStartedGoingToSleep(arg1);
                }
            }
            mGoingToSleep = true;
            /*重点关注*/
            updateBiometricListeningState();//更新生物识别状态
        }
    
    

    updateBiometricListeningState()

     //在这个方法中我们可以看到同时更新了指纹和人脸的状态
        private void updateBiometricListeningState() {
            updateFingerprintListeningState();
            /*重点关注*/
            updateFaceListeningState();
        }
    
    

    updateFaceListeningState()

    
        private void updateFaceListeningState() {
            // If this message exists, we should not authenticate again until this message is
            // consumed by the handler
            if (mHandler.hasMessages(MSG_BIOMETRIC_AUTHENTICATION_CONTINUE)) {
                return;
            }
            mHandler.removeCallbacks(mRetryFaceAuthentication);
            boolean shouldListenForFace = shouldListenForFace();
            if (mFaceRunningState == BIOMETRIC_STATE_RUNNING && !shouldListenForFace) {
                stopListeningForFace();
            } else if (mFaceRunningState != BIOMETRIC_STATE_RUNNING
                    && shouldListenForFace) {
                /*重点关注*/
                startListeningForFace();
            }
        }
    
    

    startListeningForFace()

    
        private void startListeningForFace() {
            if (mFaceRunningState == BIOMETRIC_STATE_CANCELLING) {
                setFaceRunningState(BIOMETRIC_STATE_CANCELLING_RESTARTING);
                return;
            }
            if (DEBUG) Log.v(TAG, "startListeningForFace()");
            int userId = getCurrentUser();
            if (isUnlockWithFacePossible(userId)) {
                if (mFaceCancelSignal != null) {
                    mFaceCancelSignal.cancel();
                }
                mFaceCancelSignal = new CancellationSignal();
                /*重点关注*/
                mFaceManager.authenticate(null, mFaceCancelSignal, 0,
                        mFaceAuthenticationCallback, null, userId);
                setFaceRunningState(BIOMETRIC_STATE_RUNNING);
            }
        }
    
    

    frameworks/base/core/java/android/hardware/face/FaceManager.java

    
        public void authenticate(@Nullable CryptoObject crypto, @Nullable CancellationSignal cancel,
                int flags, @NonNull AuthenticationCallback callback, @Nullable Handler handler,
                int userId) {
            if (callback == null) {
                throw new IllegalArgumentException("Must supply an authentication callback");
            }
    
            if (cancel != null) {
                if (cancel.isCanceled()) {
                    Log.w(TAG, "authentication already canceled");
                    return;
                } else {
                    cancel.setOnCancelListener(new OnAuthenticationCancelListener(crypto));
                }
            }
    
            if (mService != null) {
                try {
                    useHandler(handler);
                    mAuthenticationCallback = callback;
                    mCryptoObject = crypto;
                    long sessionId = crypto != null ? crypto.getOpId() : 0;
                    Trace.beginSection("FaceManager#authenticate");
                    /*重点关注*/
                    mService.authenticate(mToken, sessionId, userId, mServiceReceiver,
                            flags, mContext.getOpPackageName());
                } catch (RemoteException e) {
                    Log.w(TAG, "Remote exception while authenticating: ", e);
                    if (callback != null) {
                        // Though this may not be a hardware issue, it will cause apps to give up or
                        // try again later.
                        callback.onAuthenticationError(FACE_ERROR_HW_UNAVAILABLE,
                                getErrorString(mContext, FACE_ERROR_HW_UNAVAILABLE,
                                        0 /* vendorCode */));
                    }
                } finally {
                    Trace.endSection();
                }
            }
        }
    
    

    frameworks/base/services/core/java/com/android/server/biometrics/face/FaceService.java

    
            @Override // Binder call
            public void authenticate(final IBinder token, final long opId, int userId,
                    final IFaceServiceReceiver receiver, final int flags,
                    final String opPackageName) {
                checkPermission(USE_BIOMETRIC_INTERNAL);
                updateActiveGroup(userId, opPackageName);
                final boolean restricted = isRestricted();
                final AuthenticationClientImpl client = new FaceAuthClient(getContext(),
                        mDaemonWrapper, mHalDeviceId, token, new ServiceListenerImpl(receiver),
                        mCurrentUserId, 0 /* groupId */, opId, restricted, opPackageName,
                        0 /* cookie */, false /* requireConfirmation */);
                /*重点关注*/
                authenticateInternal(client, opId, opPackageName);
            }
    
    

    frameworks/base/services/core/java/com/android/server/biometrics/BiometricServiceBase.java

    
        protected void authenticateInternal(AuthenticationClientImpl client, long opId,
                String opPackageName) {
            final int callingUid = Binder.getCallingUid();
            final int callingPid = Binder.getCallingPid();
            final int callingUserId = UserHandle.getCallingUserId();
            authenticateInternal(client, opId, opPackageName, callingUid, callingPid, callingUserId);
        }
    
    

    frameworks/base/services/core/java/com/android/server/biometrics/BiometricServiceBase.java

    
        protected void authenticateInternal(AuthenticationClientImpl client, long opId,
                String opPackageName, int callingUid, int callingPid, int callingUserId) {
            if (!canUseBiometric(opPackageName, true /* foregroundOnly */, callingUid, callingPid,
                    callingUserId)) {
                if (DEBUG) Slog.v(getTag(), "authenticate(): reject " + opPackageName);
                return;
            }
    
            mHandler.post(() -> {
                mMetricsLogger.histogram(getConstants().tagAuthToken(), opId != 0L ? 1 : 0);
    
                // Get performance stats object for this user.
                HashMap<Integer, PerformanceStats> pmap
                        = (opId == 0) ? mPerformanceMap : mCryptoPerformanceMap;
                PerformanceStats stats = pmap.get(mCurrentUserId);
                if (stats == null) {
                    stats = new PerformanceStats();
                    pmap.put(mCurrentUserId, stats);
                }
                mPerformanceStats = stats;
                mIsCrypto = (opId != 0);
                /*重点关注*/
                startAuthentication(client, opPackageName);
            });
        }
    
    

    startAuthentication(client, opPackageName);

        private void startAuthentication(AuthenticationClientImpl client, String opPackageName) {
            if (DEBUG) Slog.v(getTag(), "startAuthentication(" + opPackageName + ")");
    
            int lockoutMode = getLockoutMode();
            if (lockoutMode != AuthenticationClient.LOCKOUT_NONE) {
                Slog.v(getTag(), "In lockout mode(" + lockoutMode + ") ; disallowing authentication");
                int errorCode = lockoutMode == AuthenticationClient.LOCKOUT_TIMED ?
                        BiometricConstants.BIOMETRIC_ERROR_LOCKOUT :
                        BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT;
                if (!client.onError(getHalDeviceId(), errorCode, 0 /* vendorCode */)) {
                    Slog.w(getTag(), "Cannot send permanent lockout message to client");
                }
                return;
            }
            /*重点关注*/
            startClient(client, true /* initiatedByClient */);
            //这里将AuthenticationClient传递进去
        }    
    
    

    startClient(client, true /* initiatedByClient */);

     private void startClient(ClientMonitor newClient, boolean initiatedByClient) {
            ClientMonitor currentClient = mCurrentClient;
            if (currentClient != null) {
                if (DEBUG) Slog.v(getTag(), "request stop current client " +
                        currentClient.getOwnerString());
                // This check only matters for FingerprintService, since enumerate may call back
                // multiple times.
                if (currentClient instanceof InternalEnumerateClient
                        || currentClient instanceof InternalRemovalClient) {
                    // This condition means we're currently running internal diagnostics to
                    // remove extra templates in the hardware and/or the software
                    // TODO: design an escape hatch in case client never finishes
                    if (newClient != null) {
                        Slog.w(getTag(), "Internal cleanup in progress but trying to start client "
                                + newClient.getClass().getSuperclass().getSimpleName()
                                + "(" + newClient.getOwnerString() + ")"
                                + ", initiatedByClient = " + initiatedByClient);
                    }
                } else {
                    currentClient.stop(initiatedByClient);
    
                    // Only post the reset runnable for non-cleanup clients. Cleanup clients should
                    // never be forcibly stopped since they ensure synchronization between HAL and
                    // framework. Thus, we should instead just start the pending client once cleanup
                    // finishes instead of using the reset runnable.
                    mHandler.removeCallbacks(mResetClientState);
                    mHandler.postDelayed(mResetClientState, CANCEL_TIMEOUT_LIMIT);
                }
                mPendingClient = newClient;
            } else if (newClient != null) {
                // For BiometricPrompt clients, do not start until
                // <Biometric>Service#startPreparedClient is called. BiometricService waits until all
                // modalities are ready before initiating authentication.
                if (newClient instanceof AuthenticationClient) {
                    AuthenticationClient client = (AuthenticationClient) newClient;
                    if (client.isBiometricPrompt()) {
                        if (DEBUG) Slog.v(getTag(), "Returning cookie: " + client.getCookie());
                        mCurrentClient = newClient;
                        if (mBiometricService == null) {
                            mBiometricService = IBiometricService.Stub.asInterface(
                                    ServiceManager.getService(Context.BIOMETRIC_SERVICE));
                        }
                        try {
                            mBiometricService.onReadyForAuthentication(client.getCookie(),
                                    client.getRequireConfirmation(), client.getTargetUserId());
                        } catch (RemoteException e) {
                            Slog.e(getTag(), "Remote exception", e);
                        }
                        return;
                    }
                }
    
                // We are not a BiometricPrompt client, start the client immediately
                mCurrentClient = newClient;
                /*重点关注*/
                startCurrentClient(mCurrentClient.getCookie());
                //这里继续将AuthenticationClient传递进去
            }
        }
    
    

    startCurrentClient(mCurrentClient.getCookie());

        protected void startCurrentClient(int cookie) {
            if (mCurrentClient == null) {
                Slog.e(getTag(), "Trying to start null client!");
                return;
            }
            if (DEBUG) Slog.v(getTag(), "starting client "
                    + mCurrentClient.getClass().getSuperclass().getSimpleName()
                    + "(" + mCurrentClient.getOwnerString() + ")"
                    + " cookie: " + cookie + "/" + mCurrentClient.getCookie());
            if (cookie != mCurrentClient.getCookie()) {
                Slog.e(getTag(), "Mismatched cookie");
                return;
            }
            notifyClientActiveCallbacks(true);
            /*重点关注*/
            mCurrentClient.start();
            //这里调用的是AuthenticationClient的start方法
        }
    
    

    frameworks/base/services/core/java/com/android/server/biometrics/AuthenticationClient.java

        public int start() {
            mStarted = true;
            onStart();
            try {
                /*重点关注*/
                final int result = getDaemonWrapper().authenticate(mOpId, getGroupId());
                if (result != 0) {
                    Slog.w(getLogTag(), "startAuthentication failed, result=" + result);
                    mMetricsLogger.histogram(mConstants.tagAuthStartError(), result);
                    onError(getHalDeviceId(), BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE,
                            0 /* vendorCode */);
                    return result;
                }
                if (DEBUG) Slog.w(getLogTag(), "client " + getOwnerString() + " is authenticating...");
            } catch (RemoteException e) {
                Slog.e(getLogTag(), "startAuthentication failed", e);
                return ERROR_ESRCH;
            }
            return 0; // success
        }
    
    

    start方法会调用faced,调用底层的人脸库,底层库返回结果后会调用onAuthenticated来反馈结果给receiver,在往上层反馈

    五、人脸解锁屏幕

    frameworks/base/services/core/java/com/android/server/biometrics/AuthenticationClient.java

        public boolean onAuthenticated(BiometricAuthenticator.Identifier identifier,
                boolean authenticated, ArrayList<Byte> token) {
            super.logOnAuthenticated(getContext(), authenticated, mRequireConfirmation,
                    getTargetUserId(), isBiometricPrompt());
    
            final BiometricServiceBase.ServiceListener listener = getListener();
    
            mMetricsLogger.action(mConstants.actionBiometricAuth(), authenticated);
            boolean result = false;
    
            try {
                if (DEBUG) Slog.v(getLogTag(), "onAuthenticated(" + authenticated + ")"
                        + ", ID:" + identifier.getBiometricId()
                        + ", Owner: " + getOwnerString()
                        + ", isBP: " + isBiometricPrompt()
                        + ", listener: " + listener
                        + ", requireConfirmation: " + mRequireConfirmation
                        + ", user: " + getTargetUserId());
    
                if (authenticated) {
                    mAlreadyDone = true;
    
                    if (listener != null) {
                        vibrateSuccess();
                    }
                    result = true;
                    if (shouldFrameworkHandleLockout()) {
                        resetFailedAttempts();
                    }
                    onStop();
    
                    final byte[] byteToken = new byte[token.size()];
                    for (int i = 0; i < token.size(); i++) {
                        byteToken[i] = token.get(i);
                    }
                    if (isBiometricPrompt() && listener != null) {
                        // BiometricService will add the token to keystore
                        listener.onAuthenticationSucceededInternal(mRequireConfirmation, byteToken);
                    } else if (!isBiometricPrompt() && listener != null) {
                        KeyStore.getInstance().addAuthToken(byteToken);
                        try {
                            // Explicitly have if/else here to make it super obvious in case the code is
                            // touched in the future.
                            if (!getIsRestricted()) {
                                /*重点关注*/
                                listener.onAuthenticationSucceeded(
                                        getHalDeviceId(), identifier, getTargetUserId());
                            } else {
                                listener.onAuthenticationSucceeded(
                                        getHalDeviceId(), null, getTargetUserId());
                            }
                        } catch (RemoteException e) {
                            Slog.e(getLogTag(), "Remote exception", e);
                        }
                    } else {
                        // Client not listening
                        Slog.w(getLogTag(), "Client not listening");
                        result = true;
                    }
                } else {
                    if (listener != null) {
                        vibrateError();
                    }
    
                    // Allow system-defined limit of number of attempts before giving up
                    final int lockoutMode = handleFailedAttempt();
                    if (lockoutMode != LOCKOUT_NONE && shouldFrameworkHandleLockout()) {
                        Slog.w(getLogTag(), "Forcing lockout (driver code should do this!), mode("
                                + lockoutMode + ")");
                        stop(false);
                        final int errorCode = lockoutMode == LOCKOUT_TIMED
                                ? BiometricConstants.BIOMETRIC_ERROR_LOCKOUT
                                : BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT;
                        onError(getHalDeviceId(), errorCode, 0 /* vendorCode */);
                    } else {
                        // Don't send onAuthenticationFailed if we're in lockout, it causes a
                        // janky UI on Keyguard/BiometricPrompt since "authentication failed"
                        // will show briefly and be replaced by "device locked out" message.
                        if (listener != null) {
                            if (isBiometricPrompt()) {
                                listener.onAuthenticationFailedInternal(getCookie(),
                                        getRequireConfirmation());
                            } else {
                                listener.onAuthenticationFailed(getHalDeviceId());
                            }
                        }
                    }
                    result = lockoutMode != LOCKOUT_NONE; // in a lockout mode
                }
            } catch (RemoteException e) {
                Slog.e(getLogTag(), "Remote exception", e);
                result = true;
            }
            return result;
        }
    
    

    frameworks/base/services/core/java/com/android/server/biometrics/BiometricServiceBase.java

        protected interface ServiceListener {
            default void onEnrollResult(BiometricAuthenticator.Identifier identifier,
                    int remaining) throws RemoteException {};
    
            void onAcquired(long deviceId, int acquiredInfo, int vendorCode) throws RemoteException;
    
            /*重点关注*/
            default void onAuthenticationSucceeded(long deviceId,
                    BiometricAuthenticator.Identifier biometric, int userId) throws RemoteException {
                throw new UnsupportedOperationException("Stub!");
            }
    
            default void onAuthenticationSucceededInternal(boolean requireConfirmation, byte[] token)
                    throws RemoteException {
                throw new UnsupportedOperationException("Stub!");
            }
    
            default void onAuthenticationFailed(long deviceId) throws RemoteException {
                throw new UnsupportedOperationException("Stub!");
            }
    
            default void onAuthenticationFailedInternal(int cookie, boolean requireConfirmation)
                    throws RemoteException {
                throw new UnsupportedOperationException("Stub!");
            }
    
            void onError(long deviceId, int error, int vendorCode, int cookie) throws RemoteException;
    
            default void onRemoved(BiometricAuthenticator.Identifier identifier,
                    int remaining) throws RemoteException {};
    
            default void onEnumerated(BiometricAuthenticator.Identifier identifier,
                    int remaining) throws RemoteException {};
        }
    
    

    onAuthenticationSucceeded

    
        /**
         * Wraps the callback interface from Service -> BiometricPrompt
         */
        protected abstract class BiometricServiceListener implements ServiceListener {
            private IBiometricServiceReceiverInternal mWrapperReceiver;
    
            public BiometricServiceListener(IBiometricServiceReceiverInternal wrapperReceiver) {
                mWrapperReceiver = wrapperReceiver;
            }
    
            public IBiometricServiceReceiverInternal getWrapperReceiver() {
                return mWrapperReceiver;
            }
    
            @Override
            public void onAuthenticationSucceededInternal(boolean requireConfirmation, byte[] token)
                    throws RemoteException {
                if (getWrapperReceiver() != null) {
                
                    /*重点关注*/
                    getWrapperReceiver().onAuthenticationSucceeded(requireConfirmation, token);
                }
            }
    
            @Override
            public void onAuthenticationFailedInternal(int cookie, boolean requireConfirmation)
                    throws RemoteException {
                if (getWrapperReceiver() != null) {
                    getWrapperReceiver().onAuthenticationFailed(cookie, requireConfirmation);
                }
            }
        }
    
    

    frameworks/base/core/java/android/hardware/face/FaceManager.java

    
        private IFaceServiceReceiver mServiceReceiver = new IFaceServiceReceiver.Stub() {
    
        ......
    
            @Override // binder call
            public void onAuthenticationSucceeded(long deviceId, Face face, int userId) {
                /*重点关注*/
                mHandler.obtainMessage(MSG_AUTHENTICATION_SUCCEEDED, userId, 0, face).sendToTarget();
            }
    
    

    MSG_AUTHENTICATION_SUCCEEDED

    
    case MSG_AUTHENTICATION_SUCCEEDED:
                        /*重点关注*/
                        sendAuthenticatedSucceeded((Face) msg.obj, msg.arg1 /* userId */);
                        break;
    
    

    sendAuthenticatedSucceeded

    
        private void sendAuthenticatedSucceeded(Face face, int userId) {
            if (mAuthenticationCallback != null) {
                final AuthenticationResult result =
                        new AuthenticationResult(mCryptoObject, face, userId);
                /*重点关注*/
                mAuthenticationCallback.onAuthenticationSucceeded(result);
            }
        }
    
    

    AuthenticationCallback是Fm的一个内部回调接口

    
        public abstract static class AuthenticationCallback
                extends BiometricAuthenticator.AuthenticationCallback {
    
            /**
             * Called when an unrecoverable error has been encountered and the operation is complete.
             * No further callbacks will be made on this object.
             *
             * @param errorCode An integer identifying the error message
             * @param errString A human-readable error string that can be shown in UI
             */
            public void onAuthenticationError(int errorCode, CharSequence errString) {
            }
    
            /**
             * Called when a recoverable error has been encountered during authentication. The help
             * string is provided to give the user guidance for what went wrong, such as
             * "Sensor dirty, please clean it."
             *
             * @param helpCode   An integer identifying the error message
             * @param helpString A human-readable string that can be shown in UI
             */
            public void onAuthenticationHelp(int helpCode, CharSequence helpString) {
            }
    
            /**
             * Called when a face is recognized.
             *
             * @param result An object containing authentication-related data
             */
             /*重点关注*/
            public void onAuthenticationSucceeded(AuthenticationResult result) {
            }
    
            /**
             * Called when a face is detected but not recognized.
             */
            public void onAuthenticationFailed() {
            }
    
            /**
             * Called when a face image has been acquired, but wasn't processed yet.
             *
             * @param acquireInfo one of FACE_ACQUIRED_* constants
             * @hide
             */
            public void onAuthenticationAcquired(int acquireInfo) {
            }
        }
        
    
    

    frameworks/base/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java

    AuthenticationCallback接口在KeyguardUpdateMonitor.java中实现,用于监听FaceService中人脸的解锁状态

    
       @VisibleForTesting
       FaceManager.AuthenticationCallback mFaceAuthenticationCallback
               = new FaceManager.AuthenticationCallback() {
    
           @Override
           public void onAuthenticationFailed() {
               handleFaceAuthFailed();
           }
    
           @Override
           public void onAuthenticationSucceeded(FaceManager.AuthenticationResult result) {
               Trace.beginSection("KeyguardUpdateMonitor#onAuthenticationSucceeded");
               /*重点关注*/
               handleFaceAuthenticated(result.getUserId());
               Trace.endSection();
           }
    
           @Override
           public void onAuthenticationHelp(int helpMsgId, CharSequence helpString) {
               handleFaceHelp(helpMsgId, helpString.toString());
           }
    
           @Override
           public void onAuthenticationError(int errMsgId, CharSequence errString) {
               handleFaceError(errMsgId, errString.toString());
           }
    
           @Override
           public void onAuthenticationAcquired(int acquireInfo) {
               handleFaceAcquired(acquireInfo);
           }
       };
    
    

    handleFaceAuthenticated

    
        private void handleFaceAuthenticated(int authUserId) {
            Trace.beginSection("KeyGuardUpdateMonitor#handlerFaceAuthenticated");
            try {
                final int userId;
                try {
                    userId = ActivityManager.getService().getCurrentUser().id;
                } catch (RemoteException e) {
                    Log.e(TAG, "Failed to get current user id: ", e);
                    return;
                }
                if (userId != authUserId) {
                    Log.d(TAG, "Face authenticated for wrong user: " + authUserId);
                    return;
                }
                if (isFaceDisabled(userId)) {
                    Log.d(TAG, "Face authentication disabled by DPM for userId: " + userId);
                    return;
                }
                /*重点关注*/
                onFaceAuthenticated(userId);
            } finally {
                setFaceRunningState(BIOMETRIC_STATE_STOPPED);
            }
            Trace.endSection();
        }
    
    

    onFaceAuthenticated(userId)

    
        protected void onFaceAuthenticated(int userId) {
            Trace.beginSection("KeyGuardUpdateMonitor#onFaceAuthenticated");
            mUserFaceAuthenticated.put(userId, true);
            // Update/refresh trust state only if user can skip bouncer
            if (getUserCanSkipBouncer(userId)) {
                mTrustManager.unlockedByBiometricForUser(userId, BiometricSourceType.FACE);
            }
            // Don't send cancel if authentication succeeds
            mFaceCancelSignal = null;
            for (int i = 0; i < mCallbacks.size(); i++) {
                /*重点关注*/
                KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
                if (cb != null) {
                    /*重点关注*/
                    cb.onBiometricAuthenticated(userId,
                            BiometricSourceType.FACE);
                }
            }
    
            mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_BIOMETRIC_AUTHENTICATION_CONTINUE),
                    BIOMETRIC_CONTINUE_DELAY_MS);
    
            // Only authenticate face once when assistant is visible
            mAssistantVisible = false;
    
            Trace.endSection();
        }
    
    

    这里开始调用接口将解锁成功消息层层传递直至keyguard解锁,与指纹解锁逻辑一致

    可以看到在onFaceAuthenticated(userId)方法中调用了KeyguardUpdateMonitorCallback这个抽象类的onBiometricAuthenticated()抽象方法,而BiometricUnlockController extends KeyguardUpdateMonitorCallback,并注册了回调mUpdateMonitor.registerCallback(this)

    frameworks/base/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java

       /**
        * Called when a biometric is recognized.
        * @param userId the user id for which the biometric sample was authenticated
        * @param biometricSourceType
        */
        /*重点关注*/
       public void onBiometricAuthenticated(int userId, BiometricSourceType biometricSourceType) { }
    
    

    frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java

        @Override
        public void onBiometricAuthenticated(int userId, BiometricSourceType biometricSourceType) {
            Trace.beginSection("BiometricUnlockController#onBiometricAuthenticated");
            if (mUpdateMonitor.isGoingToSleep()) {
                mPendingAuthenticatedUserId = userId;
                mPendingAuthenticatedBioSourceType = biometricSourceType;
                Trace.endSection();
                return;
            }
            mMetricsLogger.write(new LogMaker(MetricsEvent.BIOMETRIC_AUTH)
                    .setType(MetricsEvent.TYPE_SUCCESS).setSubtype(toSubtype(biometricSourceType)));
             /*重点关注*/
            startWakeAndUnlock(calculateMode(biometricSourceType));
        }
    
    

    startWakeAndUnlock(calculateMode(biometricSourceType));

        public void startWakeAndUnlock(int mode) {
            // TODO(b/62444020): remove when this bug is fixed
            Log.v(TAG, "startWakeAndUnlock(" + mode + ")");
            boolean wasDeviceInteractive = mUpdateMonitor.isDeviceInteractive();
            mMode = mode;
            mHasScreenTurnedOnSinceAuthenticating = false;
            if (mMode == MODE_WAKE_AND_UNLOCK_PULSING && pulsingOrAod()) {
                // If we are waking the device up while we are pulsing the clock and the
                // notifications would light up first, creating an unpleasant animation.
                // Defer changing the screen brightness by forcing doze brightness on our window
                // until the clock and the notifications are faded out.
                mStatusBarWindowController.setForceDozeBrightness(true);
            }
            // During wake and unlock, we need to draw black before waking up to avoid abrupt
            // brightness changes due to display state transitions.
            boolean alwaysOnEnabled = DozeParameters.getInstance(mContext).getAlwaysOn();
            boolean delayWakeUp = mode == MODE_WAKE_AND_UNLOCK && alwaysOnEnabled && mWakeUpDelay > 0;
            Runnable wakeUp = ()-> {
                if (!wasDeviceInteractive) {
                    if (DEBUG_BIO_WAKELOCK) {
                        Log.i(TAG, "bio wakelock: Authenticated, waking up...");
                    }
                    mPowerManager.wakeUp(SystemClock.uptimeMillis(), PowerManager.WAKE_REASON_GESTURE,
                            "android.policy:BIOMETRIC");
                }
                if (delayWakeUp) {
                   /*重点关注*/
                    mKeyguardViewMediator.onWakeAndUnlocking();
                }
                Trace.beginSection("release wake-and-unlock");
                releaseBiometricWakeLock();
                Trace.endSection();
            };
    
            if (!delayWakeUp) {
                wakeUp.run();
            }
            switch (mMode) {
                case MODE_DISMISS_BOUNCER:
                    Trace.beginSection("MODE_DISMISS");
                    mStatusBarKeyguardViewManager.notifyKeyguardAuthenticated(
                            false /* strongAuth */);
                    Trace.endSection();
                    break;
                case MODE_UNLOCK:
                case MODE_SHOW_BOUNCER:
                    Trace.beginSection("MODE_UNLOCK or MODE_SHOW_BOUNCER");
                    if (!wasDeviceInteractive) {
                        mPendingShowBouncer = true;
                    } else {
                        showBouncer();
                    }
                    Trace.endSection();
                    break;
                case MODE_WAKE_AND_UNLOCK_FROM_DREAM:
                case MODE_WAKE_AND_UNLOCK_PULSING:
                case MODE_WAKE_AND_UNLOCK:
                    if (mMode == MODE_WAKE_AND_UNLOCK_PULSING) {
                        Trace.beginSection("MODE_WAKE_AND_UNLOCK_PULSING");
                        mMediaManager.updateMediaMetaData(false /* metaDataChanged */,
                                true /* allowEnterAnimation */);
                    } else if (mMode == MODE_WAKE_AND_UNLOCK){
                        Trace.beginSection("MODE_WAKE_AND_UNLOCK");
                    } else {
                        Trace.beginSection("MODE_WAKE_AND_UNLOCK_FROM_DREAM");
                        mUpdateMonitor.awakenFromDream();
                    }
                    mStatusBarWindowController.setStatusBarFocusable(false);
                    if (delayWakeUp) {
                        mHandler.postDelayed(wakeUp, mWakeUpDelay);
                    } else {
                        mKeyguardViewMediator.onWakeAndUnlocking();
                    }
                    if (mStatusBar.getNavigationBarView() != null) {
                        mStatusBar.getNavigationBarView().setWakeAndUnlocking(true);
                    }
                    Trace.endSection();
                    break;
                case MODE_ONLY_WAKE:
                case MODE_NONE:
                    break;
            }
            mStatusBar.notifyBiometricAuthModeChanged();
            Trace.endSection();
        }
    
    

    frameworks/base/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java

        public void onWakeAndUnlocking() {
            Trace.beginSection("KeyguardViewMediator#onWakeAndUnlocking");
            mWakeAndUnlocking = true;
            /*重点关注*/
            keyguardDone();
            Trace.endSection();
        }
    
    

    keyguardDone();

        public void keyguardDone() {
            Trace.beginSection("KeyguardViewMediator#keyguardDone");
            if (DEBUG) Log.d(TAG, "keyguardDone()");
            userActivity();
            EventLog.writeEvent(70000, 2);
            /*重点关注*/
            Message msg = mHandler.obtainMessage(KEYGUARD_DONE);
            mHandler.sendMessage(msg);
            Trace.endSection();
        }
    
    

    KEYGUARD_DONE

                    case KEYGUARD_DONE:
                        Trace.beginSection("KeyguardViewMediator#handleMessage KEYGUARD_DONE");
                        /*重点关注*/
                        handleKeyguardDone();
                        Trace.endSection();
                        break;
    
    

    handleKeyguardDone();

        private void handleKeyguardDone() {
            Trace.beginSection("KeyguardViewMediator#handleKeyguardDone");
            final int currentUser = KeyguardUpdateMonitor.getCurrentUser();
            mUiOffloadThread.submit(() -> {
                if (mLockPatternUtils.isSecure(currentUser)) {
                    mLockPatternUtils.getDevicePolicyManager().reportKeyguardDismissed(currentUser);
                }
            });
            if (DEBUG) Log.d(TAG, "handleKeyguardDone");
            synchronized (this) {
                resetKeyguardDonePendingLocked();
            }
    
            mUpdateMonitor.clearBiometricRecognized();
    
            if (mGoingToSleep) {
                Log.i(TAG, "Device is going to sleep, aborting keyguardDone");
                return;
            }
            if (mExitSecureCallback != null) {
                try {
                    mExitSecureCallback.onKeyguardExitResult(true /* authenciated */);
                } catch (RemoteException e) {
                    Slog.w(TAG, "Failed to call onKeyguardExitResult()", e);
                }
    
                mExitSecureCallback = null;
    
                // after succesfully exiting securely, no need to reshow
                // the keyguard when they've released the lock
                mExternallyEnabled = true;
                mNeedToReshowWhenReenabled = false;
                updateInputRestricted();
            }
            /*重点关注*/
            handleHide();
            Trace.endSection();
        }
    
    
    

    handleHide();

        private void handleHide() {
            Trace.beginSection("KeyguardViewMediator#handleHide");
    
            // It's possible that the device was unlocked in a dream state. It's time to wake up.
            if (mAodShowing) {
                PowerManager pm = mContext.getSystemService(PowerManager.class);
                pm.wakeUp(SystemClock.uptimeMillis(), PowerManager.WAKE_REASON_GESTURE,
                        "com.android.systemui:BOUNCER_DOZING");
            }
    
            synchronized (KeyguardViewMediator.this) {
                if (DEBUG) Log.d(TAG, "handleHide");
    
                if (mustNotUnlockCurrentUser()) {
                    // In split system user mode, we never unlock system user. The end user has to
                    // switch to another user.
                    // TODO: We should stop it early by disabling the swipe up flow. Right now swipe up
                    // still completes and makes the screen blank.
                    if (DEBUG) Log.d(TAG, "Split system user, quit unlocking.");
                    return;
                }
                mHiding = true;
    
                if (mShowing && !mOccluded) {
                    mKeyguardGoingAwayRunnable.run();
                } else {
                    /*重点关注*/
                    handleStartKeyguardExitAnimation(
                            SystemClock.uptimeMillis() + mHideAnimation.getStartOffset(),
                            mHideAnimation.getDuration());
                }
            }
            Trace.endSection();
        }
    
    

    handleHide();

        private void handleHide() {
            Trace.beginSection("KeyguardViewMediator#handleHide");
    
            // It's possible that the device was unlocked in a dream state. It's time to wake up.
            if (mAodShowing) {
                PowerManager pm = mContext.getSystemService(PowerManager.class);
                pm.wakeUp(SystemClock.uptimeMillis(), PowerManager.WAKE_REASON_GESTURE,
                        "com.android.systemui:BOUNCER_DOZING");
            }
    
            synchronized (KeyguardViewMediator.this) {
                if (DEBUG) Log.d(TAG, "handleHide");
    
                if (mustNotUnlockCurrentUser()) {
                    // In split system user mode, we never unlock system user. The end user has to
                    // switch to another user.
                    // TODO: We should stop it early by disabling the swipe up flow. Right now swipe up
                    // still completes and makes the screen blank.
                    if (DEBUG) Log.d(TAG, "Split system user, quit unlocking.");
                    return;
                }
                mHiding = true;
    
                if (mShowing && !mOccluded) {
                    mKeyguardGoingAwayRunnable.run();
                } else {
                    /*重点关注*/
                    handleStartKeyguardExitAnimation(
                            SystemClock.uptimeMillis() + mHideAnimation.getStartOffset(),
                            mHideAnimation.getDuration());
                }
            }
            Trace.endSection();
        }
    
    

    handleStartKeyguardExitAnimation

        private void handleStartKeyguardExitAnimation(long startTime, long fadeoutDuration) {
            Trace.beginSection("KeyguardViewMediator#handleStartKeyguardExitAnimation");
            if (DEBUG) Log.d(TAG, "handleStartKeyguardExitAnimation startTime=" + startTime
                    + " fadeoutDuration=" + fadeoutDuration);
            synchronized (KeyguardViewMediator.this) {
    
                if (!mHiding) {
                    // Tell ActivityManager that we canceled the keyguardExitAnimation.
                    setShowingLocked(mShowing, mAodShowing, true /* force */);
                    return;
                }
                mHiding = false;
    
                if (mWakeAndUnlocking && mDrawnCallback != null) {
    
                    // Hack level over 9000: To speed up wake-and-unlock sequence, force it to report
                    // the next draw from here so we don't have to wait for window manager to signal
                    // this to our ViewRootImpl.
                    mStatusBarKeyguardViewManager.getViewRootImpl().setReportNextDraw();
                    notifyDrawn(mDrawnCallback);
                    mDrawnCallback = null;
                }
    
                // only play "unlock" noises if not on a call (since the incall UI
                // disables the keyguard)
                if (TelephonyManager.EXTRA_STATE_IDLE.equals(mPhoneState)) {
                    playSounds(false);
                }
    
                mWakeAndUnlocking = false;
                setShowingLocked(false, mAodShowing);
                mDismissCallbackRegistry.notifyDismissSucceeded();
                /*重点关注*/
                mStatusBarKeyguardViewManager.hide(startTime, fadeoutDuration);
                resetKeyguardDonePendingLocked();
                mHideAnimationRun = false;
                adjustStatusBarLocked();
                sendUserPresentBroadcast();
            }
            Trace.endSection();
        }
    
    

    frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java

        /**
         * Hides the keyguard view
         */
        public void hide(long startTime, long fadeoutDuration) {
            mShowing = false;
            mKeyguardMonitor.notifyKeyguardState(
                    mShowing, mKeyguardMonitor.isSecure(), mKeyguardMonitor.isOccluded());
            launchPendingWakeupAction();
    
            if (KeyguardUpdateMonitor.getInstance(mContext).needsSlowUnlockTransition()) {
                fadeoutDuration = KEYGUARD_DISMISS_DURATION_LOCKED;
            }
            long uptimeMillis = SystemClock.uptimeMillis();
            long delay = Math.max(0, startTime + HIDE_TIMING_CORRECTION_MS - uptimeMillis);
    
            if (mStatusBar.isInLaunchTransition() ) {
                mStatusBar.fadeKeyguardAfterLaunchTransition(new Runnable() {
                    @Override
                    public void run() {
                        mStatusBarWindowController.setKeyguardShowing(false);
                        mStatusBarWindowController.setKeyguardFadingAway(true);
                        hideBouncer(true /* destroyView */);
                        updateStates();
                    }
                }, new Runnable() {
                    @Override
                    public void run() {
                        mStatusBar.hideKeyguard();
                        mStatusBarWindowController.setKeyguardFadingAway(false);
                        mViewMediatorCallback.keyguardGone();
                        executeAfterKeyguardGoneAction();
                    }
                });
            } else {
                executeAfterKeyguardGoneAction();
                boolean wakeUnlockPulsing =
                        mBiometricUnlockController.getMode() == MODE_WAKE_AND_UNLOCK_PULSING;
                if (wakeUnlockPulsing) {
                    delay = 0;
                    fadeoutDuration = 240;
                }
                mStatusBar.setKeyguardFadingAway(startTime, delay, fadeoutDuration);
                mBiometricUnlockController.startKeyguardFadingAway();
                /*重点关注*/
                hideBouncer(true /* destroyView */);
                if (wakeUnlockPulsing) {
                    mStatusBar.fadeKeyguardWhilePulsing();
                    wakeAndUnlockDejank();
                } else {
                    boolean staying = mStatusBar.hideKeyguard();
                    if (!staying) {
                        mStatusBarWindowController.setKeyguardFadingAway(true);
                        // hide() will happen asynchronously and might arrive after the scrims
                        // were already hidden, this means that the transition callback won't
                        // be triggered anymore and StatusBarWindowController will be forever in
                        // the fadingAway state.
                        mStatusBar.updateScrimController();
                        wakeAndUnlockDejank();
                    } else {
                        mStatusBar.finishKeyguardFadingAway();
                        mBiometricUnlockController.finishKeyguardFadingAway();
                    }
                }
                updateStates();
                mStatusBarWindowController.setKeyguardShowing(false);
                mViewMediatorCallback.keyguardGone();
            }
            StatsLog.write(StatsLog.KEYGUARD_STATE_CHANGED,
                StatsLog.KEYGUARD_STATE_CHANGED__STATE__HIDDEN);
        }
    
    

    hideBouncer

        private void hideBouncer(boolean destroyView) {
            if (mBouncer == null) {
                return;
            }
            /*重点关注*/
            mBouncer.hide(destroyView);
            cancelPendingWakeupAction();
        }
    
    

    frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java

        public void hide(boolean destroyView) {
            if (isShowing()) {
                StatsLog.write(StatsLog.KEYGUARD_BOUNCER_STATE_CHANGED,
                    StatsLog.KEYGUARD_BOUNCER_STATE_CHANGED__STATE__HIDDEN);
                mDismissCallbackRegistry.notifyDismissCancelled();
            }
            mIsScrimmed = false;
            mFalsingManager.onBouncerHidden();
            mCallback.onBouncerVisiblityChanged(false /* shown */);
            cancelShowRunnable();
            if (mKeyguardView != null) {
                mKeyguardView.cancelDismissAction();
                mKeyguardView.cleanUp();
            }
            mIsAnimatingAway = false;
            if (mRoot != null) {
                mRoot.setVisibility(View.INVISIBLE);
                if (destroyView) {
    
                    // We have a ViewFlipper that unregisters a broadcast when being detached, which may
                    // be slow because of AM lock contention during unlocking. We can delay it a bit.
                    /*重点关注*/
                    mHandler.postDelayed(mRemoveViewRunnable, 50);
                }
            }
        }
    
    

    mRemoveViewRunnable

    private final Runnable mRemoveViewRunnable = this::removeView;
        protected void removeView() {
            if (mRoot != null && mRoot.getParent() == mContainer) {
                /*重点关注*/
                mContainer.removeView(mRoot);
                mRoot = null;
            }
        }
    
    

    至此锁屏界面移除的逻辑基本clear
    原文链接:https://blog.csdn.net/Easyhood/article/details/104353983

    友情推荐:

    Android 开发干货集锦

    至此,本篇已结束。转载网络的文章,小编觉得很优秀,欢迎点击阅读原文,支持原创作者,如有侵权,恳请联系小编删除,欢迎您的建议与指正。同时期待您的关注,感谢您的阅读,谢谢!

    点击阅读原文,为大佬点赞!

    展开全文
  • 最近闲来无事,研究研究在安卓...但是由于安卓打包的工具链很长,包括 Android Sdk 打包 Java 代码、NDK 编译 Python、 编译各种 Python 依赖包,经常花一整天从入门到放弃。这次使出认真研究的心态,终于找到一个解...

    最近闲来无事,研究研究在安卓上跑 Python,想起以前玩过的 kivy 技术,kivy 是一个跨平台的 UI 框架,当然对我们最有用的是,kivy 可以把 Python 代码打包成安卓应用。

    但是由于安卓打包的工具链很长,包括 Android Sdk 打包 Java 代码、NDK 编译 Python、 编译各种 Python 依赖包,经常花一整天从入门到放弃。

    wAAACwAAAAAAQABAEACAkQBADs=

    wAAACwAAAAAAQABAEACAkQBADs=

    这次使出认真研究的心态,终于找到一个解决方案,于是有了这篇文章。

    只要会 Python 就能写安卓 App,无需安卓开发基础,无需编译

    手机上也有交互式 Python 解释器,直接调试 Python 代码

    可以使用各种 Python 库,包括 numpy/opencv 等机器学习包

    可以与安卓接口交互,使用手机硬件,比如摄像头

    那么我们就以人脸识别 App 为例,看看如何简单几步搞定,先看看成品的效果。

    wAAACwAAAAAAQABAEACAkQBADs=

    1、安装 airport.apk

    AirPort 是我编译好的一个安卓 App,里面包含了 Python 解释器和一些常用的依赖库。

    2、连接手机的 Python 解释器

    启动手机上的 AirPort 应用,就会运行 Python 解释器,为了调试的方便,应用内置了一个 ssh 服务器,启动的时候会显示手机的 IP 地址。

    wAAACwAAAAAAQABAEACAkQBADs=

    在电脑上使用 ssh 命令,就可以连接到手机。

    ps: 注意:确保你的手机和电脑在同一局域网中。

    # 在电脑上连接手机,注意这里ip需要替换成AirPort显示的ip

    ssh -p 8000 admin@192.168.31.101

    # 输入密码,这里密码是固定为:meteorix

    meteorix

    然后你就可以在手机上尽情使用 Python 了。

    3、摄像头的 App

    在 kivy 的官方文档中,我们可以找到这样一个摄像头的 example

    wAAACwAAAAAAQABAEACAkQBADs=

    代码非常简单,Builder.load_string 函数加载了一段配置,这是 kivy 提供的 UI 定义语言 kivy language。

    点击 UI 上创建的 Capture 按钮,回调 CameraClick.capture() 函数,用 Python 实现函数功能。

    from kivy.app import App

    from kivy.lang import Builder

    from kivy.uix.boxlayout import BoxLayout

    import time

    Builder.load_string(”’

    :

    orientation: ‘vertical’

    Camera:

    id: camera

    resolution: (640, 480)

    play: False

    ToggleButton:

    text: ‘Play’

    on_press: camera.play = not camera.play

    size_hint_y: None

    height: ’48dp’

    Button:

    text: ‘Capture’

    size_hint_y: None

    height: ’48dp’

    on_press: root.capture()

    ”’)

    class CameraClick(BoxLayout):

    def capture(self):

    ”’

    Function to capture the images and give them the names

    according to their captured time and date.

    ”’

    camera = self.ids[‘camera’]

    timestr = time.strftime(“%Y%m%d_%H%M%S”)

    camera.export_to_png(“IMG_{}.png”.format(timestr))

    print(“Captured”)

    class TestCamera(App):

    def build(self):

    return CameraClick()

    TestCamera().run()

    将这段代码保存为 kvmain.py 文件,我们可以直接在电脑上运行,如果你的电脑有摄像头,就可以看到摄像头 App 的效果。

    4、推送代码到安卓手机

    这一步需要做的就是,把这个摄像头 App 推送到安卓手机上,然后启动 AirPort 应用,将 kvmain.py 推送到手机 /sdcard/kv/kvmain.py 路径,然后启动 AirPort 应用,就会加载这个路径下的 Python 代码。

    adb shell mkdir -p /sdcard/kvadb push kvmain.py /sdcard/kv/kvmain.py

    重新启动手机上的 AirPort 应用,即可看到我们的摄像头 App 运行在手机上了。

    5、增加人脸识别功能

    这一步,我们主要用到了 opencv 的人脸识别接口。

    import cv2

    detector = cv2.CascadeClassifier(‘haarcascade_frontalface_default.xml’)

    img = cv2.imread(‘faces.jpg’)

    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

    faces = detector.detectMultiScale(gray, 1.3, 5)

    print(faces)

    最后修改 App 代码,读取摄像头的图片,调用 opencv 人脸识别接口,将识别出来的人脸坐标,画到手机屏幕的对应位置上。

    bbox = BoundingBox(name=face_name, size_hint=(None, None))

    for loc in faces:

    # calculate position of the face

    x, y, w, h = loc

    t = int(anchor_t – y*sh)

    b = int(anchor_t – (y+h)*sh)

    r = int(anchor_l + x*sw)

    l = int(anchor_l + (x+w)*sw)

    # update bounding box

    bbox.pos = (int(l), int(b))

    bbox.size = (int(r-l), int(t-b))

    当然,我们还需要针对安卓手机进行一些调试,我们再次推送代码到手机上。

    adb push src/* /sdcard/kv/

    重启应用就可以看到上文展示的 GIF 效果了。

    https://www.jianshu.com/p/f3b64d3456be

    Python量化投资网携手4326手游为资深游戏玩家推荐:《无敌的椅子下载》

    展开全文
  • NULL 博文链接:https://govfate.iteye.com/blog/1637443
  • 原标题:华为Mate 10升级人脸解锁:可能是最好用的安卓人脸识别随着全面屏手机的普及,手机的解锁方式也由单一的指纹变得更加多元化,其中人脸识别成为用户呼声最高的潮流解锁姿势。抬手之间,就完成解锁,在一些...
  • Android11.0 增加人脸解锁功能

    千次阅读 2021-07-16 15:40:26
    从 Q 版本开始 aosp 代码就已经默认支持 Biometric 生物识别相关功能,Settings 中不显示入口菜单是因为判断了 硬件是否支持。可以先看下安全页面中显示的菜单如下。 我的设备默认是支持指纹模块的,所以在安全菜单...
  • Android自带的人脸识别

    2021-06-03 04:10:59
    1.Android自带的人脸识别Android自带的人脸识别只能识别出人脸在画面中的位置,中点,眼间距,角度等基本特性,提供给拍照性质的应用使用。从基本功能中不能得出明显的特征数据2.底层库支持external/neven/*3.接口...
  • 这篇文章起源于我们用pad开发了一个人脸识别系统,装在电动门上代替刷卡闸机,作为一个从来没接触过android开发的小白,就靠着下面这些教程入门了,其实这个是一些人脸识别必备的资料和教程,可以收藏之后有需要的...
  • Android人脸识别

    万次阅读 2018-01-09 11:27:03
    前言 人工智能时代快速来临,其中人脸识别是当前比较热门的技术,在国内也越来越多的运用,例如刷脸打卡、刷脸App,身份识别,人脸门禁等等。...Android人脸识别 Android实现人脸识别可以通过google原生自带或第...
  • 使用于目前面部识别的测试用例,编写执行主要步骤比较合理,符符合大体流程,仅供参考
  • [Android]Android人脸识别接口的使用

    千次阅读 2016-06-20 18:43:57
    本文的目标是介绍android人脸识别API。 在翻看Android API的时候无意间发现Android自带了人脸识别的接口:android.media.FaceDetector,于是就实验了一下,下面是过程: 1.布局: <RelativeLayout xmlns:android=...
  • 人脸识别,人脸解锁

    2016-01-08 10:05:43
    人脸识别 人脸解锁 99%正确率,简单实用 底层是科大讯飞实现
  • 随着AI技术的发展,人脸识别的应用场景越来越多,提供技术支持的API也有好多可以选择,但是大部分都是需要收费的,或者免费试用。由此可见人脸识别算法确实是核心技术,不是随便就可以获取到的。经过多次尝试,记录...
  • Android人脸识别

    千次阅读 2018-08-23 10:03:46
    Android自带的人脸识别API 第三方提供 大牛们的封装 Android自带的人脸识别API Android实现人脸识别可以通过google原生自带API实现,只能识别静态图片,缺点是精度不高,识别信息很少,只有眼睛的识别 栗子 ...
  • 基于Android人脸识别系统设计与实现基于Android人脸识别系统设计与实现摘要:人脸识别是公共安全领域的研究重点。随着移动互联网的快速发展,移动式终端人脸识别应用日益广泛。探讨人脸识别在Android系统中的实现...
  • 安卓SDK——人脸识别

    千次阅读 2019-05-23 16:15:05
    引 还是讯飞,前两篇文章 语音识别 https://blog.csdn.net/nishigesb123/article/details/90478104 语音合成 https://blog.csdn.net/nishigesb123/article/details/90478584 ...人脸识别说明文档:https://do...
  • Android人脸识别--基于虹软免费SDK

    千次阅读 多人点赞 2017-09-18 13:17:30
    苹果刚发布最新的iphone X,新增了人脸识别解锁,我也挺好奇,没有尝试过,所以就在闲暇的时候找了些资料,写了一个小项目。当前的人脸识别技术分为WEBAPI和SDK调用两种方式,WEBAPI需要实时联网,SDK调用可以离线...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 2,156
精华内容 862
关键字:

安卓人脸识别解锁