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

cordova battery-status插件

$
0
0


介绍

这个 Cordova 插件用于监视设备的电池状态。

 

 

 

安装

cordova plugin add cordova-plugin-battery-status

 

 

 

 

 

使用方法:

应用程序可以使用window.addeventlistener将上述事件的事件侦听器,不过要在deviceready事件之后触发

document.addEventListener("deviceready", onDeviceReady, false);

function onDeviceReady() {

    console.log(“在此处使用插件”);

}




 

事件

这个插件提供了一个旧版本的实现电池状态事件API。它增加了以下三个事件window 象:

· batterystatus

· batterylow

· batterycritical

 


状态对象Status object

在这个插件中的所有事件还具有以下属性的对象:

 level:电池的充电率(0-100)。(数字型)

 isPlugged一个布尔值指示是否这个设备已经插入电源。(布尔

 

 

 


电池状态事件batterystatus event

当电池电量百分比变化至少1%,或当设备插入或拔出。返回一个电池状态的对象

  

支持的平台Supported Platforms

· Amazon Fire OS

· iOS

· Android

· BlackBerry 10

· Windows Phone 7 and 8

· Windows (Windows Phone 8.1 and Windows 10)

· Firefox OS

· Browser (Chrome, Firefox, Opera)

  

Android & Amazon Fire OS 特性:

警告:AndroidFire OS的实现是贪婪的,长时间使用会消耗设备的电池。

 

Windows Phone 7 & Windows Phone 8 特性: 

这个level属性不支持Windows Phone 7因为操作系统不提供原生API来确定电池电量。

这个isPlugged 参数是支持.

 

Windows Phone 8.1 特性: 

这个isPlugged参数不支持Windows Phone 8.1

这个level参数是支持.





 

蓄电池事件batterylow event

当电池电量百分比达到了较阈值时,触发此事件。这个值是特定于设备的(此值在不同的设备可能有所不同)。返回一个目标含电池状态

  

支持的平台Supported Platforms

· Amazon Fire OS

· iOS

· Android

· BlackBerry 10

· Firefox OS

· Windows (Windows Phone 8.1 and Windows 10)

· Browser (Chrome, Firefox, Opera)

  

Windows Phone 8.1 特性: 

这个蓄电池事件会在Windows Phone 8.1不管设备插入或不。这是因为操作系统没有提供API来检测设备是否插好。





 

batterycritical事件

当电池充电率达到临界电荷阈值,触发此事件。这个值是特定于设备的(此值在不同的设备可能有所不同)。返回一个含电池状态的对象

 

 支持的平台Supported Platforms

· Amazon Fire OS

· iOS

· Android

· BlackBerry 10

· Firefox OS

· Windows (Windows Phone 8.1 and Windows 10)

· Browser (Chrome, Firefox, Opera)

  

Windows Phone 8.1 特性:

这个batterycritical事件会在Windows Phone 8.1不管设备是否插入。这是因为操作系统没有提供API来检测设备是否插好。

 




示例

index.html

<!DOCTYPE html>
<html>
    <head>
        <meta http-equiv="Content-Security-Policy" content="default-src 'self' data: gap: https://ssl.gstatic.com 'unsafe-eval'; style-src 'self' 'unsafe-inline'; media-src *; img-src 'self' data: content:;">
        <meta name="format-detection" content="telephone=no">
        <meta name="msapplication-tap-highlight" content="no">
        <meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width">
        <title>Hello World</title>
    </head>
    <body>
        <div class="app">
            <h1>电池状态</h1>
            <div id="device"></div>
        </div>
        <script type="text/javascript" src="cordova.js"></script>
        <script type="text/javascript" src="js/index.js"></script>
    </body>
</html>

 

index.js

var app = {
    initialize: function() {
        document.addEventListener('deviceready', this.onDeviceReady.bind(this), false);
    },
    onDeviceReady: function() {
        this.onBatterystatu();
        this.onBatterylow();
        this.onBatterycritical();
    },
    /* 电量变化 */
    onBatterystatu: function(){
        window.addEventListener("batterystatus", onBatteryStatus, false);
        function onBatteryStatus(status) {
            console.log("Level: " + status.level + "///// isPlugged: " + status.isPlugged);
        }
    },
    /* 电量较低 */
    onBatterylow:function(){
        window.addEventListener("batterylow", onBatteryLow, false);
        function onBatteryLow(status) {
            console.log("Battery Level Low " + status.level + "%");
        }
    },
    /* 电量已满 */
    onBatterycritical: function(){
        window.addEventListener("batterycritical", onBatteryCritical, false);
        function onBatteryCritical(status) {
            console.log("Battery Level Critical " + status.level + "%\nRecharge Soon!");
        }
    }
};

app.initialize();

 

运行:

显示电量100,是否正在充电





作者:michael_ouyang 发表于2017/7/21 21:08:31 原文链接
阅读:240 评论:0 查看评论

程序员日记(一)

$
0
0
     2017.7.21   北京    晴雨交加
    不知道怎么的,突然想写点东西。可能是傻逼一样的语无伦次,但也无关紧要。我不知道即将写下的这些文字,是为了给别人看,还是纯粹为了排遣内心的某种情绪或是记录什么,大概前者的原因多一些,但给别人看是为了什么,我也说不好自己的动机。近来特别关注CSDN的博客排名。每天上班的第一件事就是打开电脑,登录qq,打开火狐,登录csdn查看博客排名。然后白天一整天和晚上有空也会看看。要说我为什么这么关注这排名,这大概和当年关注qq等级和点亮qq图标是一样的情结。或许写下这篇流水账正因为如此,什么?骗点击量?嗯嗯。一十二十不算少,三十四十算赚着,排名能靠前一点是一点。好嘛。无聊的人,总得有点无聊的追求。嘿,咱幼稚着呢。
    因为项目需要,需要写音频播放器,拿AVPlayer随便写了个单利,简单的在线播放功能也算满足了。但和写下这篇文章一样,也是心血来潮的想写个音频播放的第三方。于是乎,最近几日晚上下班回家以后都会抠这个播放器。要说写第三方,还真是比单纯的写功能麻烦的多。要变着花样的暴露接口,原本几句就写完的功能,各种代理,各种通知,真麻烦。这不,昨天晚上,一写写到12点半多。靠,12点半啊!什么概念?哈哈,蒙圈了。靠,对我这个颜值高于一切的人来说,这他妈简直是犯罪。虽然比平时只晚睡了半个小时。哦,我是男的。我的前前任老板说我是三里屯小帅哥,当时我在同事圈自封五道口霸主。我的前任老板说我是侧颜杀,当然还有别的朋友说…而对我来说,虽然有时也会对着镜子对自己产生性冲动。可大多数时候,总觉得自己丑。我靠,丑是我不能忍受的。可以因为我穷,因为我矮,看不上我,你要说我丑,求你插我一刀吧。今天下班地铁上,在我身旁站着一个帅哥,是真的帅的那种。眼睛特别深邃有神,鼻梁像咱华哥,脸型也真的不错,这么说好像还是不那么具象,这么说吧,淘宝上,等下,我看看记录。哦,来了。比如“都市男女、个性迷彩情侣短T、港风夏季男士、哈伦弹力小脚裤日系韩版”,诸如此类的字眼衣服的男模那个模样。他就是现实版的,说实话这种样貌的人在朝九晚六的程序员生活中并不常见。我从呼家楼上车从国贸换乘,不避讳的说,我看了他一路,大概他余光扫到有人正在不时的偷瞄他,他也装作不经意的看了我两次,靠,真他妈帅,我要是他这样,肯定会有女朋友。这事在我换乘以后,跟我从未见面的网上老相好说了,她强烈要求我拍照,我跟她说,我早就换乘了。她还开玩笑地说“你不会性取向有变化了吧”。我说,怎么会呢,我只喜欢你一个。然后加一个微信表情“奸笑”。哈哈,老相好,真好骗。还是回到插我一刀的事上吧,公司的楼下是一个大超市,从地铁可以直接穿到这个超市里面 ,然后上楼进公司。在这个超市通往厕所的过道里,有三面超大的镜子。我一般会假借上厕所之名去照镜子,潇洒地往上捋一把刘海,有时内心暗喜,哇,今天真帅。有时内心一片凄凉,靠,今天脸色怎么这么差,靠,这发型怎么这样。而我今天就是后一种,穿过走廊前往厕所的时候,看到脸色像那彩笔灰,跑进厕所洗了把手又折返回来,在仔细定睛一看,哦,脸色也不是那么暗嘛,发型也不错,然后高高兴兴的上楼了。
    走进办公室的时候,看到前台小妹妹在昨天请了一天假以后,今天又按时上班了。因为我们程序开发团队是后来整体进入这个公司的,所以我们对这里的人还都不熟悉。我们运营给前台小妹妹取了个外号,叫高冷小姐姐。至于为什么叫这个,只是因为有一次我们运营叫她倒杯水给一个领导,而高冷小姐姐说,那不是有水嘛。大体就是这样说的吧。因为我们是后进来的研发团队,所以我们并不按照公司的上下班时间,也不用打卡。我们会比他们晚半个小时上班,晚半个小时下班。当然,几乎每次都是晚一个多小时。高冷小姐姐在我刚来这个公司的时候,每当我从电梯间走出来走进房门的时候都会抬头看我一眼,其实,我一出电梯间,就会用我那迷离的眼神注视着她坐的位置,可近来,她都不会再看了。高冷小姐姐衣品很好,经常穿个短裤,两条大白腿很性感。高冷小姐姐好像很腼腆,几乎不会大声讲话。我跟高冷小姐姐到目前只说过一句话,因为饮水机在她旁边,有一次我去接水,饮水机里没水了。我准备换水,我指着她旁边堆着的几桶水,明知故问地对她说,是这个吗?然后,她看着我“嗯”了一声。嗯,就这样。嗯?等下。我正在单曲循环孙燕姿的“我不难过”,你们有没有听到曲子开头有类似蛐蛐的叫声。我单曲循环着写这篇流水账,也是刚发现这歌曲里的蛐蛐声,我还摘下耳机,以为是窗外的蛐蛐叫声呢,嗯,窗外的叫声已经分不清是青蛙叫声还是蛐蛐声了。靠,快一点了。完蛋。明天周六,哦不,今天周六,不用上班,还是写完吧。高冷小姐姐应该是我对这里新同事第一个说话的人了。
    中午吃饭是我一个人去的。我去吃了牛肉汤,是82号,可是82号的碗里,没有鹌鹑蛋。三个鹌鹑蛋的标配没有了,连菜叶好像也比以前少。不开心。我没告诉店家,懒得去说了。店很火,人很多。下午,测试了一下项目,修改了一写逻辑,没有写博客,也没有转发。下班地铁里的时候,网上老相好说晚上要跟我腻歪。到家的时候,已经八点半了,她说她去洗衣服。而我躺在床上看了几个秒拍视频,别说那个吃萝卜的狗还真是可爱,还有旅游团大妈俄罗斯偶遇普京大帝,大喊我爱你。还有“这大概是我见过最怂的熊了”,这个太搞笑了,一只大胖黑熊跑进了一个住户家里,竟然让一只小狗的叫声吓得翻出了围墙。笑死了。之后,我又翻开笔记本,看了几眼播放器代码,已经是将近10点了。换好行装,拿上跳绳,楼下单摇双摇一共1000个,出汗的感觉很好,仿佛我运动,我的皮肤状态就会变好,不过好像还真有点关系。运动的时候,网上老相好微信问我干嘛去了,怎么不理人。我说,我在锻炼。还说,我一会要去奢侈一把,买瓶加多宝。老相好很理智地不理我了。哎。女人真麻烦。回到家,洗了澡,写了这些东西。呜呜呀呀,不知所言。
    
   
作者:HDFQQ188816190 发表于2017/7/22 1:09:13 原文链接
阅读:33 评论:0 查看评论

cordova network-information插件

$
0
0

 

介绍

这个插件提供了有关设备的蜂窝和wifi连接信息和设备是否有网络连接。

 

 

 

 

 

安装

cordova plugin add cordova-plugin-network-information

 

 

 

 

 


支持的平台Supported Platforms

· Amazon Fire OS

· Android

· BlackBerry 10

· Browser

· iOS

· Windows Phone 7 and 8

· Tizen

· Windows

· Firefox OS

 

 

 


 

详情

连接Connection

这个connection对象,暴露了navigator.connection,提供关于设备的蜂窝和WiFi连接信息。

 

 

 

 

属性Properties

· connection.type

 

 

connection.type

此属性提供了一个快速的方法来确定设备的网络连接状态和连接类型。

 

iOS 特性

无法检测到蜂窝网络连接的类型

navigator.connection.type 设置为Connection.CELL 为所有的蜂窝数据

 

 

 

常量Constants

· Connection.UNKNOWN

· Connection.ETHERNET

· Connection.WIFI

· Connection.CELL_2G

· Connection.CELL_3G

· Connection.CELL_4G

· Connection.CELL

· Connection.NONE

 

 

 

 

网络相关事件Network-related Events

离线offline

事件触发时,一个应用程序脱机,和设备没有连接到互联网。

document.addEventListener("offline", yourCallbackFunction, false);

 

详情Details

这个offline 事件触发时,先前连接的设备失去网络连接,应用程序不能访问互联网。它依赖于相同的信息连接的API,和连接时的值connection.type成为NONE

应用程序应该使用document.addEventListener附加到事件监听器deviceready事件触发

 

小示例

document.addEventListener("offline", onOffline, false);

function onOffline() {

    // Handle the offline event

}

 

iOS 特性

During initial startup, the first offline event (if applicable) takes at least a second to fire.

在初始启动时,第一个离线事件(如适用)需要至少一秒才触发

 

 

 

在线online

当一个应用上线事件会被触发,和设备将连接到网络

document.addEventListener("online", yourCallbackFunction, false);

 

详情Details

当先前无连接的设备接收网络连接,允许应用连接到网络时,这个online 事件触发。它依赖于相同的信息连接的API,和connection.typeNONE改变到其他值时触发

应用程序应该使用document.addEventListener附加到事件监听器deviceready事件触发

 

小示例

document.addEventListener("online", onOnline, false);

function onOnline() {

    // Handle the online event

}

 

iOS 特性

在初始启动时,首先online 事件(要是可的话),需要至少一秒才触发,要是connection.typeUNKNOWN之前

 




示例

示例一:

 

index.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta http-equiv="Content-Security-Policy" content="default-src 'self' data: gap: https://ssl.gstatic.com 'unsafe-eval'; style-src 'self' 'unsafe-inline'; media-src *; img-src 'self' data: content:;">
    <meta name="format-detection" content="telephone=no">
    <meta name="msapplication-tap-highlight" content="no">
    <meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width">
    <title>Hello World</title>
    <style>
         .line{
              padding: 10px;
         }
    </style>
</head>
<body>
<div class="app">
    <h1>网络信息</h1>
    <div class="line"><button id="btn">按钮</button></div>
</div>
<script type="text/javascript" src="cordova.js"></script>
<script type="text/javascript" src="js/index.js"></script>
</body>
</html>

 

index.js

var app = {
    initialize: function() {
        document.addEventListener('deviceready', this.onDeviceReady.bind(this), false);
    },
    onDeviceReady: function() {
        this.receivedEvent();
    },
    receivedEvent: function() {
        var _this = this;
        var btn = document.getElementById("btn");
        btn.onclick = function(){
            _this.checkConnection();
        }
    },
    checkConnection() {
        var networkState = navigator.connection.type;
        var states = {};
        states[Connection.UNKNOWN]  = 'Unknown connection';
        states[Connection.ETHERNET] = 'Ethernet connection';
        states[Connection.WIFI]     = 'WiFi connection';
        states[Connection.CELL_2G]  = 'Cell 2G connection';
        states[Connection.CELL_3G]  = 'Cell 3G connection';
        states[Connection.CELL_4G]  = 'Cell 4G connection';
        states[Connection.CELL]     = 'Cell generic connection';
        states[Connection.NONE]     = 'No network connection';
        alert('Connection type: ' + states[networkState]);
    }
};

app.initialize();

 

运行:


点击“按钮”,显示网络信息是WIFI网络





示例二:

在示例一的基础上,加入了onOnline事件监听器,设备在线的情况下触发此事件。

index.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta http-equiv="Content-Security-Policy" content="default-src 'self' data: gap: https://ssl.gstatic.com 'unsafe-eval'; style-src 'self' 'unsafe-inline'; media-src *; img-src 'self' data: content:;">
    <meta name="format-detection" content="telephone=no">
    <meta name="msapplication-tap-highlight" content="no">
    <meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width">
    <title>Hello World</title>
    <style>
         .line{
              padding: 10px;
         }
    </style>
</head>
<body>
<div class="app">
    <h1>网络信息</h1>
    <div class="line"><button id="btn">按钮</button></div>
</div>
<script type="text/javascript" src="cordova.js"></script>
<script type="text/javascript" src="js/index.js"></script>
</body>
</html>

 

index.js

var app = {
    initialize: function() {
        document.addEventListener('deviceready', this.onDeviceReady.bind(this), false);
    },
    onDeviceReady: function() {
        document.addEventListener("online", this.onOnline.bind(this), false);
    },
    // 获取网络状态
     onOnline() {
         var networkState = navigator.connection.type;
         // 如果状态不等于Connection.NONE(即有网络)
         if (networkState !== Connection.NONE) {
             this.receivedEvent();
         }
     },
    receivedEvent: function() {
        var _this = this;
        var btn = document.getElementById("btn");
        btn.onclick = function(){
            _this.checkConnection();
        }
    },
    checkConnection() {
        var networkState = navigator.connection.type;
        var states = {};
        states[Connection.UNKNOWN]  = 'Unknown connection';
        states[Connection.ETHERNET] = 'Ethernet connection';
        states[Connection.WIFI]     = 'WiFi connection';
        states[Connection.CELL_2G]  = 'Cell 2G connection';
        states[Connection.CELL_3G]  = 'Cell 3G connection';
        states[Connection.CELL_4G]  = 'Cell 4G connection';
        states[Connection.CELL]     = 'Cell generic connection';
        states[Connection.NONE]     = 'No network connection';
        alert('Connection type: ' + states[networkState]);
    }
};

app.initialize();


运行:

在触发时,会有一秒的迟缓


点击“按钮”,显示网络信息是WIFI网络






运行:


点击“按钮”,显示网络信息是WIFI网络

作者:michael_ouyang 发表于2017/7/22 8:59:17 原文链接
阅读:48 评论:0 查看评论

Flutter学习之旅(三)----Flutter常见问题FAQ,看完之后不再是Flutter小白

$
0
0

很多人还不熟悉甚至没听过Flutter,只知道它是用来搞移动端开发的,现在Android和iOS开发已经很成熟了,还有RN(React Native)框架,为什么谷歌还要搞一个Flutter,它能够做什么,究竟比别人好在哪里,有什么优势和不足,又是怎么做到的?下面是一些关于Flutter的常见问题,参考文章(英文版)。
相信看完之后你就真相大白了。

什么是Flutter?

Flutter是移动端开发SDK,包括框架,控件和工具等,能开发出漂亮的移动端APP,且同时支持Android和iOS。

Flutter为谁而生?

如果你是移动端开发者,并且想以快速而又简单的方式开发漂亮的APP,那么Flutter是为你而生。

我能使用Flutter开发什么样的APP?

Flutter为运行在Android和iOS的2D APP而优化。Flutter开发的APP既适用于简单场景,比如传递品牌价值,也适用于复杂场景,比如股票交易平台。你可以用Flutter开发出各种有特点的APP,比如相机,地理定位,网络,存储和第三方SDK等待。

有哪些人使用了Flutter?

Google公司的销售工具APP-商店管理APP和Newsvoice的Android和iOS端的APP都是用Flutter开发的,还有一些其他正在用Flutter开发的APP。

Flutter的特别之处在哪里?

Flutter既不使用WebView,也不使用系统的原生控件,而是通过高性能的渲染引擎来画控件。除此之外,Flutter不同是因为它只有C/C++代码编写的单一层,这样开发者更容易控制系统,或者说更容易读取或者修改系统的组件,手势,动画框架和控件等等。

我应该用Flutter开发我的下一个APP产品吗?

Flutter正在开发中并尚未达1.0版,不过API很稳定,你需要的特点基本上已经都提供了,所以最终取决于你自己。

Flutter SDK包括哪些东西?

*深度优化,手机优先的2D渲染引擎以及对文本的极佳支持。
*Rx(响应式编程)框架
*针对Android和iOS丰富的控件集合
*提供单元和集成测试API
*提供连接系统和第三方SDK的API
*使用命令行来create, build,test 和 compile APP

Flutter工作是否需要编辑器或者IDE?

在IntelliJ IDEA(同时支持Ultimate和Community版本)里面安装Flutter插件即可工作。或者你可以结合flutter命令行和支持编辑Dart的编辑器。

Flutter是否包含框架?

是的,有响应式编程框架,灵感来源于React框架。不过该框架是可选的,开发者可以选择使用该框架的一部分或者换一个其他框架。

Flutter包含依赖注入(dependency injection)框架吗?

暂时没有

Flutter用什么技术构建的?

C, C++, Dart, and Skia (2D 渲染引擎)

Flutter如何将代码运行在Android上?

引擎的C/C++代码使用Android的NDK编译的,并且框架的大部分和APP代码作为本地代码(由Dart编译器编译的)运行的。

Flutter如何将代码运行在iOS上?

引擎的C/C++代码使用LLVM编译,并且任何Dart代码都是AOT编译成本地代码。

Flutter使用了我的系统的原生(OEM)控件吗?

没有,Flutter提供了一系列的MD风格和Cuperitno(iOS风格)风格的控件,假如我们复用OEM控件,那么FLutter APP的性能会被这些控件质量所限制。

FLutter支持哪些操作系统?

支持Linux, Mac和Windows。

开发FLutter使用什么语言?

使用Dart语言,其底层图表框架和Dart虚拟机用C/C++实现。

为什么FLutter选择使用Dart?

选择一门编程语言遵循如下标准:
*提高开发效率:一套代码运行在iOS和Android,极大地加速开发。
*面向对象:FLutter需要创建可视的用户体验,所以被选择的语言应该是面向对象编程,并且在构建UI框架上经验丰富。如果选择非面向对象语言就是重复造轮子。
*可以预见的,高性能的:FLutter旨在让开发者开发更迅速,让用户体验更流畅。为了实现该目标,需要每个动画帧能够运行大量的代码,也意味着被选择的语言能同时传递高性能和传递可预见的性能,而不是由于周期性停顿而引起丢帧。
*快速分配内存:FLutter框架使用了Rx响应式编程,它依赖于底层内存分配器(处理小而短的内存分配),所以被选择的语言需要有该特性。
Dart语言满足这四个条件,除此之外,我们还有机会与Dart社区一起近距离工作(不断完善Dart以便在Flutter中更好的使用)。

Flutter引擎有多大?

2017年5月,我们测量了Flutter APP的最小尺寸(没有MD风格,仅仅是Center控件),release版apk接近6.7M。在这个最小apk中,核心引擎大约3.3M,框架和APP代码约1.25M,LICENSE文件(在app.flx中)约55k,必须的Java代码(classes.dex)是40k,还有2.1M的ICU数据。你也可以自己测量你的APP大小,执行flutter build apk 并查看 app/outputs/apk/app-release.apk。

Flutter APP性能究竟如何?

性能很优秀,Flutter是为60fps而设计的,Flutter运行的是编译后的代码,而不是解释性程序,这意味着Flutter能快速启动。

从IDE下载程序到手机需要多长时间?

Flutter有热加载(hot reload)功能,亚秒级加载时间。热加载功能是状态保持的(stateful),也就是说热加载后APP状态是保留的,这意味着你可以快速重复屏幕内容而不需要从主屏幕开始加载。

热加载与完全重启哪里不同?

热加载是注入源代码到运行中的Dart虚拟机,包括增加新的类,和给已有类增加新的方法和变量以及修改已有方法。下面几种情况热加载无效:
*全局变量初始化
*静态变量初始化
*修改main()方法

Flutter APP支持的设备和操作系统版本?

支持的手机操作系统:Android Jelly Bean, v16, 4.1.x or newer, and iOS 8 or newer
手机硬件:64-bit iOS 手机(从iPhone 5S开始), and ARM Android 手机。
支持Android 和iOS物理设备,支持Android和iOS模拟器,不支持平板。

Flutter能在web上运行吗?

不支持

Flutter支持开发桌面APP吗?

手机开发优先,鼓励以其他的方式使用开源的Flutter。

能在已经存在的原生APP中使用Flutter吗?

可以,你可以嵌入Flutter视图到你已经存在的Android和iOSAPP中,相关文档正在开发中。

能访问跨平台的服务和API吗,比如传感器和本地存储?

可以,Flutter支持开发者开箱即用来访问一些平台特性的服务和API。然而,为了避免大多数跨平台API出现的”最小公分母”问题,我们不打算支持所有的跨平台服务和API。最后,鼓励开发者使用Flutter的异步消息来创建平台和第三方API综合体。

控件能够继承和自定义吗?

当然可以,Flutter的控件系统被设计为很容易自定义。Flutter并没有为每一个控件提供很多参数,而是提供创作。比如说,RaisedButton 控件并不是普通button控件的子类,而是Material 控件和GestureDetector控件的组合,前者提供视觉设计,后者提供交互设计。如果你想创建一个自定义视觉设计的button控件,可以与实现了你的视觉设计的GestureDetector控件相结合,比如说CupertinoButton控件。视觉设计与交互设计的结合创作赋予开发者最大的控制权,并且还能复用大量代码。我们已经将复杂的控件分解为多个独立的控件(分别实现视觉,交互和手势),你可以任意组合。

能与手机平台默认编程语言交互吗?

可以,能与开发Android的Java代码和开发iOS的 Objective-C或Swift交互,Flutter APP与手机平台通过BasicMessageChannel传递消息。

Flutter中的internationalization (i18n), localization (l10n), and accessibility (a11y)?

基本支持Android和iOS的 accessibility,鼓励开发者使用 intl package(需翻墙)来 internationalization 和localization。

Flutter开发中并行和并发问题?

Flutter支持虚拟机的堆隔离,所以支持并行,隔离后通过异步消息通信。暂不支持共享内存并发解决方案。

Flutter能使用JSON/XML/protobuffers等吗?

当然可以,https://pub.dartlang.org上面有相关库。

Flutter能开发3D (OpenGL) APP吗?

暂不支持

为什么APK 或 IPA包这么大?

一般assets文件夹包括图片,音频文件和字体等占据了apk或IPA主要体积。同时release版本比debug版本要小很多。

为什么build()方法在State对象里而不是在StatefulWidget控件里?

这是为了当子类是StatefulWidget控件的时候给予开发者更多弹性控件,详情请参考

为什么APP右上角有“Slow Mode”条幅?

这是因为使用了调试模式,安装release版本就没有了。

苹果公司会拒绝Flutter APP吗?

目前是允许的,虽然苹果公司的政策每年都会变化,但我们会尽全力保证Flutter APP能发布到苹果公司的App Store。

这篇文章主要回答了一些关于Flutter的常见问题,如果你对Flutter一无所知,那么阅读完这篇文章后对Flutter的各方面算是了解了,Flutter值得花精力学习吗,Flutter的成熟度与稳定性,Flutter的未来趋势怎么样等等这类问题相信你心中有了自己的答案。

作者:zhangxiangliang2 发表于2017/7/22 10:52:21 原文链接
阅读:2 评论:0 查看评论

cordova whitelist白名单

$
0
0

 

介绍

这个插件在Cordova 4.0以后实现了一个白名单政策用于导航到应用程序的webview

 

 

 

 

安装插件

自动安装

 

 

 

 

 

详情

导航白名单Navigation Whitelist

控制URL WebView本身可以导航到。适用于顶级水平导航。

特性Android也适用于非HTTPS)方案iframes

默认情况下,导航只是到 file:// URLs,是允许的。允许到其他URLs,你必须添加<allow-navigation>签到你的config.xml

<!-- Allow links to example.com -->

<allow-navigation href="http://example.com/*" />

 

<!-- Wildcards are allowed for the protocol, as a prefix

     to the host, or as a suffix to the path -->

<allow-navigation href="*://*.example.com/*" />

 

<!-- A wildcard can be used to whitelist the entire network,

     over HTTP and HTTPS.

     *NOT RECOMMENDED* -->

<allow-navigation href="*" />

 

<!-- The above is equivalent to these three declarations -->

<allow-navigation href="http://*/*" />

<allow-navigation href="https://*/*" />

<allow-navigation href="data:*" />

 

 

 

 

 

意图白名单Intent Whitelist

控制URL的应用程序允许要求系统打开。默认情况下,没有外部链接是允许的。

Android上,这等同于发送BROWSEABLE类型的意图。

这个名单不适用于插件,只有超链接和调用window.open()

进入config.xml,添加<allow-intent>标签,这样:

<!-- Allow links to web pages to open in a browser -->

<allow-intent href="http://*/*" />

<allow-intent href="https://*/*" />

 

<!-- Allow links to example.com to open in a browser -->

<allow-intent href="http://example.com/*" />

 

<!-- Wildcards are allowed for the protocol, as a prefix

     to the host, or as a suffix to the path -->

<allow-intent href="*://*.example.com/*" />

 

<!-- Allow SMS links to open messaging app -->

<allow-intent href="sms:*" />

 

<!-- Allow tel: links to open the dialer -->

<allow-intent href="tel:*" />

 

<!-- Allow geo: links to open maps -->

<allow-intent href="geo:*" />

 

<!-- Allow all unrecognized URLs to open installed apps

     *NOT RECOMMENDED* -->

<allow-intent href="*" />

 

 

 

 

 

网络请求白名单Network Request Whitelist

控制网络请求(图片,XHRs,等)是允许(通过cordova本地挂载)。

注意:我们建议您使用一个内容安全政策(见下文),这是更安全。这个名单是历史做不支持CSP

进入config.xml,添加<access>标签,这样: 

<!-- Allow images, xhrs, etc. to google.com -->

<access origin="http://google.com" />

<access origin="https://google.com" />

 

<!-- Access to the subdomain maps.google.com -->

<access origin="http://maps.google.com" />

 

<!-- Access to all the subdomains on google.com -->

<access origin="http://*.google.com" />

 

<!-- Enable requests to content: URLs -->

<access origin="content:///*" />

 

<!-- Don't block any requests -->

<access origin="*" />

 

 

 

没有任何<access>标签,只请求file://URL是允许的然而Cordova应用默认情况下包括<access origin="*">

注:白名单不能阻止网络重定向从内远程网站(如HTTPHTTPS)到非白名单的网站。webviews支持CSP使用CSP规则减重定向到非白名单的网站。CSPContent Security Policy

特性Android默认允许请求https://ssl.gstatic.com/accessibility/javascript/android/,因为这是对讲功能要求。

 

 

 

内容安全策略Content Security Policy

控制网络请求(图片,XHRs,等)是允许(通过WebView直接)。

AndroidiOS,网络请求的白名单(见上图)是不能够过滤所有类型的请求(例如<video>WebSockets没有堵塞)。所以,除了白名单,你应该使用一个Content Security Policy内容安全策略<meta>标签贴在所有页面

Android系统上,支持CSP在系统的webview中启动KitKat(但可在所有版本使用Crosswalk WebView)。

这里有你的一些例子CSP声明到你的HTML网页

 <!-- Good default declaration:

    * gap: is required only on iOS (when using UIWebView) and is needed for JS->native communication

    * https://ssl.gstatic.com is required only on Android and is needed for TalkBack to function properly

    * Disables use of eval() and inline scripts in order to mitigate risk of XSS vulnerabilities. To change this:

        * Enable inline JS: add 'unsafe-inline' to default-src

        * Enable eval(): add 'unsafe-eval' to default-src

-->

<meta http-equiv="Content-Security-Policy" content="default-src 'self' data: gap: https://ssl.gstatic.com; style-src 'self' 'unsafe-inline'; media-src *">

 

<!-- Allow everything but only from the same origin and foo.com -->

<meta http-equiv="Content-Security-Policy" content="default-src 'self' foo.com">

 

<!-- This policy allows everything (eg CSS, AJAX, object, frame, media, etc) except that

    * CSS only from the same origin and inline styles,

    * scripts only from the same origin and inline styles, and eval()

-->

<meta http-equiv="Content-Security-Policy" content="default-src *; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline' 'unsafe-eval'">

 

<!-- Allows XHRs only over HTTPS on the same domain. -->

<meta http-equiv="Content-Security-Policy" content="default-src 'self' https:">

 

<!-- Allow iframe to https://cordova.apache.org/ -->

<meta http-equiv="Content-Security-Policy" content="default-src 'self'; frame-src 'self' https://cordova.apache.org">

 

 

  


作者:michael_ouyang 发表于2017/7/22 11:49:22 原文链接
阅读:301 评论:0 查看评论

Flutter实战一Flutter聊天应用(十四)

$
0
0

优化输入体验

在进行下一步之前,我们先优化一下注册的体验:

  • 正在输入注册信息时,点击屏幕空白部分,清除当前文本输入框的焦点,同时收起键盘。
  • 正在输入注册信息时,直接收起键盘,再点击空白部分,清除当前文本输入框的焦点。
  • 不在用户输入时直接判断并显示错误提示信息,而是在用户输入完成以及点击加入按钮时判断并显示错误提示信息。
  • 在每一个输入框下方都显示帮助信息,提示用户输入什么内容。

首先我们把SignUpState类的_correctUsername_correctUsername变量改为bool类型。并把用户名和密码的判断逻辑合并到_checkInput方法中。

class SignUpState extends State<SignUp> {
  bool _correctPhone = true;
  bool _correctUsername = true;
  bool _correctPassword = true;
  //...
  void _checkInput() {
    if (_phoneController.text.isNotEmpty &&
        (_phoneController.text.trim().length < 7 ||
            _phoneController.text.trim().length > 12)) {
      _correctPhone = false;
    } else {
      _correctPhone = true;
    }
    if (_usernameController.text.isNotEmpty &&
        _usernameController.text.trim().length < 2) {
      _correctUsername = false;
    } else {
      _correctUsername = true;
    }
    if (_passwordController.text.isNotEmpty &&
        _passwordController.text.trim().length < 6) {
      _correctPassword = false;
    } else {
      _correctPassword = true;
    }
    setState(() {});
  }
  //...
}

因为我们将使用手机号作为用户的唯一ID存储在数据库中,所以在上面的代码中,我们增加了一个_correctPhone变量与判断手机号长度的逻辑。

SignUpState类的build里添加helperText属性,它能在文本框下方显示输入帮助信息。使用onSubmitted代替onChanged属性,它会在用户点击键盘上的完成按钮时触发事件,也就是上面合并修改的_checkInput方法。

class SignUpState extends State<SignUp> {
          //...
                      new TextField(
                        controller: _phoneController,
                        keyboardType: TextInputType.phone,
                        decoration: new InputDecoration(
                          helperText: 'Your unique ID.',
                          hintText: 'Phone',
                          errorText: _correctPhone
                              ? null
                              : 'phone length is 7 to 12 bits.',
                          icon: new Icon(
                            Icons.phone,
                          ),
                        ),
                        onSubmitted: (value) {
                          _checkInput();
                        },
                      ),
                      new TextField(
                        controller: _usernameController,
                        decoration: new InputDecoration(
                          helperText: "What's your name?",
                          hintText: 'Username',
                          errorText: _correctUsername
                              ? null
                              : 'Username length is less than 2 bits.',
                          icon: new Icon(
                            Icons.account_circle,
                          ),
                        ),
                        onSubmitted: (value) {
                          _checkInput();
                        },
                      ),
                      new TextField(
                        controller: _passwordController,
                        obscureText: true,
                        keyboardType: TextInputType.number,
                        decoration: new InputDecoration(
                          helperText: 'Your landing password.',
                          hintText: 'Password',
                          errorText: _correctPassword
                              ? null
                              : 'Password length is less than 6 bits.',
                          icon: new Icon(
                            Icons.lock_outline,
                          ),
                        ),
                        onSubmitted: (value) {
                          _checkInput();
                        },
                      ),
          //...
}

然后我们需要在把有背景图片的Container控件包装在GestureDetector控件中,再添加点击事件,清除文本输入框的焦点,然后判断输入内容。

class SignUpState extends State<SignUp> {
  //...
  @override
  Widget build(BuildContext context) {
    return new Scaffold(
        body: new Stack(children: <Widget>[
      new Opacity(
          opacity: 0.3,
          child: new GestureDetector(
              onTap: () {
                FocusScope.of(context).requestFocus(new FocusNode());
                _checkInput();
              },
              child: new Container(
                decoration: new BoxDecoration(
                  image: new DecorationImage(
                    image: new ExactAssetImage('images/sign_up_background.jpg'),
                    fit: BoxFit.cover,
                  ),
                ),
              ))),
      //...
  }
  //...
}

最后我们在用户点击加入按钮提交注册信息之前,先清除文本输入框的焦点并判断输入内容。

class SignUpState extends State<SignUp> {
  //...
  Future _handleSubmitted() async {
    FocusScope.of(context).requestFocus(new FocusNode());
    _checkInput();
    //...
  }
  //...
}

添加等待动画

用户在提交注册请求后,需要等待服务端返回请求结果,这一等待时间会根据当前网络的因素或长或短。所以我们现在要增加一个等待动画,使用CircularProgressIndicator控件实现,其效果是一个不断转动的圆形。在等待期间我们不需要用户有其他操作,因此需要一个新屏幕。

class ShowAwait extends StatefulWidget {
  ShowAwait(this.requestCallback);
  final Future<int> requestCallback;

  @override
  _ShowAwaitState createState() => new _ShowAwaitState();
}

class _ShowAwaitState extends State<ShowAwait> {
  @override
  initState() {
    super.initState();
    new Timer(new Duration(seconds: 2), () {
      widget.requestCallback.then((int onValue) {
        Navigator.of(context).pop(onValue);
      });
    });
  }

  @override
  Widget build(BuildContext context) {
    return new Center(
      child: new CircularProgressIndicator(),
    );
  }
}

上面代码中,在initState方法中有一个Timer类,它能设置定时任务,定时执行一次或者每隔一段时间执行一次,我们在这里设置的是定时2秒执行一次传入requestCallbac方法。同时requestCallbac方法需要返回一个int值,该值用于返回服务端的处理结果。执行完毕后使用Navigator.of(context).pop返回结果并关闭等待屏幕。

提交注册信息

然后回到sign_up.dart文件,我们要开始写服务端的代码,关于如何使用Firebase数据库,可以查看《Flutter进阶—Firebase数据库实例》,我们保存用户信息的数据名称是users,因此需要先连接上Firebase数据库。

class SignUpState extends State<SignUp> {
  //...
  final reference = FirebaseDatabase.instance.reference().child('users');
  //...
}

现在我们修改一下_userLogUp方法,在注册之前要先验证用户注册的手机号码是否已经存在,如果已经被注册,则返回0。确定当前手机号码未被注册之后,才提交用户注册信息到数据库,并返回1

class SignUpState extends State<SignUp> {
  //...
  Future<int> _userLogUp(String username, String password,
      {String email, String phone}) async {
    return await reference
        .child(_phoneController.text)
        .once()
        .then((DataSnapshot onValue) {
      if (onValue.value == null) {
        reference.child(phone).set({
          'name': username,
          'password': password,
          'email': email,
          'phone': phone,
        });
        return 1;
      } else {
        return 0;
      }
    });
  }
  //...
}

再修改一下_handleSubmitted方法,在用户点击注册按钮时弹出ShowAwait创建的等待屏幕,并把上面的_userLogUp方法传给ShowAwait实例。如果返回0,则提示用户,如果返回1,则返回并传递用户的手机号、密码到登陆屏幕。

class SignUpState extends State<SignUp> {
  //...
  void _handleSubmitted() {
    FocusScope.of(context).requestFocus(new FocusNode());
    _checkInput();
    if (_usernameController.text == '' || _passwordController.text == '') {
      showMessage(context, "The registration information is incomplete!");
      return;
    } else if (!_correctUsername || !_correctPassword || !_correctPhone) {
      showMessage(context, "The registration information is incomplete!");
      return;
    }
    showDialog<int>(
        context: context,
        barrierDismissible: false,
        child: new ShowAwait(_userLogUp(
            _usernameController.text, _passwordController.text,
            email: _emailController.text,
            phone: _phoneController.text))).then((int onValue) {
      if (onValue == 0) {
        showMessage(context, "This number has been registered!");
      } else if (onValue == 1) {
        Navigator
            .of(context)
            .pop([_phoneController.text, _passwordController.text]);
      }
    });
  }
  //...
}

这里写图片描述

作者:hekaiyou 发表于2017/7/22 23:53:11 原文链接
阅读:186 评论:0 查看评论

获取网络状态、WiFi 名以及跳转系统邮箱

$
0
0

本次来个大杂烩,把之前的项目的一些零碎但十分实用的知识点做一个总结,供大家参考学习,同时也作为日后项目的参考点。

要想知道当前手机的网络状态可根据状态栏获取,可以区分2G、3G、4G、WIFI,系统的方法,比较快捷,话不多说,上示例代码(代码经过测试,可放心安全使用):

// 获取当前网络状态
- (NSString *)getNetWorkStates{
    UIApplication *app = [UIApplication sharedApplication];
    NSArray *children = [[[app valueForKeyPath:@"statusBar"]valueForKeyPath:@"foregroundView"]subviews];
    NSString *state = [[NSString alloc]init];
    int netType = 0;
    //获取到网络返回码
    for (id child in children) {
        if ([child isKindOfClass:NSClassFromString(@"UIStatusBarDataNetworkItemView")]) {
            //获取到状态栏
            netType = [[child valueForKeyPath:@"dataNetworkType"]intValue];

            switch (netType) {
                case 0:
                    state = @"无网络";
                    //无网模式
                    break;
                case 1:
                    state =  @"2G";
                    break;
                case 2:
                    state =  @"3G";
                    break;
                case 3:
                    state =  @"4G";
                    break;
                case 5:
                {
                    state =  @"wifi";
                    break;
                default:
                    break;
                }
            }
        }
    }
    //根据状态选择
    return state;
}

单单知道当前的网络状态有时候又是不够的,还得知道更加具体的信息,比如,当前的所连接的 WiFi 名称,此时,同时使用系统方法,需要导入系统库:

#import<SystemConfiguration/CaptiveNetwork.h>
#import<SystemConfiguration/SystemConfiguration.h>
#import<CoreFoundation/CoreFoundation.h>
// 获取WiFi名
- (NSString *)getWifiName{

    NSString *wifiName = @"无网络";
    CFArrayRef myArray = CNCopySupportedInterfaces();
    if (myArray != nil) {
        CFDictionaryRef myDict =CNCopyCurrentNetworkInfo(CFArrayGetValueAtIndex(myArray, 0));
        if (myDict != nil) {
            NSDictionary *dict = (NSDictionary*)CFBridgingRelease(myDict);
            wifiName = [dict valueForKey:@"SSID"];
        }
    }
    return wifiName;
}

APP 上线后希望接收到用户的反馈,此时一种比较简单的方式是直接转到系统邮箱,通过邮箱的方式反馈,可能这种方式用户体验不是最佳,但不失为一种办法。通过邮箱反馈有多种方式,本文只讲跳转至系统邮箱的方式。

- (IBAction)feedBackAction:(id)sender {
    //创建可变的地址字符串对象
    NSMutableString *mailUrl = [[NSMutableString alloc] init];
    NSString *recipients = @"XXXXX@163.com";
    [mailUrl appendFormat:@"mailto:%@?", recipients];
    //添加邮件主题
    [mailUrl appendFormat:@"&subject=%@",@"用户反馈"];
    //添加邮件内容
//    [mailUrl appendString:@"&body=<b>Hello</b> World!"];
    //跳转到系统邮件App发送邮件
    NSString *emailPath = [mailUrl stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLFragmentAllowedCharacterSet]];
    [[UIApplication sharedApplication]openURL:[NSURL URLWithString:emailPath] options:@{} completionHandler:nil];
}

需要注意的是上述方法只在真机看到效果,模拟器的功能是有限的。当然,还可以根据需要添加一些其他的邮件信息,此处只讲解方法,不作细致处理。之后的博文会更新继续更新解锁各种方便实用的技巧。

作者:huangfei711 发表于2017/7/23 21:23:31 原文链接
阅读:49 评论:0 查看评论

Keil综合(03)_map文件全解析

$
0
0

相关标题:Keil map文件 内存分布文件


0、写在前面

相信有较大项目开发经验的朋友都曾遇到内存溢出的问题,那么大家都是如何分析这类问题的呢?大家遇到HardFault_Handler 有对map分析过吗?

 

首先讲述一下关于mapMDK-ARM中的配置。其实,在MDK-ARM中,我们可以根据自己的情况(不同配置),在map文件中输出对应(我们需要)的内容。默认情况下,输出所有信息。

 

Project -> Options for Target -> Listing:会看到如下配置界面:


 

看到上图,相信都应该明白map文件大概有哪些内容了吧?


map文件里面内容大致分为五大类(按照map文件分类的顺序):

1.Section Cross References:模块、段(入口)交叉引用;

2.Removing Unused input sections from the image:移除未使用的模块;

3.Image Symbol Table:映射符号表;

4.Memory Map of the image:内存(映射)分布;

5.Image component sizes:存储组成大小。

 

下面章节就针对MDK-ARM详细讲述一下map文件里面的几大内容。

 

Ⅰ、Section Cross References:模块、段(入口)交叉引用


配置中需勾选上:Cross Reference

 

Section Cross References:模块、段(入口)交叉引用,指的是各个源文件生成的模块、段(定义的入口)之间相互引用的关系


比如:

main.o(i.System_Initializes) refers to bsp.o(i.BSP_Initializes) for BSP_Initializes

意思是:

main模块(main.o)中的System_Initializes函数(i.System_Initializes),引用(或者说调用)了bsp模块(bsp.o)中的BSP_Initializes函数。

 

提示:

main.omain.c源文件生成的目标文件模块;

I.System_InitializesSystem_Initializes函数的入口。

 

Ⅱ、Removing Unused input sections from the image:移除未使用的模块


配置中需勾选上:Unuaed Sections Info

 

这一选项很好理解,就是我们工程代码中,没有被调用的模块。

最后还有一个统计信息:

52 unused section(s) (total 2356 bytes) removed from the image.

 

1.总共有52段没有被调用;

2.没有被调用的大小为2356 字节;

 

Ⅲ、Image Symbol Table:映射符号表


配置中需勾选上:Symbols

 

Image Symbol Table:映射符号表,也就是各个段所存储对应地址的表(这一项比较重要)。

 

Symbols分为两大类

1.Local Symbols局部

2.Global Symbols全局

 

内容要点

1.Symbol Name:符号名称

 

2.Value:存储对应的地址;

大家会发现有0x0800xxxx0x2000xxxx这样的地址。

0x0800xxxx指存储在FLASH里面的代码、变量等。

0x2000xxxx指存储在内存RAM的变量Data等。

 

3.Ov Type:符号对应的类型

符号类型大概有几种:NumberSectionThumb CodeData等;

细心的朋友会发现:全局、静态变量等位于0x2000xxxx的内存RAM中。

 

4.Size:存储大小

这个容易理解,我们怀疑内存溢出,可以查看代码存储大小来分析。

 

5.Object(Section):段目标

这里一般指所在模块(所在源文件)。

Ⅳ、Memory Map of the image:内存(映射)分布


配置中需勾选上:Memory Map

 

Memory Map of the image:内存(映射)分布,内容相对较多,也是比较重要的一项

 

Image Entry point : 0x08000131:指映射入口地址。

 

Load Region LR_IROM1 (Base: 0x08000000, Size: 0x000004cc, Max: 0x00080000, ABSOLUTE):

指加载区域位于LR_IROM1开始地址0x08000000,大小有0x000004cc,这块区域最大为0x00080000.

 

执行区域

Execution Region ER_IROM1

Execution Region RW_IRAM1

这个区域,其实就是对应我们目标配置中的区域,如下如:


 

内容要点

1.Base Addr:存储地址

0x0800xxxxFLASH地址和0x2000xxxx内存RAM地址。

 

2.Size:存储大小

 

3.Type:类型

Data:数据类型

Code:代码类型

Zero:未初始化变量类型

PAD:这个类型在map文件中放在这个位置,其实它不能算这里的类型。要翻译的话,只能说的“补充类型”。

ARM处理器是32位的,如果定义一个8位或者16位变量就会剩余一部分,这里就是指的“补充”的那部分,会发现后面的其他几个选项都没有对应的值。

 

4.Attr:属性

RO:存储与ROM中的段

RW:存储与RAM中的段

 

5.Section Name:段名

这里也可以说为入口分类名,与第一章节Section Cross References”指的模块、段一样。

大概包含:RESET.ARM.texti.data.bssHEAPSTACK等。

 

6.Object:目标

 

Ⅴ、Image component sizes:存储组成大小


配置中需勾选上:Size Info

 

Image component sizes:存储组成大小,其实主要就是对模块进行汇总存储大小信息。

 

这一章节内容相信大家都能理解,我们编译工程后,在编译窗口一般会看到类似如下一段信息:

Program Size: Code=908 RO-data=320 RW-data=0 ZI-data=1024

Code:指代码的大小;

Ro-data:指除了内联数据(inline data)之外的常量数据;

RW-data:指可读写(RW)、已初始化的变量数据;

ZI-data:指未初始化(ZI)的变量数据;

 

CodeRo-data:位于FLASH中;

RW-dataZI-data:位于RAM中;

提醒:RW-data已初始化的数据会存储在Flash中,上电会从FLASH搬移至RAM中。

 

关系如下:

RO  Size = Code + RO Data

RW  Size = RW Data + ZI Data

ROM Size = Code + RO Data + RW Data

 

更多具体内容可以参看文章:Keil编译存储相关说明及拓展

 

上面信息是比较全面的汇总,如果不想看那些模块的详细,只看汇总统计的信息可以在配置中只勾选Totals Info”,对比信息:


 

Ⅵ、最后

微信搜索EmbeddDeveloper” 或者扫描下面二维码、关注,在我的底部菜单查看更多精彩内容!

 



为方便大家阅读,本文内容已经整理成
PDF文件:

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


作者:strongerHuang

版权所有,未经允许,禁止用于其它商业用途!!!


转载请注明出处:

http://blog.csdn.net/ybhuangfugui/article/details/75948282


作者:ybhuangfugui 发表于2017/7/23 21:45:37 原文链接
阅读:100 评论:0 查看评论

编程路上,送给处于迷茫中的你和自己

$
0
0

在迷茫中入行

从离校算起,踏入社会都快五年了,在最初的浑浑噩噩中度过了半年,终于在2013年上半年,我开启了编程这条不归路,或许你不会相信,我刚入行的时候,九九乘法表都不会写,甚至一个处了多年的哥们都嘲笑我,一个连java都不会的人,居然跑去做Android,现在想起,内心还是一阵心酸,或许也是那句话,才让我更加坚定的走下去,跪着也要走完自己选择的路。

刚出来找工作的时候,那真的就是我的辛酸史,我是住在一个学校的宿舍,睁开眼,就是各种海投,能投的网站我都投了,包括一些地方的招聘网站或者生活类网站,只要有稍微合适点的就会去关注,当时也够惨的,真的如上面所说,九九乘法表都不会写,没有特长,那只能各种技术都去投,我记得当时投的是C、javaEE、HTML+CSS、C#,应该是这四种吧,具体已经记不清了,投完简历后就是无脑的打游戏,想通过这种方式迷幻自己、麻痹自己、逃避自己,到了很饿的时候就去吃个饭,然后继续打游戏,这段时间,我去过南京、张家港、昆山、江阴面试,除了游戏和投简历,一直在路上。。。

我的第一份工作挺另类的,当时是在江阴的一个小镇上,那边很难招到人,就算偶尔有去面试的,也很少有人愿意留在那里,当时招聘信息写的是要求有C语言基础,就抱着碰碰运气的心态去面试了,并没有抱有什么希望,让我吃惊的是,面试过程中,其实什么技术性的都没问,就是随便聊聊,当时给我的感觉就是,你技术怎样没关系,会点基础就行。当时还是没底啊,我真的基础都不会,更何况来了做Android,天哪!

第一个月里,其实就是一个边上班边自学状态,一个月考核,过了就录取,过不了就自己走人,本着一定要留下来的心态,那段时间早上起来就看书看视频,太多看不懂,自信心备受打击,那个时候甚至连构造函数都不懂,更不知道干嘛的,看到有个同事写这个传context,我的类里面,除了activity外,其它的类都会写一个构造函数传context,不管用不用到。那时候住着200一个月的房子,除了电灯,别的什么用电的都没有提供,就在这样的环境下度过了第一个月,最后在这家公司留下来了。

说实话,在那个时候,我对编程真的没什么兴趣,被录取了后,我又开始无脑的打游戏,下班就开始玩游戏,当时工资低的可怜,仅仅够自己生活费的,公司也想给我省一笔开支,就建议我和一个同事合租稍微大一点的房子,我们就换成了400一间的,那时候已经非常满足了,觉得这个已经挺好了。那时候我们都还没毕业,还要回学校忙着毕业设计和答辩,很多时候,那房间就是住一个人。

和我住在一个房间的那个同事,他一直都是很认真的学习和工作,进步很快,晚上的时候,经常看他学习,看着mars和sundy的视频,近朱者赤,慢慢的我也会学着一点,时间不长,他就跳槽走了,听说他拿了3500一个月,当时好羡慕啊,经常在幻想,我什么时候可以拿这么多工资,^_^,他走了后,我并没有把更多时间放在学习上,反而用更多时间打游戏了,一直到那年的十一月份,身边发生一个悲剧,一件事改变了我的一生,那一次好像看透了很多,当时就想着给自己两条路,要么好好学编程,要么好好准备去考研,一天的犹豫后,我选择了前者。

无脑学习期

从那以后,我真的脱胎换骨了,每天都是打鸡血一样的去工作,下班后,匆匆吃完晚饭就去看书、看视频、写代码,一直到自己很累了就睡觉,第二天睁开眼就继续看书、看视频、写代码,好多次头不舒服,晚上八九点就睡了,早上两三点醒了看书,知道自己的水平,我把更多的时间放在了java基础,完整地看了三遍,很用心的去学着,在每天睡眠六小时左右的情况下,我坚持了接近一年,一个脱胎换骨的一年。

很快就到了春节,节前我打了辞职,一番颠沛流离后,我去了南京,在一家外包公司入职,不得不承认,工作不久的人来说,在外包公司真的很锻炼人,那时候对便Java基础有了一定的了解,对网络编程了解不多,买了一本《深入理解Android网络编程》,这应该是我毕业后,完整看完的第一本专业书,在公司工作一段时间后便开始无脑加班,经常一天工作十四到十六小时,下班后在宿舍还要继续搞起,当时没钱租房子,借宿在同学那边,都是程序员,加班都很频繁,也不觉得什么,都习以为常了,但有一点比较恶心,他睡了我还在敲代码,他醒了,我已经到公司敲代码了,公司的行为令人发指,无脑压榨劳动力,就这样环境下一直坚持到了2014年十月份,当时决定去上海寻找发展。

稳步成长期

从我无脑学习到后来来了上海的一年时间里,从工资角度来说,这可能是我工作以来最大的骄傲,我工资翻了接近十倍,这期间,我从一个对编程反感到爱上编程了,从什么都不懂转变成能写点东西的程序员了,到上海后,我遇到了一位贵人,我上家公司的领导,我是一个很容易迷失方向的人,每次当我迷失自己的时候,我总会找他谈话,他也把我当自家小弟,经常和我分享一些他的经历来开导我,平时也会推荐我应该朝哪方向发展,在那家公司时间不长,我便开始接触一些框架上的东西,一点点接触架构,在那个时候,我对泛型还是很模糊,更不懂什么叫做面向接口、面向泛型,这些也都是一点点在那个时候建立起来的,在团队意识方面,那时候公司招人,领导会安排我去第一轮面试,面试中遇到形形色色的人,确实有技术性很强的,我就会对领导推荐,他聊了后觉得不合适,给我灌输团队意识,我们是一个团队,即使那个人技术再好,融入不了我们团队也不行,那时候我才意识到,我是有组织的人,^_^。

在上家公司工作的两年多,搭建过三个框架,在工作中一点点完善和改进,让我在这方面有了一定的经验,给我以后的跳槽增加了好多信心。

总结与推荐

之前一篇勿忘初心,继续coding中已经对三年以内的朋友做了一些推荐,这次再做一下补充,如果此刻你已经学完设计模式了,学完《effective Java》了,不妨看一些更深层的书,比如《深入理解java虚拟机》、《Android设计与实现》、《Java并发编程实战》、《Android软件安全与逆向分析》、《Android系统源代码情景分析》等,可以更系统的了解java和Android,对于没学完设计模式和《effective Java》的朋友,个人还是很推荐继续学完,这些书会让人更加聪明的写代码,不再那么无脑的只为实现功能而开发,对于工作三年以上的开发者来说,这种无脑开发是没任何意义。

现在kotlin被Google纳入正室,已经峰王封后,但短期内想取代java,还真没那么简单,再说了,编程重要的是思想,我在去年就学习过kotlin,如果真的转了,相信大家可以在很短时间内就可以转变。

有时候心累,这些都是难免的,我和身边的同事,都经常遇到,当你心累的时候,如果觉得在这家公司还有留下去的比较,那就不如出去透透气,或者来个说走就走的旅游,放松一下自己,或者做一些自己喜欢做的事,我现在给自己减压,更多时候就是通过旅游和打游戏,工作不是太忙,就出去旅游了,工作上任务太多,只能去打打游戏调节下自己了,有时候也会用另一种奇葩方式,给自己做一顿美食,O(∩_∩)O哈哈~,结束后就回到自己的岗位上继续工作。

最后还有一点想说的,有时候想通过一门语言的深入来更好地提高自己,其实很有时候并没有任何卵用,更多时候,我们还需要更多的接触其它语言来提高自己,因为编程思想才是最值钱的,在Android开发过程中,能接触到的编程思想毕竟有限,何尝不去了解下其它技术,百利而无一害,何乐而不为?

微信扫我,^_^

作者:pangpang123654 发表于2017/7/22 13:01:38 原文链接
阅读:729 评论:4 查看评论

Android版本的"Wannacry"文件加密病毒样本分析(附带锁机)

$
0
0

一、前言

之前一个Wannacry病毒样本在PC端肆意了很久,就是RSA加密文件,勒索钱财。不给钱就删除。但是现在移动设备如此之多,就有一些不法分子想把这个病毒扩散到移动设备了,这几天一个哥们给了一个病毒样本,就抽空看了一下,下面就来分析一下这个病毒样本程序。


二、病毒样本分析

首先国际惯例,这类的病毒都是用一些特殊的app名称吸引诱导用户下载,这里是一个叫做:魅影WIFI,下载安装之后界面如下:


我们点击免费激活,出现授权界面:


需要设备管理器,这时候应该猜到了,他是想修改锁机密码,我们就授权,然后分析程序找到重置的密码就好了,授权之后,就被锁屏了,解锁屏幕会发现:


病毒作者是真够狠的,尽然有自己弄了一个浮窗锁机,这时候我们不得不看代码了,找到这个密码了,当然这个浮窗类型的锁机其实很容易解决的。因为他主要是借助WindowManager搞得,所以这时候可以连接adb使用命令可以直接干掉这个进程就好了:am force-stop pkgname;我们可以用dumpsys命令获取其包名:


然后在强制停止app即可:


停止了之后,发现就是解锁屏幕了,当然密码已经被篡改了,所以的分析软件了,当然如果设备root了,可以直接删除/data/system/password.key文件即可。当然这些说的都是因为本身就是个开发者,而对于小白用户,肯定这么做不合适的。我们需要分析app,拿到这两个地方锁机密码。告诉被坑的小白用户,解救他们。下面就来分析app了:


三、破解获取解锁密码

第一、获取锁屏密码

因为我们知道现在大多数锁机软件都是利用设备管理器来修改设备密码的,所以想看他的密码也很简单。直接用jadx打开软件,然后全局搜索类:DeviceAdminReceiver


然后看看这个类里面修改代码部分逻辑:


看到了,这里直接将手机的锁机密码修改成9815了。这个就是我们待会需要解锁的密码。


第二、浮窗锁机密码

这个比上面密码有点麻烦,因为是自己定义的浮窗锁机,所以直接看到锁机界面文字,去Jadx全局搜索即可,这里全局搜索字符串"输入密码":


进入到这个类即可:


这里是定义了一个Service然后用WindowManager实现,然后把权限设置为最高,用户就无法进行任何操作了。因为最终的密码输入都是在这个EditText,取密码作比较也要用到这个文本框,就看这里的this.ed在哪里取值:


这里直接比对密码,如果密码正确了,就直接干掉服务,浮窗锁机就没有了。所以这里最重要的是decrypt方法了,他是从SP中拿到key是passw的密文进行解密比对。其实到这里我们可以借助Xposed工具直接hook这个decrypt方法就能很轻易的拿到这个密码了:


然后直接运行就可以获取对应的密码了:


不过这里还想继续分析这个DES的加密逻辑,因为本身就是要学习的。所以我们继续手动分析这个加密算法,我们看看des变量定义:


这里会看到有一个du和du2变量,这个不要在意,可能是代码混淆原因,其实就是一个值。看初始化传入一个字符串值,可能是DES加密的key值,然后就立马对一个密文进行解密,之后的内容在作为新的DES的密钥值。相当于这里二次获取密钥了。看DU代码实现:


传入的字符串就是作为key进行加解密操作的。那么下面我们就需要手动写一个简单的DES加解密算法了,这个网上很多代码了,直接拷贝一个:


第一次初始化密钥是字符串:"flower",然后直接解密内容:"c29fe56fa59ab0db"


然后直接解密,获取第二次要用到的密钥"xxx",然后在初始化以下key:


然后,我们需要去程序的xml中找到加密内容,然后拷贝出来进行解密:


把这个串拷贝出来进行解密:


这个就是解锁密码了,而我们在回过头看看怎么把这个加密串存到xml中的:


这里他生成密码尽然是采用随机值,然后在加上123456,最后在DES加密保存到xml中,同时也会把随机数保存到xml中的,从上面可以看到值是:


而这个值加上123456也就是密码了:92944926+12346=93068382,这个也就是上面我们解密之后的密码,这就对上了。所以这个锁机,如果加他QQ索要密码,其实他是需要让小白用户做点东西,因为这个密码是随机的,不是固定值,必须让小白用户把程序的xml文件给他。不然它也是不知道解锁密码的。实在坑爹。


四、破解进行文件解密

而上面的锁机并不是本文的重点,本文的重点其实是文件加密,在回他的app中:


然后点击注册使用:


点击注册软件,会等一会,其实这里他在做一些坏事,后面会分析代码干什么坏事:


这就是我们熟悉的Wannacry病毒界面了,需要付钱才能对文件解密了,而这时候悲剧的是我们的设备SD卡中的文件已经被全部加密了:


那么这时候就很恶心了,文件全部被加密,最蛋疼的是,你打开自己的SD卡可能都打不开了,这个是因为他做了一件更恶心的事就是无限制的新建一些文件和文件夹到SD中。这样其实手机已经废了。因为SD卡的文件非常多。看到上面的截图可以发现,每个文件都是0字节,然后文件对应一个空文件夹。下面来分析他的代码:


然后进入这个类看看,在他的onCreate方法中看到几个方法:


继续追踪发现:


有一个可怕的方法就是爆炸boom了:


在这里会创造出很多空文件和空文件夹,这样你的SD卡到最后几乎打不开了。手机也就废了。不过到这里我们貌似还没有介绍文件加密的内容,其实文件加密在程序启动的时候就做了,就是MainActivity中:


然后就开始跟踪这个方法了:


这里会过滤他自己创建出来的空文件和空文件夹不进行加密的,继续看代码:


这里通过方法的最后一个参数来判断是加密文件还是解密文件。加解密方法参数都是类似的,第一个参数是AES加解密的密钥。所以到这里我们大致清楚了,这个病毒是用AES对文件进行加密的。而密钥他又用AES加密,这时候的加密内容的key和内容是:


所以分析到这里,我们就可以开始进行手动解密文件了。这里当然有很多种方法:

第一种方法:借助Xposed工具直接hook他的FormetFileSize方法,因为我们在上面分析知道,这个方法的最后一个参数标志是解密文件还是加密文件,我们拦截这个方法之后,修改这个参数状态为false表示就是解密文件了:



第二种方法:把他解密功能代码拷贝出来,自己写一个解密程序,这个比较适合给中招的小白使用了:


因为他的代码这几个类都比较独立,所以直接拷贝出来不会有太多的错误,而有些错误就是变量定义重复自己手动改一下即可:


下面就开始运行这个程序即可,这里为了操作不浪费时间,就把SD卡情况了,然后写入三个简单的文件,让他加密:


这时候会发现,他加密之后的文件名就是原始文件名凭借一个串而已,这里忽略乱码哈哈。因为只有这样,他在解密之后才能获取到原始文件,从他代码中也可以看到这点:


然后运行我们解密程序,看到日志:


到这里,我们就成功的进行解密了。


五、病毒分析总结

到这里我们就分析完了这个病毒,总得来说病毒作者心非常狠,锁机采用两套机制,而且浮窗锁机尽然还用随机密码坑人。最恶心的是他的文件加密,尽然在SD中创建了那么多的空文件和空文件夹,让设备无法使用了。打开SD卡也是失败的。罪恶中的罪恶。最后他用的是AES算法对文件进行加解密操作。而这里他没有采用RSA加密的原因其实很简单,如果用RSA加密的话,设备真的要报废了。因为RSA加密算法非常耗时吃内存。手机会扛不住的。他也不敢用RSA加密了,只好用AES了,但是AES加密只要知道了密钥其实没多大问题对于解密来说。这里我们解密可以用两种方式一种是Xposed框架进行hook,一种是自己把它的界面代码拷贝出来自己写一个解密程序。当然第二种是最优的,因为对于那些中招的小白用户他们是没有root的,也是没有安装Xposed框架的,只有写一个解密程序给他安装进行解密就好了。不过有个很大的问题,就是因为病毒还创建了很多空文件和空文件夹,导致操作的时候,我们需要区别对待。也要做一次过滤,这些病毒自己生成的文件和文件夹就不要做解密了,不然会无限制死机了。


从这个病毒中我们可以了解到关于锁机策略机制,对于设备管理器的锁机直接找到DeviceAdminReceiver这个类就好了,或者直接看xml中的定义:


快速找到修改密码的地方,找到锁机密码即可。而对于那种浮窗锁机。因为都是采用Service和WindowManager来实现逻辑的。所以直接使用am命令强制停止程序运行即可。


特别说明:

关于病毒样本这里不能给出,非常抱歉,我知道你们肯定想说:"留毒不留种,小心被人捅",但是四哥不想被请去喝茶,所以你们要骂要捅随意吧。而没有病毒样本,其实都是扯淡。只看分析不操作就是浪费时间。如果真的真的想要样本私信我。方式加入我的小密圈。留言四哥的真实姓名即可。


六、总结

从这个病毒来看,其实即使你付费了,他帮你解密,也是很恶心的,因为他创建了那么多文件,删除需要时间,解密还消耗机器性能,这就是赤裸裸的迫害。所以遇到这种病毒千万不要给钱,可以自己解密,或者刷机。不要想那么多。手机解密这么多文件会死机了,你的手机会扛不住爆炸的。干脆备份文件,刷机得了。还有最重要的一点就是千万不要去下载来历不明的app,不要有歪想法,好好用手机。去正规的应用市场下载应用是最保险的。


更多内容:点击这里

关注微信公众号,最新技术干货实时推送

编码美丽技术圈
微信扫一扫进入我的"技术圈"世界

扫一扫加小编微信
添加时请注明:“编码美丽”非常感谢!


作者:jiangwei0910410003 发表于2017/7/24 8:42:38 原文链接
阅读:197 评论:1 查看评论

Android源码解析四大组件系列(六)---广播的处理过程

$
0
0

这篇文章紧接着上篇分析广播的发送过程,发送广播都是调用ContextImpl的接口去实现的,总共有二十多个,最终都是调用到AMS的broadcastIntent。主要分成下面九小节来说明。
1、设置Flag
2、检查BroadcastOptions
3、当前是否有权力发出广播
4、处理系统相关广播
5、处理粘性广播
6、registeredReceivers和receivers查询
7、处理并行广播
8、整理两个receiver列表
9、处理串行广播

1、上层调用sendBroadcast发送广播

    @Override
    public void sendBroadcast(Intent intent) {
        warnIfCallingFromSystemProcess();
        String resolvedType = intent.resolveTypeIfNeeded(getContentResolver());
        try {
            // 准备离开应用程序进程,进入SysfangfatemServer进程  
            intent.prepareToLeaveProcess(this);
            ActivityManagerNative.getDefault().broadcastIntent(
                    mMainThread.getApplicationThread(), intent, resolvedType, null,
                    Activity.RESULT_OK, null, null, null, AppOpsManager.OP_NONE, null, false, false,
                    getUserId());
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

省去跨进程的过程,进入AMS的broadcastIntent方法


  public final int broadcastIntent(IApplicationThread caller,
            Intent intent, String resolvedType, IIntentReceiver resultTo,
            int resultCode, String resultData, Bundle resultExtras,
            String[] requiredPermissions, int appOp, Bundle bOptions,
            boolean serialized, boolean sticky, int userId) {
        enforceNotIsolatedCaller("broadcastIntent");
        synchronized(this) {
            intent = verifyBroadcastLocked(intent);
            //从mLruProcesses列表中查询到进程
            final ProcessRecord callerApp = getRecordForAppLocked(caller);
            final int callingPid = Binder.getCallingPid();
            final int callingUid = Binder.getCallingUid();
            final long origId = Binder.clearCallingIdentity();
            int res = broadcastIntentLocked(callerApp,
                    callerApp != null ? callerApp.info.packageName : null,
                    intent, resolvedType, resultTo, resultCode, resultData, resultExtras,
                    requiredPermissions, appOp, bOptions, serialized, sticky,
                    callingPid, callingUid, userId);
            Binder.restoreCallingIdentity(origId);
            return res;
        }
    }

2、broadcastIntentLocked中处理广播

broadcastIntentLocked的方法接近600行代码,需要分段阅读,才比较清楚。

    final int broadcastIntentLocked(ProcessRecord callerApp,
            String callerPackage, Intent intent, String resolvedType,
            IIntentReceiver resultTo, int resultCode, String resultData,
            Bundle resultExtras, String[] requiredPermissions, int appOp, Bundle bOptions,
            boolean ordered, boolean sticky, int callingPid, int callingUid, int userId) {

        //设置Flag
        //检查BroadcastOptions
        //当前是否有权力发出广播
        //处理系统相关广播
        //处理粘性广播
        //registeredReceivers和receivers查询
        // 处理并行广播
        //整理两个receiver列表
        // 处理串行广播

}
2.1、设置Flag
         intent = new Intent(intent);

        // 设置这个flag后,广播不会发送给已经停止的应用,看来系统默认是不让我们的广播发送给已经停止的应用的
        intent.addFlags(Intent.FLAG_EXCLUDE_STOPPED_PACKAGES);

        //如果AMS还没有启动好,不允许启动一个新的进程
        if (!mProcessesReady && (intent.getFlags()&Intent.FLAG_RECEIVER_BOOT_UPGRADE) == 0) {
            //这个flag表示只有动态注册的广播接收者能收到广播,如果你错误的设置了这个标记,广播又是静态注册的,那么就收不到广播
            intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
        }

      // 当不是USER_ALL广播且当前用户不是运行状态,除非是系统升级广播或者关机广播,否则直接返回
    if (userId != UserHandle.USER_ALL && !mUserController.isUserRunningLocked(userId, 0)) {
            if ((callingUid != Process.SYSTEM_UID
                    || (intent.getFlags() & Intent.FLAG_RECEIVER_BOOT_UPGRADE) == 0)
                    && !Intent.ACTION_SHUTDOWN.equals(intent.getAction())) {
                Slog.w(TAG, "Skipping broadcast of " + intent
                        + ": user " + userId + " is stopped");
                return ActivityManager.BROADCAST_FAILED_USER_STOPPED;
            }
        }

Intent中有两个FLAG,FLAG_INCLUDE_STOPPED_PACKAGES和FLAG_EXCLUDE_STOPPED_PACKAGES,表示intent是否要激活“处于停止状态的”应用,如果确定要激活“处于停止状态的”应用,那么Intent add FLAG_INCLUDE_STOPPED_PACKAGES就行了。
“`
/**
* If set, this intent will not match any components in packages that
* are currently stopped. If this is not set, then the default behavior
* is to include such applications in the result.
*/
public static final int FLAG_EXCLUDE_STOPPED_PACKAGES = 0x00000010;
/**
* If set, this intent will always match any components in packages that
* are currently stopped. This is the default behavior when
* {@link #FLAG_EXCLUDE_STOPPED_PACKAGES} is not set. If both of these
* flags are set, this one wins (it allows overriding of exclude for
* places where the framework may automatically set the exclude flag).
*/
public static final int FLAG_INCLUDE_STOPPED_PACKAGES = 0x00000020;


#####2.2、检查BroadcastOptions

BroadcastOptions brOptions = null;
if (bOptions != null) {
brOptions = new BroadcastOptions(bOptions);
if (brOptions.getTemporaryAppWhitelistDuration() > 0) {
// See if the caller is allowed to do this. Note we are checking against
// the actual real caller (not whoever provided the operation as say a
// PendingIntent), because that who is actually supplied the arguments.
if (checkComponentPermission(
android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST,
Binder.getCallingPid(), Binder.getCallingUid(), -1, true)
!= PackageManager.PERMISSION_GRANTED) {
String msg = “Permission Denial: ” + intent.getAction()
+ ” broadcast from ” + callerPackage + ” (pid=” + callingPid
+ “, uid=” + callingUid + “)”
+ ” requires ”
+ android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST;
Slog.w(TAG, msg);
throw new SecurityException(msg);
}
}
}


#####2.3、当前是否有权力发出广播
   final String action = intent.getAction();
    final boolean isProtectedBroadcast;
    try {
      //isProtectedBroadcast为true则代表该广播在Framework/base/core/res/AndroidManifest.xml中有声明为保护广播,这样的广播只能由系统发出
        isProtectedBroadcast = AppGlobals.getPackageManager().isProtectedBroadcast(action);
    } catch (RemoteException e) {
        Slog.w(TAG, "Remote exception", e);
        return ActivityManager.BROADCAST_SUCCESS;
    }

    final boolean isCallerSystem;
    switch (UserHandle.getAppId(callingUid)) {
        case Process.ROOT_UID:
        case Process.SYSTEM_UID:
        case Process.PHONE_UID:
        case Process.BLUETOOTH_UID:
        case Process.NFC_UID:
             //以上进程都有权限发送广播
            isCallerSystem = true;
            break;
        default:
            isCallerSystem = (callerApp != null) && callerApp.persistent;
            break;
    }

    // First line security check before anything else: stop non-system apps from
    // sending protected broadcasts.
    if (!isCallerSystem) {
        if (isProtectedBroadcast) {
            String msg = "Permission Denial: not allowed to send broadcast "
                    + action + " from pid="
                    + callingPid + ", uid=" + callingUid;
            Slog.w(TAG, msg);
            throw new SecurityException(msg);

        } else if (AppWidgetManager.ACTION_APPWIDGET_CONFIGURE.equals(action)
                || AppWidgetManager.ACTION_APPWIDGET_UPDATE.equals(action)) {
            // Special case for compatibility: we don't want apps to send this,
            // but historically it has not been protected and apps may be using it
            // to poke their own app widget.  So, instead of making it protected,
            // just limit it to the caller.
            if (callerPackage == null) {
                String msg = "Permission Denial: not allowed to send broadcast "
                        + action + " from unknown caller.";
                Slog.w(TAG, msg);
                throw new SecurityException(msg);
            } else if (intent.getComponent() != null) {
                // They are good enough to send to an explicit component...  verify
                // it is being sent to the calling app.
                if (!intent.getComponent().getPackageName().equals(
                        callerPackage)) {
                    String msg = "Permission Denial: not allowed to send broadcast "
                            + action + " to "
                            + intent.getComponent().getPackageName() + " from "
                            + callerPackage;
                    Slog.w(TAG, msg);
                    throw new SecurityException(msg);
                }
            } else {
                // Limit broadcast to their own package.
                intent.setPackage(callerPackage);
            }
        }
    }
保护性广播是什么呢,frameworks/base/core/res/AndroidManifest.xml文件中可以看到定义,这些定义的广播都是保护广播,只能由系统发送,如果有不具有系统权限的应用试图发送系统中的“保护性广播”,那么到AMS的broadcastIntentLocked()处就会被拦住,AMS会抛出异常,提示"Permission Denial: not allowed to send broadcast"


#####2.4、处理系统相关广播
if (action != null) {
        switch (action) {
            case Intent.ACTION_UID_REMOVED://uid移除
            case Intent.ACTION_PACKAGE_REMOVED://package移除
            case Intent.ACTION_PACKAGE_CHANGED://package改变
            case Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE:  app正在移动到SD卡中,发出的广播
            case Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE: app完成移动到SD的操作,发出的广播  

    case Intent.ACTION_PACKAGES_SUSPENDED:

    case Intent.ACTION_PACKAGES_UNSUSPENDED:

            case Intent.ACTION_PACKAGE_REPLACED://替换一个现有的安装包时发出的广播(不管现在安装的APP比之前的新还是旧,都会发出此广播)

            case Intent.ACTION_PACKAGE_ADDED: //增加package

            case Intent.ACTION_PACKAGE_DATA_CLEARED:

            case Intent.ACTION_TIMEZONE_CHANGED://时区改变

            case Intent.ACTION_TIME_CHANGED://时间改变

            case Intent.ACTION_CLEAR_DNS_CACHE://DNS缓存清空

            case Proxy.PROXY_CHANGE_ACTION://网络代理改变

            case android.hardware.Camera.ACTION_NEW_PICTURE:

            case android.hardware.Camera.ACTION_NEW_VIDEO:

        }
    }
还有更多[系统广播](http://uiuno.com/2017/05/07/android%E5%B8%B8%E7%94%A8%E7%B3%BB%E7%BB%9F%E5%B9%BF%E6%92%AD/)

#####2.5、处理粘性广播

if (sticky) {
//需要android.Manifest.permission.BROADCAST_STICKY才能发送粘性广播
if (checkPermission(android.Manifest.permission.BROADCAST_STICKY,
callingPid, callingUid)
!= PackageManager.PERMISSION_GRANTED) {
String msg = “Permission Denial: broadcastIntent() requesting a sticky broadcast from pid=”
+ callingPid + “, uid=” + callingUid
+ ” requires ” + android.Manifest.permission.BROADCAST_STICKY;
Slog.w(TAG, msg);
throw new SecurityException(msg);
}
//发送粘性广播不能强制添加别的权限
if (requiredPermissions != null && requiredPermissions.length > 0) {
Slog.w(TAG, “Can’t broadcast sticky intent ” + intent
+ ” and enforce permissions ” + Arrays.toString(requiredPermissions));
return ActivityManager.BROADCAST_STICKY_CANT_HAVE_PERMISSION;
}
//粘性广播也不能指定特定的组件名称
if (intent.getComponent() != null) {
throw new SecurityException(
“Sticky broadcasts can’t target a specific component”);
}

        if (userId != UserHandle.USER_ALL) {
         //根据广播类型,取出stickies
            ArrayMap<String, ArrayList<Intent>> stickies = mStickyBroadcasts.get(
                    UserHandle.USER_ALL);
            if (stickies != null) {
              //广播是使用Intent描述的,用action取出广播列表
                ArrayList<Intent> list = stickies.get(intent.getAction());
                if (list != null) {
                    int N = list.size();
                    int i;
                    for (i=0; i<N; i++) {
                      //粘性广播发送后是会保存下来的,故如果已经存在则不需要重新发送  ,
                      // filterEquals函数会比较两个intent的action、data、type、class以及categories等信息,
                        if (intent.filterEquals(list.get(i))) {
                            throw new IllegalArgumentException(
                                    "Sticky broadcast " + intent + " for user "
                                    + userId + " conflicts with existing global broadcast");
                        }
                    }
                }
            }
        }
        ArrayMap<String, ArrayList<Intent>> stickies = mStickyBroadcasts.get(userId);
        if (stickies == null) {
            stickies = new ArrayMap<>();
            mStickyBroadcasts.put(userId, stickies);
        }
        ArrayList<Intent> list = stickies.get(intent.getAction());
        if (list == null) {
            list = new ArrayList<>();
            stickies.put(intent.getAction(), list);
        }
        final int stickiesCount = list.size();
        int i;
        for (i = 0; i < stickiesCount; i++) {
            if (intent.filterEquals(list.get(i))) {
                // 新发送的intent在ArrayList中已经有个“相等的”旧intent时,则会用新的替掉旧的
                list.set(i, new Intent(intent));
                break;
            }
        }
        if (i >= stickiesCount) {
            list.add(new Intent(intent));
        }
    }
sticky为true,表示是粘性广播,发送粘性广播,一定要有android.Manifest.permission.BROADCAST_STICKY权限,没有的话就抛出SecurityException,Permission Denial: broadcastIntent() requesting a sticky broadcast...。在AMS中,所有相同的粘性广播都被保存在一个List中,这些List最终被保存在AMS成员变量mStickyBroadcasts中, mStickyBroadcasts的定义是这样的: final HashMap<String, ArrayList<Intent>> mStickyBroadcasts =  new HashMap<String, ArrayList<Intent>>();注意粘性广播是在注册的时候加入到广播队列并且处理的,请移步[Android源码解析---广播的注册过程](http://www.jianshu.com/p/ca02cecc0d1d)


#####2.6、registeredReceivers和receivers查询

为了合理处理“串行广播”和“并行广播”,broadcastIntentLocked()方法中搞出了两个list,一个是receivers,另一个是registeredReceivers,registeredReceivers是动态广播接收器列表 ,receivers是静态广播接收器列表 。
    List receivers = null;
    List<BroadcastFilter> registeredReceivers = null;
    // Need to resolve the intent to interested receivers...
    if ((intent.getFlags()&Intent.FLAG_RECEIVER_REGISTERED_ONLY)
             == 0) {
    //collectReceiverComponents内部调用包管理器的queryIntentReceivers()接口,查询出和intent匹配的所有静态receivers,此时所返回的查询结果本身已经排好序了,因此,该返回值被直接赋值给了receivers变量
        receivers = collectReceiverComponents(intent, resolvedType, callingUid, users);
    }
    if (intent.getComponent() == null) {
        if (userId == UserHandle.USER_ALL && callingUid == Process.SHELL_UID) {
            // Query one target user at a time, excluding shell-restricted users
            for (int i = 0; i < users.length; i++) {
                if (mUserController.hasUserRestriction(
                        UserManager.DISALLOW_DEBUGGING_FEATURES, users[i])) {
                    continue;
                }
              //此时返回的registeredReceivers中的子项是没有经过排序的,在后面queue.scheduleBroadcastsLocked()会被处理掉
                List<BroadcastFilter> registeredReceiversForUser =
                        mReceiverResolver.queryIntent(intent,
                                resolvedType, false, users[i]);
                if (registeredReceivers == null) {
                    registeredReceivers = registeredReceiversForUser;
                } else if (registeredReceiversForUser != null) {
                    registeredReceivers.addAll(registeredReceiversForUser);
                }
            }
        } else {
            registeredReceivers = mReceiverResolver.queryIntent(intent,
                    resolvedType, false, userId);
        }
    }
#####2.7 处理并行广播
上面已经获取了并行广播和串行广播,现在现将并行广播给处理掉
    final boolean replacePending =
            (intent.getFlags()&Intent.FLAG_RECEIVER_REPLACE_PENDING) != 0;

    if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Enqueing broadcast: " + intent.getAction()
            + " replacePending=" + replacePending);
    //并行广播列表大小
    int NR = registeredReceivers != null ? registeredReceivers.size() : 0;
    //参数ordered标记当前发送的广播是否是有序广播,可以看到如果发送的是无序广播,进入的是并行广播队列
    if (!ordered && NR > 0) {
        // 如果不是有序广播, 不用等待目标组件是否启动,就可以发送
        if (isCallerSystem) {
            checkBroadcastFromSystem(intent, callerApp, callerPackage, callingUid,
                    isProtectedBroadcast, registeredReceivers);
        }
        //构建广播队列
        final BroadcastQueue queue = broadcastQueueForIntent(intent);
       //广播实体类
        BroadcastRecord r = new BroadcastRecord(queue, intent, callerApp,
                callerPackage, callingPid, callingUid, resolvedType, requiredPermissions,
                appOp, brOptions, registeredReceivers, resultTo, resultCode, resultData,
                resultExtras, ordered, sticky, false, userId);
        if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Enqueueing parallel broadcast " + r);
        final boolean replaced = replacePending && queue.replaceParallelBroadcastLocked(r);
        if (!replaced) {
            //加入到并行广播队列
            queue.enqueueParallelBroadcastLocked(r);
           //处理上面加入并行广播消息队列里面的广播
            queue.scheduleBroadcastsLocked();
        }
        //处理完之后,registeredReceivers要赋值为null
        registeredReceivers = null;
        NR = 0;
    }
关于上面的广播队列BroadcastQueue,AMS内部维持了后台广播队列和前台广播队列

BroadcastQueue mFgBroadcastQueue;//前台广播队列
BroadcastQueue mBgBroadcastQueue;//后台广播队列
final BroadcastQueue[] mBroadcastQueues = new BroadcastQueue[2];

并且在AMS的构造函数中进行初始化
    mFgBroadcastQueue = new BroadcastQueue(this, mHandler,
            "foreground", BROADCAST_FG_TIMEOUT, false);
    mBgBroadcastQueue = new BroadcastQueue(this, mHandler,
            "background", BROADCAST_BG_TIMEOUT, true);
    mBroadcastQueues[0] = mFgBroadcastQueue;
    mBroadcastQueues[1] = mBgBroadcastQueue;

“`
初始化之后,就可以像上面那样(通过broadcastQueueForIntent方法)获取相应的广播队列了,主要就是根据intent中有没有FLAG_RECEIVER_FOREGROUND标记。

“`
BroadcastQueue broadcastQueueForIntent(Intent intent) {
final boolean isFg = (intent.getFlags() & Intent.FLAG_RECEIVER_FOREGROUND) != 0;
if (DEBUG_BROADCAST_BACKGROUND) Slog.i(TAG_BROADCAST,
“Broadcast intent ” + intent + ” on ”
+ (isFg ? “foreground” : “background”) + ” queue”);
return (isFg) ? mFgBroadcastQueue : mBgBroadcastQueue;
}

相应的,将广播加入到队列也很esay,调用 queue.enqueueParallelBroadcastLocked(r)

public void enqueueParallelBroadcastLocked(BroadcastRecord r) {
mParallelBroadcasts.add(r);
r.enqueueClockTime = System.currentTimeMillis();
}

这里又要说明一下mParallelBroadcasts了,BroadcastQueue名字叫做队列,但并不是任何集合的子类,自身不带有存储数据的功能,所以它的内部维护了两个ArrayList。mParallelBroadcasts存放并行广播(无序广播),mOrderedBroadcasts存放串行广播(有序广播)

 ```
/**
     * Lists of all active broadcasts that are to be executed immediately
     * (without waiting for another broadcast to finish).  Currently this only
     * contains broadcasts to registered receivers, to avoid spinning up
     * a bunch of processes to execute IntentReceiver components.  Background-
     * and foreground-priority broadcasts are queued separately.
     */
    final ArrayList<BroadcastRecord> mParallelBroadcasts = new ArrayList<>();

    /**
     * List of all active broadcasts that are to be executed one at a time.
     * The object at the top of the list is the currently activity broadcasts;
     * those after it are waiting for the top to finish.  As with parallel
     * broadcasts, separate background- and foreground-priority queues are
     * maintained.
     */
    final ArrayList<BroadcastRecord> mOrderedBroadcasts = new ArrayList<>();

关于最后调用scheduleBroadcastsLocked进行广播处理的,本文不分析,下篇文章分析。

2.8 整理两个receiver列表

上面判断了ordered,如果ordered==false,也就是发送的是无序广播,那么就进入并行广播队列直接处理掉,如果ordered==false,也就是发送的是有序广播,需要整合将registeredReceivers里面的合并到receivers中。

  // Merge into one list.
        int ir = 0;
        if (receivers != null) {
            // A special case for PACKAGE_ADDED: do not allow the package
            // being added to see this broadcast.  This prevents them from
            // using this as a back door to get run as soon as they are
            // installed.  Maybe in the future we want to have a special install
            // broadcast or such for apps, but we'd like to deliberately make
            // this decision.
            String skipPackages[] = null;
            if (Intent.ACTION_PACKAGE_ADDED.equals(intent.getAction())
                    || Intent.ACTION_PACKAGE_RESTARTED.equals(intent.getAction())
                    || Intent.ACTION_PACKAGE_DATA_CLEARED.equals(intent.getAction())) {
                Uri data = intent.getData();
                if (data != null) {
                    String pkgName = data.getSchemeSpecificPart();
                    if (pkgName != null) {
                        skipPackages = new String[] { pkgName };
                    }
                }
            } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(intent.getAction())) {
                skipPackages = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
            }
            if (skipPackages != null && (skipPackages.length > 0)) {
                for (String skipPackage : skipPackages) {
                    if (skipPackage != null) {
                        int NT = receivers.size();
                        for (int it=0; it<NT; it++) {
                            // 静态注册的广播是ResolveInfo类型  ,动态的是BrocastFilter
                            ResolveInfo curt = (ResolveInfo)receivers.get(it);
                            if (curt.activityInfo.packageName.equals(skipPackage)) {
                                receivers.remove(it);
                                it--;
                                NT--;
                            }
                        }
                    }
                }
            }

            int NT = receivers != null ? receivers.size() : 0;
            int it = 0;
            ResolveInfo curt = null;
            BroadcastFilter curr = null;
            while (it < NT && ir < NR) {
                if (curt == null) {
                   //  静态注册的广播是ResolveInfo类型 
                    curt = (ResolveInfo)receivers.get(it);
                }
                if (curr == null) {
                     // 动态注册的广播是BroadcastFilter类型
                    curr = registeredReceivers.get(ir);
                }
                // 如果动态注册广播接收者优先级高于等于静态广播接收者,则把动态注册的广播接收者插入到当前位置,  
            // 静态注册的广播接收者后移,所以同优先级动态注册的先于静态注册的接收到广播  
                if (curr.getPriority() >= curt.priority) {
                    // Insert this broadcast record into the final list.
                    receivers.add(it, curr);
                    ir++;
                    curr = null;
                    it++;
                    NT++;
                } else {
                    // Skip to the next ResolveInfo in the final list.
                    it++;
                    curt = null;
                }
            }
        }
         /// 把优先级低于所有静态注册广播接收者的动态广播接收者都追加到receivers列表中的末尾  
        while (ir < NR) {
            if (receivers == null) {
                receivers = new ArrayList();
            }
            receivers.add(registeredReceivers.get(ir));
            ir++;
        }
2.9 处理串行广播
   if ((receivers != null && receivers.size() > 0)
                || resultTo != null) {
            //获取广播队列
            BroadcastQueue queue = broadcastQueueForIntent(intent);
            //广播实体类
            BroadcastRecord r = new BroadcastRecord(queue, intent, callerApp,
                    callerPackage, callingPid, callingUid, resolvedType,
                    requiredPermissions, appOp, brOptions, receivers, resultTo, resultCode,
                    resultData, resultExtras, ordered, sticky, false, userId);

            if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Enqueueing ordered broadcast " + r
                    + ": prev had " + queue.mOrderedBroadcasts.size());
            if (DEBUG_BROADCAST) Slog.i(TAG_BROADCAST,
                    "Enqueueing broadcast " + r.intent.getAction());

            boolean replaced = replacePending && queue.replaceOrderedBroadcastLocked(r);
            if (!replaced) {
                //加入串行广播队列
                queue.enqueueOrderedBroadcastLocked(r);
                //处理串行广播
                queue.scheduleBroadcastsLocked();
            }
        } else {
            // There was nobody interested in the broadcast, but we still want to record
            // that it happened.
            if (intent.getComponent() == null && intent.getPackage() == null
                    && (intent.getFlags()&Intent.FLAG_RECEIVER_REGISTERED_ONLY) == 0) {
                // This was an implicit broadcast... let's record it for posterity.
                addBroadcastStatLocked(intent.getAction(), callerPackage, 0, 0, 0);
            }
        }

在2.6小节, 有行代码是 final boolean replacePending = (intent.getFlags()&Intent.FLAG_RECEIVER_REPLACE_PENDING) != 0;这是为了查看intent的flag有没有设置FLAG_RECEIVER_REPLACE_PENDING,如果设置的话, AMS就会在当前的系统中查看有没有相同的intent还未处理,如果有的话,就用当前这个新的intent 来替换旧的intent。所以当replacePending==true的时候,执行queue.replaceParallelBroadcastLocked(r)进行替换,并且返回true.


    public final boolean replaceParallelBroadcastLocked(BroadcastRecord r) {
        for (int i = mParallelBroadcasts.size() - 1; i >= 0; i--) {
            final Intent curIntent = mParallelBroadcasts.get(i).intent;
            if (r.intent.filterEquals(curIntent)) {
                if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST,
                        "***** DROPPING PARALLEL ["
                + mQueueName + "]: " + r.intent);
                mParallelBroadcasts.set(i, r);
                return true;
            }
        }
        return false;
    }

好了,文章比较长,在总结一下广播的发送过程,分为九个部分。
2.1、设置Flag
2.2、检查BroadcastOptions
2.3、当前是否有权力发出广播
2.4、处理系统相关广播
2.5、处理粘性广播
2.6、registeredReceivers和receivers查询
2.7 处理并行广播
2.8 整理两个receiver列表
2.9 处理串行广播

  • 对于粘性广播是在注册过程处理的,创建BroadcastRecord对象;并添加到mParallelBroadcasts队列;
    然后执行queue.scheduleBroadcastsLocked进行处理

  • 对于并行广播: 动态注册的广播会进入mRegisteredReceivers表,会创建BroadcastRecord对象,并添加到mParallelBroadcasts队列;然后执行queue.scheduleBroadcastsLocked;

  • 对于所有静态注册的广播和动态注册的有序广播会进入receivers表中(串行),会创建BroadcastRecord对象,并添加到mOrderedBroadcasts队列;然后执行queue.scheduleBroadcastsLocked;

下篇文章分析广播的处理过程,即scheduleBroadcastsLocked方法到底做了什么?暂时over!

作者:u013263323 发表于2017/7/24 11:48:33 原文链接
阅读:110 评论:0 查看评论

Android源码解析四大组件系列(七)---广播的发送过程

$
0
0

本篇文章的目录

1、广播数据结构分析
2、线程的切换
3、processNextBroadcast分析
  • 3.1、处理并行广播
  • 3.2、处理Pending广播
  • 3.3、处理有序广播
  • 3.4、获取下一条广播
  • 3.5、检查是否是动态广播
  • 3.6、检查是否是静态广播
  • 3.7、启动进程,处理未发送的静态广播
4、动态广播receiver处理
5、静态广播receiver处理

先了解一下广播的数据结构,然后在分析广播的处理过程。建议看本文,需要先看前面两篇文章

Android源码解析—广播的注册过程
Android源码解析—广播的处理过程

1、广播数据结构分析

final class BroadcastRecord extends Binder {

    final Intent intent;    // the original intent that generated us
    final ComponentName targetComp; // original component name set on the intent
    final ProcessRecord callerApp; // 广播调用者进程

    final String[] requiredPermissions; // 调用者需要的权限

    final List receivers;   //存储广播接收者, 包含 BroadcastFilter 和 ResolveInfo

    IIntentReceiver resultTo; // who receives final result if non-null
    long enqueueClockTime;  // 加入队列的时间
    long dispatchTime;      // 分发时间
    long dispatchClockTime; //分发时间
    long receiverTime;      // 接收时间.
    long finishTime;        ////广播完成时间

    int nextReceiver;       // 下一个广播接收者
    IBinder receiver;       //当前广播接收者
    int state;
    int anrCount;           //广播ANR次数

    ProcessRecord curApp;       // hosting application of current receiver.
    ComponentName curComponent; // the receiver class that is currently running.
    ActivityInfo curReceiver;   // info about the receiver that is currently running.

}

比较重要的数据成员有receivers,存储的都是广播接收器,callerApp是广播调用者进程,还要注意四个时间点,有入队列,分发,接收,完成,另外动态广播节点用BroadcastFilter描述,静态的用ResolveInfo描述。

  • ReceiverDispatcher: 客户端广播分发者对象,第一篇讲的很清楚了,ReceiverDispatcher的内部类InnerReceiver为binder对象,用于与AMS的传递与通信。

  • ReceiverList: 继承自ArrayList,存放了Receiver的binder对象以及其注册的BroadcastFilter列表。AMS中定义了
    final HashMap

2、线程的切换

上篇说到不管是有序广播、无序广播还是粘性广播最终都是调用scheduleBroadcastsLocked处理的。那么scheduleBroadcastsLocked做了什么?

    public void scheduleBroadcastsLocked() {
        if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Schedule broadcasts ["
                + mQueueName + "]: current="
                + mBroadcastsScheduled);

        //mBroadcastsScheduled参数是用来标记是否已经向消息队列发送了一个类型为BROADCAST_INTENT_MSG消息
        if (mBroadcastsScheduled) {
            return;
        }
        //发送一个BROADCAST_INTENT_MSG消息
        mHandler.sendMessage(mHandler.obtainMessage(BROADCAST_INTENT_MSG, this));
        mBroadcastsScheduled = true;
    }

mHandler是BroadcastQueue的成员变量,定义如下

final BroadcastHandler mHandler;

它在BroadcastQueue构造函数中初始化

   BroadcastQueue(ActivityManagerService service, Handler handler,
            String name, long timeoutPeriod, boolean allowDelayBehindServices) {
        mService = service;
        mHandler = new BroadcastHandler(handler.getLooper());
        mQueueName = name;
        mTimeoutPeriod = timeoutPeriod;
        mDelayBehindServices = allowDelayBehindServices;
    }

参数中的handler是AMS中的MainHandler,所以BroadcastHandler采用的是ActivityManager线程的Looper,所以通过上面发送一个BROADCAST_INTENT_MSG消息,现在由system_server的binder线程切换到system_server的ActivityManager线程中。

    private final class BroadcastHandler extends Handler {
        public BroadcastHandler(Looper looper) {
            super(looper, null, true);
        }

        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case BROADCAST_INTENT_MSG: {
                    if (DEBUG_BROADCAST) Slog.v(
                            TAG_BROADCAST, "Received BROADCAST_INTENT_MSG");
                    // 处理广播
                    processNextBroadcast(true);
                } break;
                case BROADCAST_TIMEOUT_MSG: {
                    synchronized (mService) {
                      //超时的时候走这里
                        broadcastTimeoutLocked(true);
                    }
                } break;
                case SCHEDULE_TEMP_WHITELIST_MSG: {
                    DeviceIdleController.LocalService dic = mService.mLocalDeviceIdleController;
                    if (dic != null) {
                        dic.addPowerSaveTempWhitelistAppDirect(UserHandle.getAppId(msg.arg1),
                                msg.arg2, true, (String)msg.obj);
                    }
                } break;
            }
        }
    }

所以现在重点分析processNextBroadcast方法。

3、processNextBroadcast分析

processNextBroadcast方法的代码跟broadcastIntentLocked方法一样,也是很长,所以分段来分析。

3.1、处理并行广播

   mService.updateCpuStats();

    //fromMsg字段标记是否是从handleMessage中调用的该方法  
    if (fromMsg) {
        //设置该参数为false,表示前面发送到消息队列中的BROADCAST_INTENT_MSG消息已经被处理了
        mBroadcastsScheduled = false;
    }

    //并行广播在这里循环一次性处理掉
    while (mParallelBroadcasts.size() > 0) {
        r = mParallelBroadcasts.remove(0);
        r.dispatchTime = SystemClock.uptimeMillis();
        r.dispatchClockTime = System.currentTimeMillis();
        final int N = r.receivers.size();
        if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG_BROADCAST, "Processing parallel broadcast ["
                + mQueueName + "] " + r);
        for (int i=0; i<N; i++) {
            Object target = r.receivers.get(i);
            if (DEBUG_BROADCAST)  Slog.v(TAG_BROADCAST,
                    "Delivering non-ordered on [" + mQueueName + "] to registered "
                    + target + ": " + r);
            //并行广播都是从deliverToRegisteredReceiverLocked发送出去的,此时r.receivers里面的都是BroadcastFilter
            deliverToRegisteredReceiverLocked(r, (BroadcastFilter)target, false, i);
        }

        addBroadcastToHistoryLocked(r);
        if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG_BROADCAST, "Done with parallel broadcast ["
                + mQueueName + "] " + r);
    }

3.2、处理Pending广播

如果一个广播在发送的时候,进程还没有启动起来,那么会将它存在mPendingBroadcast中。由于动态广播是不会保证一定能够收到的,所以mPendingBroadcast是用来描述一个正在等待静态注册的目标广播接收者启动的广播。

  // Now take care of the next serialized one...

    // If we are waiting for a process to come up to handle the next
    // broadcast, then do nothing at this point.  Just in case, we
    // check that the process we're waiting for still exists.
    if (mPendingBroadcast != null) {
        if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG_BROADCAST,
                "processNextBroadcast [" + mQueueName + "]: waiting for "
                + mPendingBroadcast.curApp);

        boolean isDead;
        // 检查这个静态注册的目标广播接收者所运行在的应用程序进程是否已经启动起来  
        synchronized (mService.mPidsSelfLocked) {
            ProcessRecord proc = mService.mPidsSelfLocked.get(mPendingBroadcast.curApp.pid);
            isDead = proc == null || proc.crashing;
        }
        如果这个应用程序进程还活着,就会继续等待,否则就不等了
        if (!isDead) {
            // It's still alive, so keep waiting
            return;
        } else {
            Slog.w(TAG, "pending app  ["
                    + mQueueName + "]" + mPendingBroadcast.curApp
                    + " died before responding to broadcast");
            mPendingBroadcast.state = BroadcastRecord.IDLE;
            mPendingBroadcast.nextReceiver = mPendingBroadcastRecvIndex;
            mPendingBroadcast = null;
        }
    }
    boolean looped = false;

3.3、处理有序广播

有序广播是一个接着一个处理的,并且还可以拦截,

do {
        //有序广播队列为0,不用处理,返回
        if (mOrderedBroadcasts.size() == 0) {
            // No more broadcasts pending, so all done!
            mService.scheduleAppGcsLocked();
            if (looped) {
                // If we had finished the last ordered broadcast, then
                // make sure all processes have correct oom and sched
                // adjustments.
                //更改一下OOM
                mService.updateOomAdjLocked();
            }
            return;
        }
        //获取一个有序广播(最顶部的BroadcastRecord)
        r = mOrderedBroadcasts.get(0);
        boolean forceReceive = false;

         //广播超时处理,这个会在下一节具体分析
        int numReceivers = (r.receivers != null) ? r.receivers.size() : 0;
        if (mService.mProcessesReady && r.dispatchTime > 0) {
            long now = SystemClock.uptimeMillis();
            if ((numReceivers > 0) &&
                    (now > r.dispatchTime + (2*mTimeoutPeriod*numReceivers))) {
                Slog.w(TAG, "Hung broadcast ["
                        + mQueueName + "] discarded after timeout failure:"
                        + " now=" + now
                        + " dispatchTime=" + r.dispatchTime
                        + " startTime=" + r.receiverTime
                        + " intent=" + r.intent
                        + " numReceivers=" + numReceivers
                        + " nextReceiver=" + r.nextReceiver
                        + " state=" + r.state);
                 //超时不能接收,就强制结束广播
                broadcastTimeoutLocked(false); // forcibly finish this broadcast
                forceReceive = true;
                //恢复初始状态。
                r.state = BroadcastRecord.IDLE;
            }
        }

        if (r.state != BroadcastRecord.IDLE) {
            if (DEBUG_BROADCAST) Slog.d(TAG_BROADCAST,
                    "processNextBroadcast("
                    + mQueueName + ") called when not idle (state="
                    + r.state + ")");
            return;
        }

         // 进入下面的条件有以下集中可能,一:广播没有接收者了,二:默认第一次r.nextReceiver = 0,但是它每次都会++,等待最后一个处理完了,再++就会得到一个不存在的nextReceiver,此时r.nextReceiver >= numReceivers,条件成立,三:
广播是否已经被拦截了,四:广播是否已经被强制结束了  
        if (r.receivers == null || r.nextReceiver >= numReceivers
                || r.resultAbort || forceReceive) {
            // No more receivers for this broadcast!  Send the final
            // result if requested...
            if (r.resultTo != null) {
                try {
                    if (DEBUG_BROADCAST) Slog.i(TAG_BROADCAST,
                            "Finishing broadcast [" + mQueueName + "] "
                            + r.intent.getAction() + " app=" + r.callerApp);
                   //处理广播消息消息,调用到onReceive()
                    performReceiveLocked(r.callerApp, r.resultTo,
                        new Intent(r.intent), r.resultCode,
                        r.resultData, r.resultExtras, false, false, r.userId);
                    // Set this to null so that the reference
                    // (local and remote) isn't kept in the mBroadcastHistory.
                    r.resultTo = null;
                } catch (RemoteException e) {
                    r.resultTo = null;
                    Slog.w(TAG, "Failure ["
                            + mQueueName + "] sending broadcast result of "
                            + r.intent, e);

                }
            }

            if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Cancelling BROADCAST_TIMEOUT_MSG");
                //r所描述的广播转发任务已经在规定时间内处理完了,需要remove掉前面给mHandler发送的BROADCAST_TIMEOUT_MSG消息。
            cancelBroadcastTimeoutLocked();

            if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG_BROADCAST,
                    "Finished with ordered broadcast " + r);

            // ... and on to the next...
            addBroadcastToHistoryLocked(r);
            if (r.intent.getComponent() == null && r.intent.getPackage() == null
                    && (r.intent.getFlags()&Intent.FLAG_RECEIVER_REGISTERED_ONLY) == 0) {
                // This was an implicit broadcast... let's record it for posterity.
                mService.addBroadcastStatLocked(r.intent.getAction(), r.callerPackage,
                        r.manifestCount, r.manifestSkipCount, r.finishTime-r.dispatchTime);
            }
            // 将r所描述的广播转发任务从有序广播队列中删除  
            mOrderedBroadcasts.remove(0);
            //赋值为null,进入下一次循环
            r = null;
            looped = true;
            continue;
        }
 } while (r == null);

 3.4、获取下一条广播

    //通过上面的循环,r就有值了,获取下一条广播
    int recIdx = r.nextReceiver++;

    // Keep track of when this receiver started, and make sure there
    // is a timeout message pending to kill it if need be.
    r.receiverTime = SystemClock.uptimeMillis();
    if (recIdx == 0) {
        r.dispatchTime = r.receiverTime;
        r.dispatchClockTime = System.currentTimeMillis();
        if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG_BROADCAST, "Processing ordered broadcast ["
                + mQueueName + "] " + r);
    }
    if (! mPendingBroadcastTimeoutMessage) {
        long timeoutTime = r.receiverTime + mTimeoutPeriod;
        if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST,
                "Submitting BROADCAST_TIMEOUT_MSG ["
                + mQueueName + "] for " + r + " at " + timeoutTime);
        setBroadcastTimeoutLocked(timeoutTime);
    }ComponentName component

    final BroadcastOptions brOptions = r.options;
    final Object nextReceiver = r.receivers.get(recIdx);
 ```

####3.5、检查是否是动态广播
 //检查是否是动态广播

if (nextReceiver instanceof BroadcastFilter) {
BroadcastFilter filter = (BroadcastFilter)nextReceiver;
if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST,
“Delivering ordered [”
+ mQueueName + “] to registered ”
+ filter + “: ” + r);
//动态注册的广播给deliverToRegisteredReceiverLocked处理
deliverToRegisteredReceiverLocked(r, filter, r.ordered, recIdx);
//判断是否是无序广播
if (r.receiver == null || !r.ordered) {
// The receiver has already finished, so schedule to
// process the next one.
if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, “Quick finishing [”
+ mQueueName + “]: ordered=”
+ r.ordered + ” receiver=” + r.receiver);
//将 r.state设置为IDLE,表示不需要等待它的前一个目标广播接收者处理完成一个广播,
// 就可以将该广播继续发送给它的下一个目标广播接收者处理
r.state = BroadcastRecord.IDLE;
//执行下一个广播,内部也是发送消息
scheduleBroadcastsLocked();
} else {
….
}
//有序广播一次只处理一个,直接返回就行
return;
}


####3.6、检查是否是静态广播

//如果上面没有return,那么肯定是静态注册的广播,静态注册注册的广播节点是ResolveInfo
ResolveInfo info = (ResolveInfo)nextReceiver;
//这个ComponentName会一直传递到ActivityThread,用来反射new广播接收者对象的
ComponentName component = new ComponentName(
info.activityInfo.applicationInfo.packageName,
info.activityInfo.name);
//很多的条件判断,skip不满足就为true
boolean skip = false;
…….

 String targetProcess = info.activityInfo.processName;
 ProcessRecord app = mService.getProcessRecordLocked(targetProcess,
        info.activityInfo.applicationInfo.uid, false);

 if (skip) {
    if (DEBUG_BROADCAST)  Slog.v(TAG_BROADCAST,
            "Skipping delivery of ordered [" + mQueueName + "] "
            + r + " for whatever reason");
    r.delivery[recIdx] = BroadcastRecord.DELIVERY_SKIPPED;
    r.receiver = null;
    r.curFilter = null;
//变成初始状态
    r.state = BroadcastRecord.IDLE;
//执行下个广播
    scheduleBroadcastsLocked();
    return;
 }

…….
//如果当前进程存在,调用processCurBroadcastLocked处理,静态广播都是走processCurBroadcastLocked处理的
if (app != null && app.thread != null) {
try {
app.addPackage(info.activityInfo.packageName,
info.activityInfo.applicationInfo.versionCode, mService.mProcessStats);
processCurBroadcastLocked(r, app);
//处理完直接返回,一次处理一个
return;
} catch (RemoteException e) {
Slog.w(TAG, “Exception when sending broadcast to ”
+ r.curComponent, e);
} catch (RuntimeException e) {
Slog.wtf(TAG, “Failed sending broadcast to ”
+ r.curComponent + ” with ” + r.intent, e);
// If some unexpected exception happened, just skip
// this broadcast. At this point we are not in the call
// from a client, so throwing an exception out from here
// will crash the entire system instead of just whoever
// sent the broadcast.
logBroadcastReceiverDiscardLocked(r);
finishReceiverLocked(r, r.resultCode, r.resultData,
r.resultExtras, r.resultAbort, false);
scheduleBroadcastsLocked();
// We need to reset the state if we failed to start the receiver.
r.state = BroadcastRecord.IDLE;
return;
}

    // If a dead object exception was thrown -- fall through to
    // restart the application.
}

//程序走到这里,说明进程不存在,那么调用startProcessLocked启动进程,
if ((r.curApp=mService.startProcessLocked(targetProcess,
info.activityInfo.applicationInfo, true,
r.intent.getFlags() | Intent.FLAG_FROM_BACKGROUND,
“broadcast”, r.curComponent,
(r.intent.getFlags()&Intent.FLAG_RECEIVER_BOOT_UPGRADE) != 0, false, false))
== null) {
// Ah, this recipient is unavailable. Finish it if necessary,
// and mark the broadcast record as ready for the next.
Slog.w(TAG, “Unable to launch app ”
+ info.activityInfo.applicationInfo.packageName + “/”
+ info.activityInfo.applicationInfo.uid + ” for broadcast ”
+ r.intent + “: process is bad”);
logBroadcastReceiverDiscardLocked(r);
finishReceiverLocked(r, r.resultCode, r.resultData,
r.resultExtras, r.resultAbort, false);
//启动失败就执行下一个
scheduleBroadcastsLocked();
r.state = BroadcastRecord.IDLE;
return;
}
//把当前的r存下来,方便下一次处理
mPendingBroadcast = r;
//设置当前的receiver的索引,用来表示将要启动的。
mPendingBroadcastRecvIndex = recIdx;

“`

3.7、启动进程,处理未发送的静态广播

当进程启动完成之后,会回调AMS的attachApplication,然后走到attachApplicationLocked。
“`
// Check if a next-broadcast receiver is in this process…
if (!badApp && isPendingBroadcastProcessLocked(pid)) {
try {
didSomething |= sendPendingBroadcastsLocked(app);
} catch (Exception e) {
// If the app died trying to launch the receiver we declare it ‘bad’
Slog.wtf(TAG, “Exception thrown dispatching broadcasts in ” + app, e);
badApp = true;
}
}



// The app just attached; send any pending broadcasts that it should receive
boolean sendPendingBroadcastsLocked(ProcessRecord app) {
boolean didSomething = false;
for (BroadcastQueue queue : mBroadcastQueues) {
didSomething |= queue.sendPendingBroadcastsLocked(app);
}
return didSomething;
}

public boolean sendPendingBroadcastsLocked(ProcessRecord app) {
boolean didSomething = false;
final BroadcastRecord br = mPendingBroadcast;
if (br != null && br.curApp.pid == app.pid) {
if (br.curApp != app) {
Slog.e(TAG, “App mismatch when sending pending broadcast to ”
+ app.processName + “, intended target is ” + br.curApp.processName);
return false;
}
try {
//目标进程已经成功启动了,那么mPendingBroadcast就可以赋值为null了
mPendingBroadcast = null;
//调用processCurBroadcastLocked处理广播
processCurBroadcastLocked(br, app);
didSomething = true;
} catch (Exception e) {
Slog.w(TAG, “Exception in new application when starting receiver ”
+ br.curComponent.flattenToShortString(), e);
logBroadcastReceiverDiscardLocked(br);
finishReceiverLocked(br, br.resultCode, br.resultData,
br.resultExtras, br.resultAbort, false);
scheduleBroadcastsLocked();
// We need to reset the state if we failed to start the receiver.
br.state = BroadcastRecord.IDLE;
throw new RuntimeException(e.getMessage());
}
}
return didSomething;
}

“`

看完上面的三个小节,到这里总结一下 processNextBroadcast()的代码逻辑:

  • 如果是动态广播接收者(无序),会调用deliverToRegisteredReceiverLocked一次性处理,即遍历并行列表(mParallelBroadcasts)的每一个BroadcastRecord以及其中的receivers列表,是一个双重循环。
  • 如果是静态广播接收者(有序),且对应进程已经创建,会调用processCurBroadcastLocked继续处理;
  • 如果是静态广播接收者(有序),且对应进程尚未创建,会调用startProcessLocked创建进程,之后仍然会调用processCurBroadcastLocked继续处理。

4、动态广播receiver处理

上面主要分析了动态广播接收者和静态广播接收者该如何处理,现在先看动态广播是如何处理的。就是分析deliverToRegisteredReceiverLocked方法实现。
“`
private void deliverToRegisteredReceiverLocked(BroadcastRecord r,
BroadcastFilter filter, boolean ordered, int index) {
boolean skip = false;
//权限判断, 检查发送者是否有权限,检查接收者是否有发送者所需的权限等等,
//此处省略,不符合的skip==true,下面就return。
….

    if (skip) {
        r.delivery[index] = BroadcastRecord.DELIVERY_SKIPPED;
        return;
    }

      ....
    //只有有序广播才会进入这个分支
    if (ordered) {
        r.receiver = filter.receiverList.receiver.asBinder();
        r.curFilter = filter;
        filter.receiverList.curBroadcast = r;
        r.state = BroadcastRecord.CALL_IN_RECEIVE;
        if (filter.receiverList.app != null) {
            // Bump hosting application to no longer be in background
            // scheduling class.  Note that we can't do that if there
            // isn't an app...  but we can only be in that case for
            // things that directly call the IActivityManager API, which
            // are already core system stuff so don't matter for this.
            r.curApp = filter.receiverList.app;
            filter.receiverList.app.curReceiver = r;
            mService.updateOomAdjLocked(r.curApp);
        }
    }
    try {
        if (DEBUG_BROADCAST_LIGHT) Slog.i(TAG_BROADCAST,
                "Delivering to " + filter + " : " + r);
        if (filter.receiverList.app != null && filter.receiverList.app.inFullBackup) {
            // Skip delivery if full backup in progress
            // If it's an ordered broadcast, we need to continue to the next receiver.
            if (ordered) {
                skipReceiverLocked(r);
            }
        } else {
        //处理广播,filter.receiverList.receiver对应的是客户端ReceiverDispatcher的Binder实体——InnerReceiver
            performReceiveLocked(filter.receiverList.app, filter.receiverList.receiver,
                    new Intent(r.intent), r.resultCode, r.resultData,
                    r.resultExtras, r.ordered, r.initialSticky, r.userId);
        }
        if (ordered) {
            r.state = BroadcastRecord.CALL_DONE_RECEIVE;
        }
    } catch (RemoteException e) {
       ....
    }
}

“`

 void performReceiveLocked(ProcessRecord app, IIntentReceiver receiver,
            Intent intent, int resultCode, String data, Bundle extras,
            boolean ordered, boolean sticky, int sendingUser) throws RemoteException {
        // Send the intent to the receiver asynchronously using one-way binder calls.
        if (app != null) {
            if (app.thread != null) {
                try {
                    //终于走到ActivityThread里面了
                    app.thread.scheduleRegisteredReceiver(receiver, intent, resultCode,
                            data, extras, ordered, sticky, sendingUser, app.repProcState);
                } catch (RemoteException ex) {
                    .....
                }
            } else {
                // Application has died. Receiver doesn't exist.
                throw new RemoteException("app.thread must not be null");
            }
        } else {
            //调用者进程不存在,则执行该分支
            receiver.performReceive(intent, resultCode, data, extras, ordered,
                    sticky, sendingUser);
        }
    }
       public void scheduleRegisteredReceiver(IIntentReceiver receiver, Intent intent,
                int resultCode, String dataStr, Bundle extras, boolean ordered,
                boolean sticky, int sendingUser, int processState) throws RemoteException {
            updateProcessState(processState, false);
           //走到ReceiverDispatcher中的performReceive实际是InnerReceiver内部类当中的方法
            receiver.performReceive(intent, resultCode, dataStr, extras, ordered,
                    sticky, sendingUser);
        }

终于走到客户端的ReceiverDispatcher(广播分发者)了,ReceiverDispatcher知道我们这个广播要分发给谁。此时正式由SystemServer进程回到客户端进程了。

    @Override
     public void performReceive(Intent intent, int resultCode, String data,
            Bundle extras, boolean ordered, boolean sticky, int sendingUser) {
        final LoadedApk.ReceiverDispatcher rd;
         ......
        if (rd != null) {
            rd.performReceive(intent, resultCode, data, extras,
                    ordered, sticky, sendingUser);
        } else {
            // The activity manager dispatched a broadcast to a registered
            // receiver in this process, but before it could be delivered the
            // receiver was unregistered.  Acknowledge the broadcast on its
            // behalf so that the system's broadcast sequence can continue.
            if (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG,
                    "Finishing broadcast to unregistered receiver");
            IActivityManager mgr = ActivityManagerNative.getDefault();
            try {
                if (extras != null) {
                    extras.setAllowFds(false);
                }
                mgr.finishReceiver(this, resultCode, data, extras, false, intent.getFlags());
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
        }
    }

可以看到我们调用ReceiverDispatcher中InnerReceiver的performReceive之后,紧接着在内部调用了ReceiverDispatcher的performReceive方法,再看ReceiverDispatcher的performReceive方法。

 public void performReceive(Intent intent, int resultCode, String data,
                Bundle extras, boolean ordered, boolean sticky, int sendingUser) {
            final Args args = new Args(intent, resultCode, data, extras, ordered,
                    sticky, sendingUser);
            if (intent == null) {
                Log.wtf(TAG, "Null intent received");
            } else {
                if (ActivityThread.DEBUG_BROADCAST) {
                    int seq = intent.getIntExtra("seq", -1);
                    Slog.i(ActivityThread.TAG, "Enqueueing broadcast " + intent.getAction()
                            + " seq=" + seq + " to " + mReceiver);
                }
            }
            //post了一个消息给主线程
            if (intent == null || !mActivityThread.post(args)) {
                if (mRegistered && ordered) {
                    IActivityManager mgr = ActivityManagerNative.getDefault();
                    if (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG,
                            "Finishing sync broadcast to " + mReceiver);
                    ///发送完成本次广播处理,用来进行下次的广播处理。
                    args.sendFinished(mgr);
                }
            }
        }

上面的mActivityThread在第一篇文章说过,代表主线程,所以现在会执行args的run方法。

   public void run() {
        final BroadcastReceiver receiver = mReceiver;
        final boolean ordered = mOrdered;

        if (ActivityThread.DEBUG_BROADCAST) {
            int seq = mCurIntent.getIntExtra("seq", -1);
            Slog.i(ActivityThread.TAG, "Dispatching broadcast " + mCurIntent.getAction()
                    + " seq=" + seq + " to " + mReceiver);
            Slog.i(ActivityThread.TAG, "  mRegistered=" + mRegistered
                    + " mOrderedHint=" + ordered);
        }

        final IActivityManager mgr = ActivityManagerNative.getDefault();
        final Intent intent = mCurIntent;
        if (intent == null) {
            Log.wtf(TAG, "Null intent being dispatched, mDispatched=" + mDispatched);
        }

        ......
        try {
            ClassLoader cl =  mReceiver.getClass().getClassLoader();
            intent.setExtrasClassLoader(cl);
            intent.prepareToEnterProcess();
            setExtrasClassLoader(cl);
            receiver.setPendingResult(this);
        //onReceive方法回调
            receiver.onReceive(mContext, intent);
        } catch (Exception e) {
            .....
    }

到此,deliverToRegisteredReceiverLocked是怎么处理动态广播就分析完毕了。总结一下主要流程

——|-BroadcastQueue.performReceiveLocked()
——|——-|-ActivityThread.ApplicationThread.scheduleRegisteredReceiver()
——|——-|——-|- ReceiverDispatcher.InnerReceiver.performReceive()
——|——-|——-|——-|-Handler.post(args)
——|——-|——-|——-|——-|-Args.run()
——|——-|——-|——-|——-|——-|-BroadcastReceiver.onReceive()

5、静态广播receiver处理

刚刚说了静态广播是processCurBroadcastLocked处理的

    private final void processCurBroadcastLocked(BroadcastRecord r,
            ProcessRecord app) throws RemoteException {
        .....

        r.receiver = app.thread.asBinder();
        r.curApp = app;
        app.curReceiver = r;
       //更新进程状态
        app.forceProcessStateUpTo(ActivityManager.PROCESS_STATE_RECEIVER);
        //更新内存
        mService.updateLruProcessLocked(app, false, null);
        //更新adj
        mService.updateOomAdjLocked();

        // Tell the application to launch this receiver.告诉客户端启动这个receiver
        r.intent.setComponent(r.curComponent);

        boolean started = false;
        try {
              .....
            //走到了ActivityThread中的ApplicationThread中对应的方法。
            app.thread.scheduleReceiver(new Intent(r.intent), r.curReceiver,
                    mService.compatibilityInfoForPackageLocked(r.curReceiver.applicationInfo),
                    r.resultCode, r.resultData, r.resultExtras, r.ordered, r.userId,
                    app.repProcState);
            if (DEBUG_BROADCAST)  Slog.v(TAG_BROADCAST,
                    "Process cur broadcast " + r + " DELIVERED for app " + app);
            started = true;
        } finally {
            .....
            }
         }
     }

此时正式由SystemServer进程进入了客户端进程了

 public final void scheduleReceiver(Intent intent, ActivityInfo info,
                CompatibilityInfo compatInfo, int resultCode, String data, Bundle extras,
                boolean sync, int sendingUser, int processState) {
            updateProcessState(processState, false);
            ReceiverData r = new ReceiverData(intent, resultCode, data, extras,
                    sync, false, mAppThread.asBinder(), sendingUser);
            r.info = info;
            r.compatInfo = compatInfo;
            sendMessage(H.RECEIVER, r);
        }

一样的套路,发送一个消息给主线程,进入对应的case,执行 handleReceiver((ReceiverData)msg.obj);

    private void handleReceiver(ReceiverData data) {

       // 这个最初就是在processNextBroadcast处理静态注册的ResolveInfo时,new的ComponentName。
        String component = data.intent.getComponent().getClassName();

        LoadedApk packageInfo = getPackageInfoNoCheck(
                data.info.applicationInfo, data.compatInfo);

        IActivityManager mgr = ActivityManagerNative.getDefault();

        BroadcastReceiver receiver;
        try {
        //反射出BroadcastReceiver
            java.lang.ClassLoader cl = packageInfo.getClassLoader();
            data.intent.setExtrasClassLoader(cl);
            data.intent.prepareToEnterProcess();
            data.setExtrasClassLoader(cl);
            receiver = (BroadcastReceiver)cl.loadClass(component).newInstance();
        } catch (Exception e) {
            if (DEBUG_BROADCAST) Slog.i(TAG,
                    "Finishing failed broadcast to " + data.intent.getComponent());
            data.sendFinished(mgr);
            throw new RuntimeException(
                "Unable to instantiate receiver " + component
                + ": " + e.toString(), e);
        }

        try {
            Application app = packageInfo.makeApplication(false, mInstrumentation);

            if (localLOGV) Slog.v(
                TAG, "Performing receive of " + data.intent
                + ": app=" + app
                + ", appName=" + app.getPackageName()
                + ", pkg=" + packageInfo.getPackageName()
                + ", comp=" + data.intent.getComponent().toShortString()
                + ", dir=" + packageInfo.getAppDir());

            ContextImpl context = (ContextImpl)app.getBaseContext();
            sCurrentBroadcastIntent.set(data.intent);
            receiver.setPendingResult(data);
        //这个和动态的不一样,静态的广播onReceive方法中的context是RestrictedContext
            receiver.onReceive(context.getReceiverRestrictedContext(),
                    data.intent);
        } catch (Exception e) {
            if (DEBUG_BROADCAST) Slog.i(TAG,
                    "Finishing failed broadcast to " + data.intent.getComponent());
            data.sendFinished(mgr);
            if (!mInstrumentation.onException(receiver, e)) {
                throw new RuntimeException(
                    "Unable to start receiver " + component
                    + ": " + e.toString(), e);
            }
        } finally {
            sCurrentBroadcastIntent.set(null);
        }

        if (receiver.getPendingResult() != null) {
            data.finish();
        }
    }

到这总结一下静态广播接收者的处理流程,如下:
如果应用程序已经启动(app.thread != null)
——|-ActivityThread.ApplicationThread.scheduleReceiver()
——|——|- ActivityThread.handleReceiver()
——|——-|——-|- BroadcastReceiver.onReceive()
否则
——|-LoadedApk.ReceiverDispatcher.IntentReceiver.performReceive()
——|——-|-LoadedApk.ReceiverDispatcher.performReceiver()
——|——-|——-|- LoadedApk.ReceiverDispatcher.Args.run()
——|——-|——-|——-|-BroadcastReceiver.onReceive()

至此广播的处理过程就结束了,下篇写一下广播细节,加深理解,比如广播有序是怎么保证的?怎么实现广播拦截处理的?广播超时是怎么处理的?onReceive方法中可以在发送广播吗?registerReceiver方法发返回值有什么用?粘性广播等等

作者:u013263323 发表于2017/7/24 11:54:01 原文链接
阅读:63 评论:0 查看评论

cordova media插件

$
0
0


简介

使用Media插件可以在设备上播放音频与录制音频。

 

注意:当前实现并不是遵循W3C规范的媒体捕捉,这里只是提供方便。未来的实现将坚持新的W3C规范和可能弃用当前API

 

 

 

 

 

安装

cordova plugin add cordova-plugin-media

 

 

 

 

 

 

 

支持的平台

· Android

· BlackBerry 10

· iOS

· Windows Phone 7 and 8

· Tizen

· Windows 8

· Windows

· Browser

 

 

 

 

 

 

使用方法

这个插件定义一个全局媒体构造函数

虽然在全局范围内,这是deviceready事件以后才可用

document.addEventListener("deviceready", onDeviceReady, false);

function onDeviceReady() {

    console.log(Media);

}







详情

Media

初始化Media对象

var media = new Media(src, mediaSuccess, [mediaError], [mediaStatus]);

 

参数Parameters

src:  一个URI包含了这个音频内容。

mediaSuccess(可选)一个媒体对象已完成当前播放,记录,或停止行动执行这个回调函数(函数)

mediaError(可选)如果出现错误执行这个回调函数。(函数)

mediaStatus(可选)执行显示状态变化回调函数(函数)

 

注意:cdvfile路径支持src参数使用:

var my_media = new Media('cdvfile://localhost/temporary/recording.mp3', ...);

 

 

 

常量Constants

下面的常量是报道的唯一参数mediastatus回调:

· Media.MEDIA_NONE = 0;

· Media.MEDIA_STARTING = 1;

· Media.MEDIA_RUNNING = 2;

· Media.MEDIA_PAUSED = 3;

· Media.MEDIA_STOPPED = 4;

 

 

 

方法Methods

media.getCurrentAmplitude返回在音频文件的当前位置。

media.getCurrentPosition返回在音频文件的当前位置。

media.getDuration返回一个音频文件的持续时间。

media.play开始或恢复播放一个音频文件。

media.pause暂停播放音频文件

media.pauseRecord音频文件暂停录音

media.release释放操作系统底层的音频资源。

media.resumeRecord恢复音频文件记录

media.seekTo移动到当前播放音频文件位置。

media.setVolume音频播放的音量设置

media.startRecord开始录制音频文件

media.stopRecord停止录制音频文件

media.stop停止播放音频文件




media.getCurrentAmplitude

返回当前记录的电流幅值。

media.getCurrentAmplitude(mediaSuccess, [mediaError]);

  

支持的平台Supported Platforms

· Android

· iOS

 

 参数Parameters

mediaSuccess回调,通过电流的幅值(0 - 1)。

mediaError(可选)回调执行如果出现错误。




media.getCurrentPosition

返回在一个音频文件的当前位置。还更新了媒体对象的位置参数.

media.getCurrentPosition(mediaSuccess, [mediaError]);

  

参数Parameters

mediaSuccess回调,当前位置的秒数。

mediaError(可选)回调执行如果出现错误。

 


 

media.getDuration

在短时间内返回一个音频文件的持续时间。如果时间是未知的,它返回的值为1

media.getDuration();




media.pause

暂停播放音频文件

media.pause();




media.pauseRecord

暂停录制音频文件

media.pauseRecord();

 

支持的平台Supported Platforms

· iOS




media.play

开始或恢复播放音频文件。

media.play();



 

media.release

发布操作系统底层的音频资源。这对于Android来说尤为重要,因为有一个有限的媒体播放OpenCore实例。应用程序不再需要任何媒体资源应该调用release 函数。

media.release();



 

media.resumeRecord

恢复录制音频文件

media.resumeRecord();

 

支持的平台Supported Platforms

· iOS


 

 

media.seekTo

设置当前的位置在一个音频文件。

media.seekTo(milliseconds);

 

参数Parameters

· milliseconds设置播放音频中的位置,以毫秒为单位。




media.setVolume

一个音频文件的音量设置。

media.setVolume(volume);

 

参数Parameters

· volume音量设置播放。该值必须在01的范围内。

 

支持的平台Supported Platforms

· Android

· iOS

 

 

 

media.startRecord

开始录制音频文件

media.startRecord();

 

支持的平台Supported Platforms

· Android

· iOS

· Windows Phone 7 and 8

· Windows

 


 

media.stop

停止播放音频文件

media.stop();


 

 

media.stopRecord

停止录制音频文件

media.stopRecord();

 

支持的平台Supported Platforms

· Android

· iOS

· Windows Phone 7 and 8

· Windows






 

示例

示例一:音频播放/暂定

index.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta http-equiv="Content-Security-Policy" content="default-src 'self' data: gap: https://ssl.gstatic.com 'unsafe-eval'; style-src 'self' 'unsafe-inline'; media-src *; img-src 'self' data: content:;">
    <meta name="format-detection" content="telephone=no">
    <meta name="msapplication-tap-highlight" content="no">
    <meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width">
    <title>Hello World</title>
    <style>
         .line{
              padding: 10px;
         }
    </style>
</head>
<body>
<div class="app">
    <h1>media插件</h1>
    <div class="line"><button id="play">播放</button></div>
    <div class="line"><button id="pause">暂定</button></div>
    <div class="line"><button id="stop">停止</button></div>
    <div class="line"><button id="release">释放</button></div>

    <div id="audio_current"></div>
    <div id="audio_duration"></div>
</div>
<script type="text/javascript" src="cordova.js"></script>
<script type="text/javascript" src="js/index.js"></script>
</body>
</html>

 

index.js

var app = {
   initialize: function() {
      document.addEventListener('deviceready', this.onDeviceReady.bind(this), false);
   },

   onDeviceReady: function() {
      this.receivedEvent();
   },
   $$: function(id) {
      return document.getElementById(id);
   },
   receivedEvent: function() {
      var src = "http://ws.stream.qqmusic.qq.com/C1000031Ecjg1tdNzL.m4a?fromtag=38";
      var my_media = null;
      var mediaTimer = null;
      var timerDur = null;

      function mediaSuccess() {
         console.log("Media成功")
      }

      function mediaError(err) {
         console.log("Media失败")
      }

      // 开始或恢复播放一个音频文件
      function playAudio() {
         if(my_media == null) {
            // 初始化Media对象
            my_media = new Media(src, mediaSuccess, mediaError);
         }
         // 播放音频
         my_media.play();
      }

      // 暂停播放音频文件
      function pauseAudio() {
         if(my_media) {
            my_media.pause();
         }
         // 清除定时器对象
            clearInterval(mediaTimer);
            mediaTimer = null;
      }

      // 释放操作系统底层的音频资源。
      function releaseAudio() {
         if(my_media) {
            my_media.release();
         }

      }

      // 停止播放音频文件
      function stopAudio() {
         if(my_media) {

         }my_media.stop();
         // 清除定时器对象
         clearInterval(mediaTimer);
         mediaTimer = null;
      }

      // 返回在音频文件的当前位置。
      function getCurrent() {
          if(mediaTimer == null){
                mediaTimer = setInterval(function() {
                    my_media.getCurrentPosition(
                        // success callback
                        function(position) {
                            if(position > -1) {
                                console.log((position) + " sec");
                            }
                            document.getElementById('audio_current').innerHTML = position;
                        },
                        // error callback
                        function(e) {
                            console.log("Error getting pos=" + e);
                        }
                    );
                }, 1000);
          }
      }

      // 返回一个音频文件的持续时间。
      function getDuration() {
         // Get duration
         var counter = 0;
         var timerDur = setInterval(function() {
            counter = counter + 100;
            if(counter > 2000) {
               clearInterval(timerDur);
            }
            var dur = my_media.getDuration();
            if(dur > 0) {
               clearInterval(timerDur);
               document.getElementById('audio_duration').innerHTML = (dur) + " sec";
            }
         }, 100);
      }

      this.$$("play").onclick = function() {
         playAudio();
         getCurrent();
         getDuration();
      }
      this.$$("pause").onclick = function() {
         pauseAudio();
      }
      this.$$("release").onclick = function() {
         releaseAudio();
      }
      this.$$("stop").onclick = function() {
         stopAudio();
      }

   }

};

app.initialize();

 

运行:

点击“播放”按钮,开始播放音频。

点击“暂停”按钮,暂定当前音频播放,点击“播放”按钮,继续播放音频。

点击“停止”按钮,音频播放停止。

点击“释放”按钮,音频播放停止,并释放系统底层音频资源。



 

 

示例二:录制音频

index.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta http-equiv="Content-Security-Policy" content="default-src 'self' data: gap: https://ssl.gstatic.com 'unsafe-eval'; style-src 'self' 'unsafe-inline'; media-src *; img-src 'self' data: content:;">
    <meta name="format-detection" content="telephone=no">
    <meta name="msapplication-tap-highlight" content="no">
    <meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width">
    <title>Hello World</title>
    <style>
         .line{
              padding: 10px;
         }
    </style>
</head>
<body>
<div class="app">
    <h1>media插件</h1>
    <div class="line"><button id="startRecord">开始录制</button></div>
</div>
<script type="text/javascript" src="cordova.js"></script>
<script type="text/javascript" src="js/index.js"></script>
</body>
</html>

  

index.js

var app = {
   initialize: function() {
      document.addEventListener('deviceready', this.onDeviceReady.bind(this), false);
   },

   onDeviceReady: function() {
      this.receivedEvent();
   },
   receivedEvent: function() {
      function mediaSuccess() {
         console.log("Media成功")
      }

      function mediaError(err) {
         console.log("Media失败")
      }

      // 录制音频
      function recordAudio(){
         var src = "myrecord.mp3";
         var mediaRec = new Media(src, mediaSuccess, mediaError);
         // 启动录制音频
         mediaRec.startRecord();
         // 10秒后停止录制
         var recTime = 0;
         var recInterval = setInterval(function(){
            recTime = recTime + 1;
            if(recTime >= 10){
               clearInterval(recInterval);
               mediaRec.stopRecord();
               alert("录制完成")
            }
         },1000);
      }

      document.getElementById("startRecord").onclick = function(){
         recordAudio();
      }
   }

};

app.initialize();

 

运行:

点击“开始录制”按钮,开始录音


10秒后,自动停止


打开手机文件管理器的根目录,可以找到我们录制的音频文件








作者:michael_ouyang 发表于2017/7/24 12:12:37 原文链接
阅读:50 评论:0 查看评论

Android中获取应用程序(包)的信息----------PackageManager的使用一

$
0
0

如何获取Android系统中应用程序的信息,主要包括packagename,label,icon,占用大小等。具体分为两部分:

1:获取应用程序的packagename,label,icon等;
2:获取应用程序的占用大小,包括:缓存大小(cachsize),数据大小(datasize)。

开发Launcher时,会更多的使用,Android系统为我们提供了很多服务管理的类,如ActivityManager,PowerManager,AudioManager,PackagerManager,NotificationManager等等。
而PackageManager主要职责是管理应用程序包。通过它,可以获取应用程序信息.

AndroidManifest.xml文件节点说明:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.simple.launcher">
<application
    android:supportsRtl="true" >
    <activity
        android:name=".Launcher"
        android:launchMode="singleTask"
        android:screenOrientation="portrait"
        android:theme="@style/activity_Theme">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />

            <category android:name="android.intent.category.LAUNCHER" />
            <category android:name="android.intent.category.HOME" />
            <category android:name="android.intent.category.DEFAULT" />
            <category android:name="android.intent.category.MONKEY" />
        </intent-filter>
    </activity>
</application>
</manifest>

一: 相关类:

总包括的就是PackageInfo,提供了节点的基类的基本信息:a label,icon,meta-data。而它由子类继承然后调用相应的方法.

常用字段:

  public int icon           获得该资源图片在R文件中的值 (对应于android:icon属性)

  public int labelRes     获得该label在R文件中的值(对应于android:label属性)

  public String name   获得该节点的name值 (对应于android:name属性)

  public String packagename   获得该应用程序的包名 (对应于android:packagename属性)

常用方法:

   Drawable  loadIcon(PackageManager pm)               获得当前应用程序的图像

   CharSequence  loadLabel(PackageManager pm)     获得当前应用程序的label

ActivityInfo类继承PackageItemInfo

说明: 获得应用程序中或者 节点的信息 。我们可以通过它来获取我们设置的任 何属性,包括
theme 、launchMode、launchmode等

常用方法继承至PackageItemInfo类中的loadIcon()和loadLabel()

pplicationInfo类 继承自 PackageItemInfo

说明:获取一个特定引用程序中节点的信息。

字段说明:

 flags字段: FLAG_SYSTEM 系统应用程序

 FLAG_EXTERNAL_STORAGE 表示该应用安装在sdcard中

常用方法继承至PackageItemInfo类中的loadIcon()和loadLabel()

当然了还有很多基类:ServiceInfo ,ResolveInfo等等都是与上述类似的。

PackageInfo类

说明:手动获取AndroidManifest.xml文件的信息 。

常用字段:

public String packageName 包名

public ActivityInfo[] activities 所有节点信息

public ApplicationInfo applicationInfo 节点信息,只有一个

public ActivityInfo[] receivers 所有节点信息,多个

public ServiceInfo[] services 所有节点信息 ,多个

PackageManger 类

说明: 获得已安装的应用程序信息 。可以通过getPackageManager()方法获得。

常用方法:

public abstract PackageManager getPackageManager()

功能:获得一个PackageManger对象

public abstrac tDrawable getApplicationIcon(StringpackageName)

参数: packageName 包名

功能:返回给定包名的图标,否则返回null

public abstract ApplicationInfo getApplicationInfo(String packageName, int flags)

参数:packagename 包名
flags 该ApplicationInfo是此flags标记,通常可以直接赋予常数0即可

功能:返回该ApplicationInfo对象

public abstract List getInstalledApplications(int flags)

参数:flag为一般为GET_UNINSTALLED_PACKAGES,那么此时会返回所有ApplicationInfo。我们可以对ApplicationInfo的flags过滤,得到我们需要的。

功能:返回给定条件的所有PackageInfo

public abstract List getInstalledPackages(int flags)

参数如上

功能:返回给定条件的所有PackageInfo

public abstractResolveInfo resolveActivity(Intent intent, int flags)

参数: intent 查寻条件,Activity所配置的action和category

flags: MATCH_DEFAULT_ONLY :Category必须带有CATEGORY_DEFAULT的Activity,才匹配

GET_INTENT_FILTERS :匹配Intent条件即可

GET_RESOLVED_FILTER :匹配Intent条件即可

功能 :返回给定条件的ResolveInfo对象(本质上是Activity)

public abstract List queryIntentActivities(Intent intent, int flags)

参数同上

功能 :返回给定条件的所有ResolveInfo对象(本质上是Activity),集合对象

public abstract ResolveInfo resolveService(Intent intent, int flags)

参数同上

功能 :返回给定条件的ResolveInfo对象(本质上是Service)

public abstract List queryIntentServices(Intent intent, int flags)

参数同上

功能 :返回给定条件的所有ResolveInfo对象(本质上是Service),集合对象

二,案例讲解

1,通过queryIntentActivities()方法,查询Android系统的所有具备ACTION_MAIN和CATEGORY_LAUNCHER的Intent的应用程序,点击后,能启动该应用,说白了就是做一个类似Home程序的简易Launcher。

2:通过getInstalledApplications()方法获取应用,然后对其过滤,查找出我们需要的第三方应用,系统应用,安装在sdcard的应用。

xml布局就不用写了,我这里直接写出主要代码:

AppInfo.java:保存应用程序的Model类

public class AppInfo {
private String appLabel; //应用程序标签
private int appIcon;//应用程序所对应的图标
private Drawable appDrawableIcon;//应用程序图像
private String appPkg;//应用程序所对应的包名 
private String appClassName;//应用程序所对应的主界面名字
private int background;//应用程序所对应的背景

public AppInfo() {

}
 }

在MainActivity.java中编写工程逻辑,

public class Launcher extends FragmentActivity implements ViewPager.OnPageChangeListener {
private static final String TAG = "Launcher";

private static final int sFIRST_WORKSPACE = 0;
private static final int sDEFAULT_WORKSPACE = 1;
private static final int sTHIRD_WORKSPACE = 2;
private static final int sFOURTH_WORKSPACE = 3;

private static final int PERMISSION_REQUEST = 4;
private static final int PERMISSION_ALL_ALLOWED = 5;
private static final int PERMISSION_ALL_DENIED = 6;
private static final int MINI_SDK_RETURN_VALUE = -1;
public static boolean permissionFlag = false;
private int mCurrPosition = sDEFAULT_WORKSPACE;

public static boolean animAble = false;

AppSectionsPagerAdapter mAppSectionsPagerAdapter;

ViewPager mViewPager;
private Context mContext = null;

private int mfocusPosition = 0;
private View mRootView;

public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    requestWindowFeature(Window.FEATURE_NO_TITLE);
    initRootView();
    mContext = this;

    initViewPager();
}

 public void initViewPager() {
    mViewPager = (ViewPager) findViewById(R.id.pager);
    FragmentManager fm = getSupportFragmentManager();
    int mViewPagerId = mViewPager.getId();
    ArrayList<LauncherFragment> fragments = getFragments(mViewPagerId, fm);

    mAppSectionsPagerAdapter = new AppSectionsPagerAdapter(
            fm,fragments);
    mViewPager.setAdapter(mAppSectionsPagerAdapter);
    mViewPager.setCurrentItem(mCurrPosition);
    mViewPager.setOnPageChangeListener(this);
}

主LauncherFragment提供各分页视图:

public abstract class LauncherFragment<T extends AppInfo> extends Fragment
    implements AdapterView.OnItemLongClickListener,
    AdapterView.OnItemClickListener, AdapterView.OnItemSelectedListener {
private static final String TAG = "LauncherFragment";
private boolean mIsActived;
protected LauncherGridView mGridView;
protected Context mContext;
protected BaseGridAdapter mAdapter;
protected List<T> mDatas = new ArrayList<T>();

public List<T> getDatas() {
    return mDatas;
}

public void setDatas(List<T> datas) {
    this.mDatas = datas;
}

public BaseGridAdapter getAdapter() {
    return mAdapter;
}

public void setAdapter(BaseGridAdapter adapter) {
    this.mAdapter = adapter;
}

public LauncherGridView getGridView() {
    return mGridView;
}

public void setGridView(LauncherGridView gridView) {
    this.mGridView = gridView;
}

public LauncherFragment() {
    mIsActived = false;
}

public boolean isActived() {
    return mIsActived;
}

public void setActived(boolean actived) {
    mIsActived = actived;
}

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    mContext = getActivity();
    mDatas = loadData();
    if (mDatas == null) {
        mDatas = new ArrayList<T>();
    }
}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    View root = inflater.inflate(R.layout.fragment_pager, container, false);
    initGridView(root);
    registerContentObservers();
    return root;
}

protected void initGridView(View root) {
    mGridView = (LauncherGridView) root.findViewById(R.id.default_grid_view);
    mGridView.setSelector(mContext.getResources().getDrawable(R.color.transparent));

    // View click Listener
    mGridView.setOnItemClickListener(this);
    // view has focus Listener
    mGridView.setOnItemSelectedListener(this);
}

@Override
public void onDestroyView() {
    super.onDestroyView();
    unregisterContentObservers();
}

@Override
public void onDestroy() {
    super.onDestroy();
    if (mDatas != null) {
        mDatas.clear();
        mDatas = null;
    }
}

@Override
public void onNothingSelected(AdapterView<?> parent) {
}

protected Intent intentForPosition(int position) {
    T info = getData(position);
    Intent intent = new Intent();
    if (info != null) {
        intent.setClassName(info.getAppPkg(), info.getAppClassName());
    }
    AppIntentUtil.intentSetFlag(intent);
    return intent;
}

public T getData(int position) {
    T data = mDatas != null && position > -1 && position < mDatas.size() ? mDatas.get(position) : null;
    return data;
}
}

以上主要是我编写Launcher时所用的主逻辑,Launcher开发和我们开发Apk其实是一样的.

作者:Google_huchun 发表于2017/7/24 12:43:15 原文链接
阅读:95 评论:0 查看评论

Android 实现通过url加载PDF

$
0
0

前两天用到PDF加载功能,需求是从url加载,本以为很简单的事,只需一个webview就解决了,没想到webview不支持,网上找了一些解决方案都不太理想,于是想自己封装一个。

开源的库基本没有支持url加载的(或者我没找到),我的实现思路是先把文件下载下来,再从已加载本地file的形式加载出来,开源库选择

/AndroidPdfViewer

核心代码

   public void loadFromUrl(){
            final String SDPath = Environment.getExternalStorageDirectory().getAbsolutePath()+"/PDFViewCache/";
            int index = fileUrl.lastIndexOf("/");
            String fileName = fileUrl.substring(index);
            final File file = new File(SDPath, fileName);
            if(file.exists()){
                //文件存在
                if(onFileDownloadCompleteListener!=null){
                    onFileDownloadCompleteListener.onDownloadComplete(file);
                }
                PDFView.this.fromFile(file);
                load();
            }else{
                DownloadUtil.get().download(fileUrl, SDPath, new DownloadUtil.OnDownloadListener() {
                    @Override
                    public void onDownloadSuccess(File file) {
                        if(onFileDownloadCompleteListener!=null){
                            onFileDownloadCompleteListener.onDownloadComplete(file);
                        }
                        PDFView.this.fromFile(file);
                        load();
                    }

                    @Override
                    public void onDownloading(int progress) {

                    }

                    @Override
                    public void onDownloadFailed() {

                    }
                });
            }
        }
先判断文件是否已经缓存了,如果缓存了则直接加载,如果没缓存就下载文件并显示

使用方法

Step 1. Add the JitPack repository to your build file

Add it in your root build.gradle at the end of repositories:

allprojects {
	repositories {
		...
		maven { url 'https://jitpack.io' }
	}
}

Step 2. Add the dependency

dependencies {
        compile 'com.github.shxdos:AndroidPdfViewer:2.7.0-beta.2'
}
pdfView.fromUrl("http://www.anweitong.com/upload/document/standard/national_standards/138793918364316200.pdf")
                        .enableSwipe(true) // allows to block changing pages using swipe
                        .defaultPage(0)
                        .onLoad(this) // called after document is loaded and starts to be rendered
                        .onPageChange(this)
                        .swipeHorizontal(false)
                        .enableAntialiasing(true)
                        .onFileDownload(this)
                        .loadFromUrl();
github地址https://github.com/shxdos/AndroidPdfViewer

源码下载 http://download.csdn.net/detail/shaohongxuan/9908649

作者:shaohongxuan 发表于2017/7/24 13:46:44 原文链接
阅读:13 评论:0 查看评论

Android 自定义感光器控件SolarProgressView,也可当做普通ProgressBar使用

$
0
0

Android 自定义感光器控件SolarProgressView,也可当做普通ProgressBar使用


本文出处: http://blog.csdn.net/qq_27512671/article/details/76020265
完整代码获取:https://github.com/miqt/SolarProgressView
实现效果:
实现效果


实现思路:

①光线强度数据的获取:Android光线传感器
②光线强度的UI展示:自定义SolarProgressView
③光线数据源 –> UI展示需要数据的转化: 数据梯度设置
④其他动画效果的实现:光线强度增加的过度动画

光线强度数据的获取:Android光线传感器

Android光线传感器是Android获取周围环境光的感光器元件,通过注册感光器的传感器监听,我们就可以通过传感器传过来的数值。

获得光线传感器实例:

manager = (SensorManager) getSystemService(SENSOR_SERVICE);
sensor = manager.getDefaultSensor(Sensor.TYPE_LIGHT);

注册监听器开始监听传感器数据

 private SensorEventListener listener = new SensorEventListener() {
        @Override
        public void onSensorChanged(SensorEvent event) {       
                        //取得数据
            Log.i("sensor_Data","\naccuracy : " + event.accuracy
            + "\ntimestamp : " + event.timestamp
            + "\nvalues : " + Arrays.toString(event.values));
        }

        @Override
        public void onAccuracyChanged(Sensor sensor, int accuracy) {

        }
    };
@Override
protected void onResume() {
    super.onResume();
    manager.registerListener(listener, sensor, SensorManager.SENSOR_DELAY_NORMAL);
}

在合适的时机取消注册,节省资源占用:

@Override
protected void onPause() {
    super.onPause();
    manager.unregisterListener(listener);
}

光线强度的UI展示:自定义SolarProgressView

在用来展示的数据都准备好了之后,我们就要考虑如何将这些被整理好的数据展示出来了,实际上我们使用一个ProgressBar来展示即可,但考虑到展示的美观性和光线强弱变化的动画交互,我们决定自定义一个类似于太阳花的自定义控件来展示数据。并且这个自定义控件具有同ProgressBar类似的属性,比如max(最大值)、progress(当前值)等等。
有了明确的想法之后,开始开发:

package com.mqt.solarprogressview;

import android.animation.Animator;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.support.annotation.ColorInt;
import android.support.annotation.FloatRange;
import android.support.annotation.IntRange;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.AnimationSet;
import android.view.animation.DecelerateInterpolator;


public class SolarView extends View {

    private int mColor = Color.RED;
    private int mMax = 9;
    private int mPregress = 0;

    private float mLolarScale;
    private int mLightRadius;
    private ObjectAnimator mScaleAnim;
    private int mLightRadiusAnim;
    private ObjectAnimator mColorAnim;

    public SolarView(Context context) {
        super(context);
        init(null, 0);
    }

    public SolarView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(attrs, 0);
    }

    public SolarView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init(attrs, defStyle);
    }

    private void init(AttributeSet attrs, int defStyle) {
        // Load attributes
        final TypedArray a = getContext().obtainStyledAttributes(
                attrs, R.styleable.SolarView, defStyle, 0);

        mColor = a.getColor(R.styleable.SolarView_color, Color.RED);
        mMax = a.getInt(R.styleable.SolarView_max, 9);
        mPregress = a.getInt(R.styleable.SolarView_pregress, 0);
        mLolarScale = a.getFloat(R.styleable.SolarView_solarScale, 0.25F);
        mLightRadius = a.getDimensionPixelSize(R.styleable.SolarView_lightRadius, 10);
        mLightRadiusAnim = mLightRadius;
        a.recycle();
        initPaint();
        initAnim();
    }

    private void initAnim() {
        mScaleAnim = ObjectAnimator
                .ofInt(this, "mLightRadiusAnim", mLightRadius / 2, mLightRadius, mLightRadius * 2, mLightRadius);
        mScaleAnim.setDuration(1200);//设置动画时间
        mScaleAnim.setInterpolator(new DecelerateInterpolator());//设置动画插入器,减速
        mScaleAnim.setRepeatCount(0);//设置动画重复次数,这里-1代表无限
        mScaleAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                int value = (int) animation.getAnimatedValue();
                mLightRadiusAnim = value;
                postInvalidate();
            }
        });

        mColorAnim = ObjectAnimator
                .ofArgb(this, "mColor", mColor, Color.rgb(250, 128, 10), mColor);
        mColorAnim.setDuration(1200);//设置动画时间
        mColorAnim.setInterpolator(new DecelerateInterpolator());//设置动画插入器,减速
        mColorAnim.setRepeatCount(0);//设置动画重复次数,这里-1代表无限
        mColorAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                int value = (int) animation.getAnimatedValue();
                mColor = value;
                postInvalidate();
            }
        });
    }

    private int radumColor() {
        return Color.rgb((int) (Math.random() * 255), (int) (Math.random() * 255), (int) (Math.random() * 255));
    }

    Paint mPaint;

    private void initPaint() {
        mPaint = new Paint();
        mPaint.setColor(mColor);
        mPaint.setAntiAlias(true);
        mPaint.setStyle(Paint.Style.FILL);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //实心圆,直径为控件的最小宽高的一半
        int min = Math.min(getWidth(), getHeight());
        float r = min * mLolarScale;
        mPaint.setColor(mColor);
        canvas.drawCircle(getWidth() / 2, getHeight() / 2, r, mPaint);
        for (int i = 0; i < mMax && i < mPregress; i++) {
            canvas.save();
            canvas.rotate((360f / mMax) * i, getWidth() >> 1, getHeight() >> 1);
            if (i == mPregress - 1) {
                canvas.drawCircle(getWidth() >> 1, getHeight() >> 3, mLightRadiusAnim, mPaint);
            } else {
                canvas.drawCircle(getWidth() >> 1, getHeight() >> 3, mLightRadius, mPaint);
            }
            canvas.restore();
        }
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int minw = getPaddingLeft() + getPaddingRight() + getSuggestedMinimumWidth();
        int w = resolveSizeAndState(minw, widthMeasureSpec, 0);
        int minh = getPaddingLeft() + getPaddingRight() + getSuggestedMinimumWidth();
        int h = resolveSizeAndState(minh, heightMeasureSpec, 0);
        setMeasuredDimension(w, h);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
    }

    public int getColor() {
        return mColor;
    }

    public void setColor(@ColorInt int color) {
        this.mColor = color;
        postInvalidate();
    }

    public int getMax() {
        return mMax;
    }

    public void setMax(@IntRange(from = 0) int max) {
        this.mMax = max;
        postInvalidate();
    }

    public int getPregress() {
        return mPregress;
    }

    public void setPregress(@IntRange(from = 0) int pregress) {
        if (pregress > this.mPregress) {
            mColorAnim.cancel();
            mColorAnim.start();
            mScaleAnim.cancel();
            mScaleAnim.start();//启动动画
        }
        this.mPregress = pregress;

    }

    public float getLolarScale() {
        return mLolarScale;
    }

    public void setLolarScale(@FloatRange(from = 0, to = 1) float lolarScale) {
        this.mLolarScale = lolarScale;
        postInvalidate();
    }

    public int getLightRadius() {
        return mLightRadius;
    }

    public void setLightRadius(@IntRange(from = 0) int lightRadius) {
        this.mLightRadius = lightRadius;
        postInvalidate();
    }
}

自定义属性XML:

<resources>
    <declare-styleable name="SolarView">
        <attr name="max" format="integer" />
        <attr name="pregress" format="integer" />
        <attr name="color" format="color" />
        <attr name="solarScale" format="float" />
        <attr name="lightRadius" format="dimension" />
    </declare-styleable>
</resources>

光线数据源 –> UI展示需要数据的转化: 数据梯度设置

在我们获取到传感器给出的光照强度数据源后,我们无法直接使用,因为光线给出的数据与UI展示需要的数据不同,因此我们需要下转化,直接在传感器监听中添加代码即可:

private SensorEventListener listener = new SensorEventListener() {
        @Override
        public void onSensorChanged(SensorEvent event) {
            float intensity = event.values[2];
            int size = (int) ((intensity * sv_light.getMax() / 120));
            sv_light.setPregress(size);
            textView.setText(
                    "\naccuracy : " + event.accuracy
                            + "\ntimestamp : " + event.timestamp
                            + "\nvalues : " + Arrays.toString(event.values)
            );
        }

        @Override
        public void onAccuracyChanged(Sensor sensor, int accuracy) {

        }
    };

其他动画效果的实现:光线强度增加的过度动画

光线传感器数据获取完成了,用于UI展示的自定义控件也写好了,数据也匹配好了可以正常展示了,但我们可能还不会满足,我们可能还希望给这个自定义控件在传感器数据发生变化的时候,有一个动画出来。这个动画其实在上面粘贴的代码中已经有了,这里只是介绍一下给这个自定义view添加动画的思路:
我们知道自定义view的所有的视图都是在onDraw(Canvas canvas)方法中用Canvas画出来的,而化成什么样子又是其中的各种参数控制的,例如我们在自定义控件中画一个圆,我们只需要动态的改变这个圆的半径,就能达到这个圆的放大缩小的目的,当我们有规律的并且足够频繁改变这个值,就可以达到平滑的动态效果了。例如实现控件中控件随progress的变化颜色渐变:

  mColorAnim = ObjectAnimator
                .ofArgb(this, "mColor", mColor, Color.rgb(250, 128, 10), mColor);
        mColorAnim.setDuration(1200);//设置动画时间
        mColorAnim.setInterpolator(new DecelerateInterpolator());//设置动画插入器,减速
        mColorAnim.setRepeatCount(0);//设置动画重复次数,这里-1代表无限
        mColorAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                int value = (int) animation.getAnimatedValue();
                mColor = value;
                postInvalidate();//重新绘制控件
            }
        });

这样,我们的自定义感光器控件的“感光”、“数据展示”和一切其他的动画效果,就达成了,干杯!!

作者:qq_27512671 发表于2017/7/24 14:30:42 原文链接
阅读:10 评论:0 查看评论

RadioGroup 自动换行且保留点击事件

$
0
0

相信用过RadioGroup的同学都踩过很多坑,其中之一就是这个控件设计的不是很合理,不能设置里面的radiobutton的 排列方式(几行几列),导致我们开发的时候要调整里面的布局很是麻烦。

另外一个坑是 动态new 的时候选默认值的问题,这个在之前的一篇文章 RadioGroup中RadioButton默认选中问题  这个里面已经提到过了,就不再细说了。今天主要说说这个radiogroup怎么调整布局为自动换行的问题。

当我们自己写完RadioGroup 后,里面写好radiobutton,非常简单,一通复制粘贴,一大排就出来了,运行一下 ,非常棒,单选等功能都很好用。就像这样


<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.example.myradiogroup.MainActivity" >

   <RadioGroup
        android:id="@+id/rg"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:padding="0dp">

        <RadioButton
            android:id="@+id/rb_1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:checked="true"
            android:text="红色" />

        <RadioButton
            android:id="@+id/rb_2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="橙色" />

        <RadioButton
            android:id="@+id/rb_3"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="黄色" />

        <RadioButton
            android:id="@+id/rb_4"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="绿色" />

        <RadioButton
            android:id="@+id/rb_5"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="蓝色" />

        <RadioButton
            android:id="@+id/rb_6"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="靛色" />

        <RadioButton
            android:id="@+id/rb_7"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="紫色" />

        <RadioButton
            android:id="@+id/rb_8"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="黑色" />

        <RadioButton
            android:id="@+id/rb_9"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="灰色" />

        <RadioButton
            android:id="@+id/rb_10"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="白色" />

        <RadioButton
            android:id="@+id/rb_11"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="七彩色" />

        <RadioButton
            android:id="@+id/rb_12"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="粉色" />
    </RadioGroup>
</RelativeLayout>


对应的界面展示效果:



默认选中了第一个,然后 ,产品经理过来了,你这么放肯定是不行的,能不能改成4行3列的。


然后你心想,小儿科,去查了一下RadioGroup的属性,发现并没有原生支持的属性,可以设置内部RadioButton的几行几列的属性。

继续想一下 ,这还能难道我? 然后写了几个LinearLayout   把RadioButton套起来。


于是代码被你改成了这样

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.example.myradiogroup.MainActivity" >

    <RadioGroup
        android:id="@+id/rg"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:padding="0dp" >

        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:orientation="horizontal" >

            <RadioButton
                android:id="@+id/rb_1"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:checked="true"
                android:text="红色" />

            <RadioButton
                android:id="@+id/rb_2"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="橙色" />

            <RadioButton
                android:id="@+id/rb_3"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="黄色" />
        </LinearLayout>

        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:orientation="horizontal" >

            <RadioButton
                android:id="@+id/rb_4"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="绿色" />

            <RadioButton
                android:id="@+id/rb_5"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="蓝色" />

            <RadioButton
                android:id="@+id/rb_6"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="靛色" />
        </LinearLayout>

        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:orientation="horizontal" >

            <RadioButton
                android:id="@+id/rb_7"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="紫色" />

            <RadioButton
                android:id="@+id/rb_8"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="黑色" />

            <RadioButton
                android:id="@+id/rb_9"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="灰色" />
        </LinearLayout>

        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:orientation="horizontal" >

            <RadioButton
                android:id="@+id/rb_10"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="白色" />

            <RadioButton
                android:id="@+id/rb_11"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="七彩色" />

            <RadioButton
                android:id="@+id/rb_12"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="粉色" />
        </LinearLayout>
    </RadioGroup>

</RelativeLayout>

对应的界面展示效果





完美实现  6不6 ,嘴角浮起了轻蔑的微笑...


然后运行,点击选一下试试,woc,不对啊,这点击效果不对啊 ,发现原来默认选中的 没有取消选中啊,点击事件不对了啊,分分钟懵逼了...


所以其实原生的RadioGroup 存在的问题:


1. 如果不结合其他布局,例如LinearLayout, 则只能实现单行多个按钮组,或者单列多个按钮组。 
2. 如果结合其他布局, 虽然可以实现多行多列的RadioButton布局,但是,如果不通过一些互斥算法,也无法实现按钮组的单选操作。 


所以要对其进行改写:

package com.example.myradiogroup;

import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.widget.RadioGroup;

public class MyRadioGroup extends RadioGroup {
    private static final String TAG = "RadioGroupEx";

    public MyRadioGroup(Context context) {
        super(context);
    }

    public MyRadioGroup(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);

        //调用ViewGroup的方法,测量子view
        measureChildren(widthMeasureSpec, heightMeasureSpec);

        //最大的宽
        int maxWidth = 0;
        //累计的高
        int totalHeight = 0;

        //当前这一行的累计行宽
        int lineWidth = 0;
        //当前这行的最大行高
        int maxLineHeight = 0;
        //用于记录换行前的行宽和行高
        int oldHeight;
        int oldWidth;

        int count = getChildCount();
        //假设 widthMode和heightMode都是AT_MOST
        for (int i = 0; i < count; i++) {
            View child = getChildAt(i);
            MarginLayoutParams params = (MarginLayoutParams) child.getLayoutParams();
            //得到这一行的最高
            oldHeight = maxLineHeight;
            //当前最大宽度
            oldWidth = maxWidth;

            int deltaX = child.getMeasuredWidth() + params.leftMargin + params.rightMargin;
            if (lineWidth + deltaX + getPaddingLeft() + getPaddingRight() > widthSize) {//如果折行,height增加
                //和目前最大的宽度比较,得到最宽。不能加上当前的child的宽,所以用的是oldWidth
                maxWidth = Math.max(lineWidth, oldWidth);
                //重置宽度
                lineWidth = deltaX;
                //累加高度
                totalHeight += oldHeight;
                //重置行高,当前这个View,属于下一行,因此当前最大行高为这个child的高度加上margin
                maxLineHeight = child.getMeasuredHeight() + params.topMargin + params.bottomMargin;
                Log.v(TAG, "maxHeight:" + totalHeight + "---" + "maxWidth:" + maxWidth);

            } else {
                //不换行,累加宽度
                lineWidth += deltaX;
                //不换行,计算行最高
                int deltaY = child.getMeasuredHeight() + params.topMargin + params.bottomMargin;
                maxLineHeight = Math.max(maxLineHeight, deltaY);
            }
            if (i == count - 1) {
                //前面没有加上下一行的搞,如果是最后一行,还要再叠加上最后一行的最高的值
                totalHeight += maxLineHeight;
                //计算最后一行和前面的最宽的一行比较
                maxWidth = Math.max(lineWidth, oldWidth);
            }
        }

        //加上当前容器的padding值
        maxWidth += getPaddingLeft() + getPaddingRight();
        totalHeight += getPaddingTop() + getPaddingBottom();
        setMeasuredDimension(widthMode == MeasureSpec.EXACTLY ? widthSize : maxWidth,
                heightMode == MeasureSpec.EXACTLY ? heightSize : totalHeight);

    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int count = getChildCount();
        //pre为前面所有的child的相加后的位置
        int preLeft = getPaddingLeft();
        int preTop = getPaddingTop();
        //记录每一行的最高值
        int maxHeight = 0;
        for (int i = 0; i < count; i++) {
            View child = getChildAt(i);
            MarginLayoutParams params = (MarginLayoutParams) child.getLayoutParams();
            //r-l为当前容器的宽度。如果子view的累积宽度大于容器宽度,就换行。
            if (preLeft + params.leftMargin + child.getMeasuredWidth() + params.rightMargin + getPaddingRight() > (r - l)) {
                //重置
                preLeft = getPaddingLeft();
                //要选择child的height最大的作为设置
                preTop = preTop + maxHeight;
                maxHeight = getChildAt(i).getMeasuredHeight() + params.topMargin + params.bottomMargin;
            } else { //不换行,计算最大高度
                maxHeight = Math.max(maxHeight, child.getMeasuredHeight() + params.topMargin + params.bottomMargin);
            }
            //left坐标
            int left = preLeft + params.leftMargin;
            //top坐标
            int top = preTop + params.topMargin;
            int right = left + child.getMeasuredWidth();
            int bottom = top + child.getMeasuredHeight();
            //为子view布局
            child.layout(left, top, right, bottom);
            //计算布局结束后,preLeft的值
            preLeft += params.leftMargin + child.getMeasuredWidth() + params.rightMargin;
        }
    }
}



然后将布局文件RadioGroup改为 自定义的MyRadioGroup


<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.example.myradiogroup.MainActivity" >

    <com.example.myradiogroup.MyRadioGroup
        android:id="@+id/rg"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:padding="0dp" >

        <RadioButton
            android:id="@+id/rb_1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:checked="true"
            android:text="红色" />

        <RadioButton
            android:id="@+id/rb_2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="橙色" />

        <RadioButton
            android:id="@+id/rb_3"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="黄色" />

        <RadioButton
            android:id="@+id/rb_4"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="绿色" />

        <RadioButton
            android:id="@+id/rb_5"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="蓝色" />

        <RadioButton
            android:id="@+id/rb_6"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="靛色" />

        <RadioButton
            android:id="@+id/rb_7"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="紫色" />

        <RadioButton
            android:id="@+id/rb_8"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="黑色" />

        <RadioButton
            android:id="@+id/rb_9"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="灰色" />

        <RadioButton
            android:id="@+id/rb_10"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="白色" />

        <RadioButton
            android:id="@+id/rb_11"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="七彩色" />

        <RadioButton
            android:id="@+id/rb_12"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="粉色" />
    </com.example.myradiogroup.MyRadioGroup>

</RelativeLayout>

看下效果和 上面套用linearlayout的效果是一样的,而且不影响RadioGroup的点击事件。


但是这个仅仅是实现了自动换行的效果,没有真正实现可以设置 RadioGroup几行几列的效果。但是可以通过设置RadioButton的宽度就调这个每行的个数,怎么直接在自定义的RadioGroup直接写两个 设置行列的方法,也是没有搞定!如果大家有搞定的 或者有好的方法,可以分享一下!技术就是用来分享的!


DEMO下载


如果还有其他问题,可以加入我们的qq群讨论交流:
开发一群:415275066 开发二群:537532956




作者:shaoyezhangliwei 发表于2017/7/24 15:36:36 原文链接
阅读:2 评论:0 查看评论

React Native入门(六)之列表组件的使用(1)

$
0
0

前言

这篇文章来了解一下相关列表组件的使用,这些组件在展示数据的时候比较有用!在Android中有ScrollView,ListView等!那么对应的RN中也有类似的组件!

ScrollView

没错,在RN中也有ScrollView这个滚动视图组件,跟Android中的ScrollView一样,功能一样,用法呢也一样!
具体就是这样:

<ScrollView>
  <Text style={{fontSize: 20}}>我是可以滚动的!</Text>
  <Text style={{fontSize: 20}}>我是可以滚动的!</Text>
  <Text style={{fontSize: 20}}>我是可以滚动的!</Text>
  <Text style={{fontSize: 20}}>我是可以滚动的!</Text>
  <Text style={{fontSize: 20}}>我是可以滚动的!</Text>
  <Text style={{fontSize: 20}}>我是可以滚动的!</Text>
  <Text style={{fontSize: 20}}>我是可以滚动的!</Text>
  <Text style={{fontSize: 20}}>我是可以滚动的!</Text>
  <Text style={{fontSize: 20}}>我是可以滚动的!</Text>
  ...        
</ScrollView>

当然里边的组件可以是文本<Text>,也可以是其他的比如图片<Image>等!
<ScrollView>的用法非常简单,这里就不多说了!

长列表

对比Android中的ListView,RN中也有ListView这个组件,但是现在已经废弃!不再推荐使用了!需要了解的可以查看官方文档,这里就不再介绍了!

在RN v0.43出现版本两个新的列表组件取代了ListView,那就是FlatListSectionList,适用于展示长列表数据的组件。

FlatList

这个呢跟ListView基本上一毛一样,适于展示长列表数据,且元素个数可以增删。和ScrollView不同的是,FlatList并不立即渲染所有元素,而是优先渲染屏幕上可见的元素
它有两个比较重要的属性:

  • data:列表的数据源
  • renderItem:从数据源中逐个解析数据,然后返回一个设定好格式的组件来渲染

我们还是来对比ListView,data呢就不用说了吧,数据源,JS中就是一个Array数组,java中可以是数组,也可以是一个List。
renderItem,我的理解呢就相当于Android中的setAdapter()方法,我们拿一个适配器将数据设置在子item的布局中!
这样是不是很好理解了!
没有学过Android的同学,可以忽略setAdapter()方法,renderItem就是依次将数据源中的数据设置在子item的组件上面!这样依次就展示出了一个列表来!
举个例子:

class FlatListTest extends Component {
  render() {
    return (
      <View style={flatListStyles.container}>
        <FlatList
          data={[
            {key: '大护法'},
            {key: '绣春刀II:修罗战场'},
            {key: '神偷奶爸3'},
            {key: '神奇女侠'},
            {key: '摔跤吧,爸爸'},
            {key: '悟空传'},
            {key: '闪光少女'},
          ]}
          renderItem={({item}) => <Text style={flatListStyles.item}>{item.key}</Text>}
        />
      </View>
    );
  }
}
;

const flatListStyles = StyleSheet.create({
  container: {
    flex: 1,
    paddingTop: 22
  },
  item: {
    padding: 10,
    fontSize: 18,
    height: 44,
  },
})

AppRegistry.registerComponent('AwesomeProject', () => FlatListTest);

这里写图片描述
需要注意的点:
data接收的数组,需要写成{key:你的数据value}的形式,否则会有黄色的警告!
renderItem绑定数据的时候:renderItem={({item}) => 你的子item的组件},获取数据调用item.key

SectionList

SectionList就相当于分组带标题的FlatList,我们可以为具有同样特征的一类数据添加一个分组标题,最常见的比如,我们的联系人,按照拼音字母分组。

对于SectionList的简单使用呢,需要了解下边3个属性:

  • sections:这个属性同样接收的是一个数组,类似{title: 'L', data: [{key: '李四'}]},里边的title,表示分组标题的名字data,表示这个分组下的数据源data下的内容跟上边讲FlatList中设置data相同!
  • renderItem :这个属性也是绑定数据到子item组件上的,跟FlatList中renderItem相同。
  • renderSectionHeader:对比renderItem,这个属性是设置分组标题的,具体使用和renderItem类似!
    使用:renderSectionHeader={({section}) => 你要展示标题的组件},获取标题的内容要调用:section.title

看一个例子:

class SectionListTest extends Component {
  render() {
    return (
      <View style={styles.container}>
        <SectionList
          sections={[
            {title: 'L', data: [{key: '李四'}]},
            {title: 'W', data: [{key: '王五'}]},
            {title: 'Z', data: [{key: '赵六'},{key: '张三'}]},
          ]}
          renderItem={({item}) => <Text style={styles.item}>{item.key}</Text>}
          renderSectionHeader={({section}) => <Text style={styles.sectionHeader}>{section.title}</Text>}
        />
      </View>
    );
  }
}
;

const styles = StyleSheet.create({
  container: {
    flex: 1,
    paddingTop: 22
  },
  sectionHeader: {
    paddingTop: 2,
    paddingLeft: 10,
    paddingRight: 10,
    paddingBottom: 2,
    fontSize: 14,
    fontWeight: 'bold',
    backgroundColor: 'skyblue',
  },
  item: {
    padding: 10,
    fontSize: 18,
    height: 44,
  },
})

AppRegistry.registerComponent('AwesomeProject', () => SectionListTest);

这里写图片描述

另外一个需要注意的点:
在上边我们的例子中,不同的section使用渲染的都是同一个子组件,这里是<Text style={styles.item}>{item.key}</Text>,那么如果想要不同的section使用不同的子组件渲染,该怎么写呢?
这样写:

<SectionList
  sections={[ // 不同section渲染不同类型的子组件
    {data: [...], key: ..., renderItem: ...},
    {data: [...], key: ..., renderItem: ...},
    {data: [...], key: ..., renderItem: ...},
  ]}
/>

结语

本篇文章介绍了常用列表组件FlatList和SectionList的简单使用,关于列表组件涉及到的内容是比较多的,所以分两篇学习,在下一篇文章将学习为FlatList添加分割线,header,footer,空数据视图,下拉刷新,上拉加载等进阶的内容
好了,下篇见!

作者:aiynmimi 发表于2017/7/24 15:37:16 原文链接
阅读:14 评论:0 查看评论

网路游戏之物理模拟

$
0
0

笔者介绍:姜雪伟,IT公司技术合伙人,IT高级讲师,CSDN社区专家,特邀编辑,畅销书作者,已出版书籍:《手把手教你架构3D游戏引擎》电子工业出版社和《Unity3D实战核心技术详解》电子工业出版社等。

CSDN视频网址:http://edu.csdn.net/lecturer/144

目前市面上的网络游戏,竞技游戏逐渐成为主流,这样也就出现了大家经常热议的帧同步策略,本博客将做一个系列文章分别介绍:帧同步,快速插值,状态同步等。游戏中的物理模拟一直是同步中的难点,本篇博客先给读者介绍几个物理碰撞的案例,然后在后面的系列文章中给读者提供解决方案。

为了能让读者更好的理解同步策略,我们将花点时间探索一下网络的物理模拟,在这里我已经在开源物理引擎ODE(官方网站:http://www.ode.org)中设置了一个立方体的简单模拟, 玩家通过在其质心中施加力量来移动, 物理模拟采用这种线性运动,并且当立方体与地面碰撞时计算摩擦力,引起滚动和翻滚运动,这些运动我们在后面的系列文章中会用不同的同步策略去模拟它的物理表现。先看如下的效果图:

这就是为什么我选择一个立方体而不是一个球体我想要这种复杂的,不可预测的动作,因为刚体通常根据其形状方式移动。

当玩家与其他物理模拟对象进行交互时,特别是当这些物体推回并影响运动时,网络物理学将变得非常有趣。所以让我们添加一些更多的立方体进行模拟:

当玩家与立方体集进行交互时,会变成红色当这个立方体停止时,它变成灰色(不相互作用)。再给读者看一幅图:

你可以看到,互动不仅仅是直接的, 由玩家推送的红色立方体也会转动其他立方体,通过这种方式,互动可以覆盖所有受影响的立方体对象。

我也想要一个非常复杂的运动,在玩家和非玩家的立方体之间,它们成为一个系统:一组刚体通过约束连接在一起。为了实现这一点,

我认为如果玩家可以滚动并创建一个立方体的球,效果如下所示:

玩家的一定距离内的立方体具有向立方体中心施加力以使这些立方体保持一个物理模拟。

       如果在网络中模拟这些物理效果是有一定困难的,但是我们可以使用不同的网络同步方式模拟出上面的物理效果并且做到不同玩家的同步。我们将在后面的系列文章中给读者介绍网络物理模拟实现方案。。。。。。。。






作者:jxw167 发表于2017/7/24 11:41:20 原文链接
阅读:43 评论:0 查看评论

osmdroid 加载gpkg格式的离线底图

$
0
0

博客介绍了一系列的osmdroid开源地图相关的内容,包括地图基本绘制,导航,缩放,加载各种离线地图等等。接下来继续介绍加载离线的gpkg格式地图。gpkg的全称是geopackage。这篇博客也是在之前的基础上添加的测试代码。

geopackage的github官网:https://github.com/ngageoint/geopackage-android

本博客的代码:http://download.csdn.net/detail/qq_16064871/9908233



1,加载的图片效果




默认的路径放在根目录的osmdroid文件夹下面。

实现需要使用的相关代码:


2,调用方式


package com.osmdroid.sample;

import android.content.Context;
import android.content.DialogInterface;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Bundle;
import android.support.v4.app.FragmentManager;
import android.support.v7.app.AppCompatActivity;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;

import com.osmdroid.sample.geopakeage.CustomGeopackageUntil;
import com.osmdroid.sample.geopakeage.GeoPackageMapTileModuleProvider;
import com.osmdroid.sample.geopakeage.GeoPackageProvider;
import com.osmdroid.sample.geopakeage.GeopackageSample;
import com.osmdroid.sample.overlay.SampleSimpleFastPointOverlay;

import org.osmdroid.api.IGeoPoint;
import org.osmdroid.api.IMapView;
import org.osmdroid.config.Configuration;
import org.osmdroid.events.MapListener;
import org.osmdroid.events.ScrollEvent;
import org.osmdroid.events.ZoomEvent;
import org.osmdroid.tileprovider.tilesource.TileSourceFactory;
import org.osmdroid.tileprovider.tilesource.XYTileSource;
import org.osmdroid.tileprovider.util.StorageUtils;
import org.osmdroid.util.GeoPoint;
import org.osmdroid.views.MapView;
import org.osmdroid.views.overlay.ScaleBarOverlay;
import org.osmdroid.views.overlay.compass.CompassOverlay;
import org.osmdroid.views.overlay.compass.InternalCompassOrientationProvider;
import org.osmdroid.views.overlay.gestures.RotationGestureOverlay;
import org.osmdroid.views.overlay.mylocation.GpsMyLocationProvider;
import org.osmdroid.views.overlay.mylocation.MyLocationNewOverlay;

import java.io.File;
import java.io.FileFilter;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * 测试加载geopackage
 */
public class GeopackageMapTestActivity extends AppCompatActivity implements View.OnClickListener  ,LocationListener{

    private MapView mapView;
    //地图旋转
    private RotationGestureOverlay mRotationGestureOverlay;
    //比例尺
    private ScaleBarOverlay mScaleBarOverlay;
    //指南针方向
    private CompassOverlay mCompassOverlay = null;
    //设置导航图标的位置
    private MyLocationNewOverlay mLocationOverlay;
    private LocationManager lm;
    private Location currentLocation = null;

    private com.osmdroid.sample.geopakeage.GeopackageSample GeopackageSample = null;

    TextView textViewCurrentLocation;
    GeoPackageProvider.TileSourceBounds tileSourceBounds;
    XYTileSource currentSource = null;
    GeoPackageProvider geoPackageProvider=null;
    public static final DecimalFormat df = new DecimalFormat("#.000000");
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_basic_test_geopackage);

        initView();
    }

    private void initView() {

        findViewById(R.id.button1).setOnClickListener(this);
        findViewById(R.id.button2).setOnClickListener(this);

        textViewCurrentLocation = (TextView) findViewById(R.id.textViewCurrentLocation);

        mapView = (MapView) findViewById(R.id.mymapview);
        mapView.setDrawingCacheEnabled(true);
        mapView.setMaxZoomLevel(20);
        mapView.setMinZoomLevel(6);
        mapView.getController().setZoom(12);
        mapView.setTileSource(TileSourceFactory.MAPNIK);
        mapView.setUseDataConnection(true);
        mapView.setMultiTouchControls(true);// 触控放大缩小
        //是否显示地图数据源
        mapView.getOverlayManager().getTilesOverlay().setEnabled(true);


        //地图自由旋转
        mRotationGestureOverlay = new RotationGestureOverlay(mapView);
        mRotationGestureOverlay.setEnabled(true);
        mapView.getOverlays().add(this.mRotationGestureOverlay);

        //比例尺配置
        final DisplayMetrics dm = getResources().getDisplayMetrics();
        mScaleBarOverlay = new ScaleBarOverlay(mapView);
        mScaleBarOverlay.setCentred(true);
        mScaleBarOverlay.setAlignBottom(true); //底部显示
        mScaleBarOverlay.setScaleBarOffset(dm.widthPixels / 5, 80);
        mapView.getOverlays().add(this.mScaleBarOverlay);

        //指南针方向
        mCompassOverlay = new CompassOverlay(this, new InternalCompassOrientationProvider(this),
                mapView);
        mCompassOverlay.enableCompass();
        mapView.getOverlays().add(this.mCompassOverlay);

        //设置导航图标
        this.mLocationOverlay = new MyLocationNewOverlay(new GpsMyLocationProvider(this),
                mapView);
        mapView.getOverlays().add(this.mLocationOverlay);
        mLocationOverlay.enableMyLocation();  //设置可视

    }

    @Override
    public void onLocationChanged(Location location) {
        currentLocation=location;
    }

    @Override
    public void onStatusChanged(String provider, int status, Bundle extras) {

    }

    @Override
    public void onProviderEnabled(String provider) {

    }

    @Override
    public void onProviderDisabled(String provider) {

    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.button1:
                if (currentLocation != null) {
                    GeoPoint myPosition = new GeoPoint(currentLocation.getLatitude(), currentLocation.getLongitude());
                    mapView.getController().animateTo(myPosition);
                }
                break;
            case R.id.button2:
                // TODO: 2017/6/19   Fragment形式的图层
//                FragmentManager fm = this.getSupportFragmentManager();
//                if (fm.findFragmentByTag("GeopackageSample") == null) {
//                    mapView.setTileSource(TileSourceFactory.DEFAULT_TILE_SOURCE);
//                    GeopackageSample = new GeopackageSample();
//                    fm.beginTransaction().add(R.id.samples_container, GeopackageSample, "SampleGridlines").commit();
//                }

                addOverlays();
                break;

        }
    }

    public void addOverlays() {
        //first let's up our map source, mapsforge needs you to explicitly specify which map files to load
        //this bit does some basic file system scanning
        Set<File> mapfiles = findMapFiles();
        //do a simple scan of local storage for .gpkg files.
        File[] maps = new File[mapfiles.size()];
        maps = mapfiles.toArray(maps);
        if (maps.length == 0) {
            //show a warning that no map files were found
            android.app.AlertDialog.Builder alertDialogBuilder = new android.app.AlertDialog.Builder(this);

            // set title
            alertDialogBuilder.setTitle("No Geopackage files found");

            // set dialog message
            alertDialogBuilder
                    .setMessage("In order to render map tiles, you'll need to either create or obtain .gpkg files. See http://www.geopackage.org/ for more info. Place them in "
                            + Configuration.getInstance().getOsmdroidBasePath().getAbsolutePath())
                    .setCancelable(false)
                    .setPositiveButton("Yes", new DialogInterface.OnClickListener() {
                        public void onClick(DialogInterface dialog, int id) {

                        }
                    });


            // create alert dialog
            android.app.AlertDialog alertDialog = alertDialogBuilder.create();

            // show it
            alertDialog.show();

        } else {
            Toast.makeText(this, "Loaded " + maps.length + " map files", Toast.LENGTH_LONG).show();
            geoPackageProvider = new GeoPackageProvider(maps, this);
            mapView.setTileProvider(geoPackageProvider);
            List<GeoPackageMapTileModuleProvider.Container> tileSources = geoPackageProvider.geoPackageMapTileModuleProvider().getTileSources();

            //here we're keeping track of the current tile source so we can reference it later, primarly for
            //displaying the bounds of the tile set
            boolean sourceSet = false;
            for (int i = 0; i < tileSources.size(); i++) {
                //this is a list of geopackages, since we only support tile tables, pick the first one of those
                //ideally this should populate a spinner so that the user can select whatever tile source they want
                if (tileSources.get(i) != null && !tileSources.get(i).tiles.isEmpty()) {
                    currentSource = (XYTileSource) geoPackageProvider.getTileSource(tileSources.get(i).database,
                            tileSources.get(0).tiles.get(i)
                    );
                    mapView.setTileSource(currentSource);
                    sourceSet = true;
                    break;
                }
            }


            if (!sourceSet) {
                Toast.makeText(this, "No tile source is available, get your geopackages for 'tiles' tables", Toast.LENGTH_LONG).show();
            } else {
                Toast.makeText(this, "Tile source set to " + mapView.getTileProvider().getTileSource().name(), Toast.LENGTH_LONG).show();
                //this part will attempt to zoom to bounds of the selected tile source
                tileSourceBounds = geoPackageProvider.getTileSourceBounds();
                if (tileSourceBounds != null) {
                    mapView.zoomToBoundingBox(tileSourceBounds.bounds, true);
                    mapView.getController().setZoom(tileSourceBounds.minzoom);
                }
            }
        }

        mapView.setMapListener(new MapListener() {
            @Override
            public boolean onScroll(ScrollEvent event) {
                Log.i(IMapView.LOGTAG, System.currentTimeMillis() + " onScroll " + event.getX() + "," + event.getY());
                updateInfo();
                return true;
            }

            @Override
            public boolean onZoom(ZoomEvent event) {
                Log.i(IMapView.LOGTAG, System.currentTimeMillis() + " onZoom " + event.getZoomLevel());
                updateInfo();
                return true;
            }
        });
        updateInfo();
    }

    private void updateInfo() {
        StringBuilder sb = new StringBuilder();
        IGeoPoint mapCenter = mapView.getMapCenter();
        sb.append(df.format(mapCenter.getLatitude()) + "," +
                df.format(mapCenter.getLongitude())
                + ",zoom=" + mapView.getZoomLevel());

        if (currentSource != null) {
            sb.append("\n");
            sb.append(currentSource.name() + "," + currentSource.getBaseUrl());
        }

        if (tileSourceBounds != null) {
            sb.append("\n");
            sb.append(" minzoom=" + tileSourceBounds.minzoom);
            sb.append(" maxzoom=" + tileSourceBounds.maxzoom);
            sb.append(" bounds=" + df.format(tileSourceBounds.bounds.getLatNorth())
                    + "," + df.format(tileSourceBounds.bounds.getLonEast()) + "," +
                    df.format(tileSourceBounds.bounds.getLatSouth()) + "," +
                    df.format(tileSourceBounds.bounds.getLonWest()));

        }

        textViewCurrentLocation.setText(sb.toString());
    }

    /**
     * simple function to scan for paths that match /something/osmdroid/*.map to find database files
     *
     * @return
     */
    protected static Set<File> findMapFiles() {
        Set<File> maps = new HashSet<>();
        List<StorageUtils.StorageInfo> storageList = StorageUtils.getStorageList();
        for (int i = 0; i < storageList.size(); i++) {
            File f = new File(storageList.get(i).path + File.separator + "osmdroid" + File.separator);
            if (f.exists()) {
                maps.addAll(scan(f));
            }
        }
        return maps;
    }

    static private Collection<? extends File> scan(File f) {
        List<File> ret = new ArrayList<>();
        File[] files = f.listFiles(new FileFilter() {
            @Override
            public boolean accept(File pathname) {
                if (pathname.getName().toLowerCase().endsWith(".gpkg"))
                    return true;
                return false;
            }
        });
        if (files != null) {
            for (int i = 0; i < files.length; i++) {
                ret.add(files[i]);
            }
        }
        return ret;
    }

    @Override
    public void onDestroy(){
        super.onDestroy();
        this.currentSource=null;
        if (geoPackageProvider!=null)
            geoPackageProvider.detach();

    }

    @Override
    public void onPause() {
        super.onPause();
        try{
            lm.removeUpdates(this);
        }catch (Exception ex){}

        mCompassOverlay.disableCompass();
        mLocationOverlay.disableFollowLocation();
        mLocationOverlay.disableMyLocation();
        mScaleBarOverlay.enableScaleBar();
    }

    @Override
    public void onResume(){
        super.onResume();
        lm = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
        try{
            //this fails on AVD 19s, even with the appcompat check, says no provided named gps is available
            lm.requestLocationUpdates(LocationManager.GPS_PROVIDER,0l,0f,this);
        }catch (Exception ex){}

        try{
            lm.requestLocationUpdates(LocationManager.NETWORK_PROVIDER,0l,0f,this);
        }catch (Exception ex){}

        mLocationOverlay.enableFollowLocation();
        mLocationOverlay.enableMyLocation();
        mScaleBarOverlay.disableScaleBar();
    }
}

第二种调用方式,使用Fragment形式的图层


                // TODO: 2017/6/19   Fragment形式的图层
                FragmentManager fm = this.getSupportFragmentManager();
                if (fm.findFragmentByTag("GeopackageSample") == null) {
                    mapView.setTileSource(TileSourceFactory.DEFAULT_TILE_SOURCE);
                    GeopackageSample = new GeopackageSample();
                    fm.beginTransaction().add(R.id.samples_container, GeopackageSample, "SampleGridlines").commit();
                }

                addOverlays();

3,注意事项

如果编译出现google_play_services_version找不到,记得把资源文件夹下面values文件夹下面values拷贝进去。


这里加载是gpkg地图格式的瓦片数据,就是有生成位图那种。如果是gpkg数据库存储的数据库表,没有转化成titles是加载不出来的。













作者:qq_16064871 发表于2017/7/24 19:45:58 原文链接
阅读:31 评论:0 查看评论
Viewing all 5930 articles
Browse latest View live


<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>