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

RecyclerView 添加head view头部 和foot view尾部、设置水平horizontal时左侧布局和右侧布局

$
0
0

折腾一下午,遇到若干问题:如,设置水平HORIZONTAL时,占用多个跨度(类似单元格的合并)

终于都好用了。。。

简陋图发6张。
LinearLayoutManager.VERTICAL
这里写图片描述
LinearLayoutManager.HORIZONTAL
这里写图片描述
GridLayoutManager.VERTICAL
这里写图片描述
GridLayoutManager.HORIZONTAL
这里写图片描述
StaggeredGridLayoutManager.VERTICAL
这里写图片描述
StaggeredGridLayoutManager.HORIZONTAL
这里写图片描述

其中一个尾部
这里写图片描述

demo结构
这里写图片描述

MainActivity

package com.louisgeek.louisrecyclerviewtest;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.DefaultItemAnimator;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.StaggeredGridLayoutManager;
import android.view.LayoutInflater;
import android.view.View;

import java.util.ArrayList;
import java.util.List;

public class MainActivity extends AppCompatActivity {

    private int list_Header_LayoutID=R.layout.list_header_layout;
    private int list_Footer_LayoutID=R.layout.list_footer_layout;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        RecyclerView idrv = (RecyclerView) findViewById(R.id.id_rv);

        idrv.setLayoutManager(new LinearLayoutManager(this));
       /*idrv.setLayoutManager(new LinearLayoutManager(this,LinearLayoutManager.HORIZONTAL,false));
         idrv.setLayoutManager(new GridLayoutManager(this,3));
        idrv.setLayoutManager(new GridLayoutManager(this,3,GridLayoutManager.HORIZONTAL,false));
        idrv.setLayoutManager(new StaggeredGridLayoutManager(4, StaggeredGridLayoutManager.VERTICAL));
       idrv.setLayoutManager(new StaggeredGridLayoutManager(4, StaggeredGridLayoutManager.HORIZONTAL));*/

        MyRecyclerViewAdapter myAdapter= new MyRecyclerViewAdapter(getData());

        /**单独为HORIZONTAL 设置左侧的布局*/
        RecyclerView.LayoutManager layoutManager=idrv.getLayoutManager();
        if (layoutManager instanceof StaggeredGridLayoutManager){
            StaggeredGridLayoutManager staggeredGridLayoutManager= (StaggeredGridLayoutManager) layoutManager;
                list_Header_LayoutID=staggeredGridLayoutManager.getOrientation()==StaggeredGridLayoutManager.HORIZONTAL
                        ?R.layout.list_header_layout_4_horizontal :list_Header_LayoutID;
              list_Footer_LayoutID=staggeredGridLayoutManager.getOrientation()==StaggeredGridLayoutManager.HORIZONTAL
                      ?R.layout.list_footer_layout_4_horizontal :list_Footer_LayoutID;
        }else if (layoutManager instanceof GridLayoutManager){
            GridLayoutManager gridLayoutManager= (GridLayoutManager) layoutManager;
            list_Header_LayoutID=gridLayoutManager.getOrientation()==GridLayoutManager.HORIZONTAL
                    ?R.layout.list_header_layout_4_horizontal :list_Header_LayoutID;
            list_Footer_LayoutID=gridLayoutManager.getOrientation()==GridLayoutManager.HORIZONTAL
                    ?R.layout.list_footer_layout_4_horizontal :list_Footer_LayoutID;
        }else if (layoutManager instanceof LinearLayoutManager){
            LinearLayoutManager linearLayoutManager= (LinearLayoutManager) layoutManager;
            list_Header_LayoutID=linearLayoutManager.getOrientation()==LinearLayoutManager.HORIZONTAL
                    ?R.layout.list_header_layout_4_horizontal :list_Header_LayoutID;
            list_Footer_LayoutID=linearLayoutManager.getOrientation()==LinearLayoutManager.HORIZONTAL
                    ?R.layout.list_footer_layout_4_horizontal :list_Footer_LayoutID;
        }
        /**注意  root view 为recycleview*/
        View headView = LayoutInflater.from(this).inflate(list_Header_LayoutID,idrv,false);
        View footView = LayoutInflater.from(this).inflate(list_Footer_LayoutID,idrv,false);
         myAdapter.setHeaderView(headView);
        myAdapter.setFooterView(footView);
        idrv.setHasFixedSize(true);
        idrv.setItemAnimator(new DefaultItemAnimator());
        idrv.setAdapter(myAdapter);
    }


    public List<String> getData(){
        List<String> dataList=new ArrayList<>();
        for (int i = 0; i <10; i++) {
            dataList.add("str"+i);
        }
        return  dataList;
    }


}

activity_main.xml就是一个RelativeLayout加一个RecyclerView

<?xml version="1.0" encoding="utf-8"?>
<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"
    tools:context="com.louisgeek.louisrecyclerviewtest.MainActivity">
    <android.support.v7.widget.RecyclerView
        android:id="@+id/id_rv"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>
</RelativeLayout>

垂直的

list_header_layout.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:orientation="vertical"
              android:padding="10dp"
              android:layout_margin="5dp"
              android:background="@color/colorAccent"
              android:gravity="center_horizontal"
              android:layout_width="match_parent"
              android:layout_height="wrap_content">
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="head str"
        android:padding="10dp"
        />

</LinearLayout>

list_footer_layout.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:orientation="vertical"
              android:padding="10dp"
              android:layout_margin="5dp"
              android:background="@color/colorPrimary"
              android:gravity="center_horizontal"
              android:layout_width="match_parent"
              android:layout_height="wrap_content">
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="foot view"
        android:padding="10dp"
        />

</LinearLayout>

list_item.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:orientation="vertical"
              android:padding="10dp"
              android:layout_margin="5dp"
              android:background="@android:color/darker_gray"
              android:gravity="center_horizontal"
              android:layout_width="match_parent"
              android:layout_height="wrap_content">
    <TextView
        android:id="@+id/id_tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="normal str"
        android:padding="10dp"
        />
</LinearLayout>

水平的。。。

list_header_layout_4_horizontal.xml

<?xml version="1.0" encoding="utf-8"?>
<!--android:layout_width="wrap_content"-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:orientation="vertical"
              android:padding="10dp"
              android:layout_margin="5dp"
              android:background="@color/colorAccent"
              android:gravity="center_vertical"
              android:layout_width="wrap_content"
              android:layout_height="match_parent">
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="left str"
        android:padding="10dp"
        />

</LinearLayout>

list_footer_layout_4_horizontal.xml

<?xml version="1.0" encoding="utf-8"?>
<!--android:layout_width="wrap_content"-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:orientation="vertical"
              android:padding="10dp"
              android:layout_margin="5dp"
              android:background="@color/colorPrimary"
              android:gravity="center_vertical"
              android:layout_width="wrap_content"
              android:layout_height="match_parent">
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="right view"
        android:padding="10dp"
        />

</LinearLayout>

list_item_4_horizontal.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:orientation="vertical"
              android:padding="10dp"
              android:layout_margin="5dp"
              android:background="@android:color/darker_gray"
              android:gravity="center_horizontal"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content">
    <TextView
        android:id="@+id/id_tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="normal str"
        android:padding="10dp"
        />
</LinearLayout>
作者:RichieZhu 发表于2016/9/11 21:27:52 原文链接
阅读:76 评论:0 查看评论

iOS10适配

$
0
0

一.工程改动

1. 工程配置

直接使用Xcode8Beta打开工程后,你会发现你的provisonfile配置出了问题,这是由于在Xcode8中对工程配置有了一些小的改动。  

在Xcode8工程配置的general选项卡中,使用更详细signing选项替换了原有的team选项。

选中:project -> target -> general

Xcode8之前team选项


Xcode8之前provisionfile选项

Xcode8之后变更为signing选项


signing选项展开

2. swift版本

假如你Xcode7的工程中使用了swift,那么你使用Xcode8一打开工程就会出现下面的选项,选择convert:

选convert后,会让你选择swift的版本。  
如果你选择了3.0版本的swift选项,那等待convert结束后,一般依然有一部分需要你做手动适配。  
假如你暂时不想花费时间做swift版本的适配,你可以选择swift2.3版本,convert并不会转换任何内容,你的项目也可以像以往在Xcode7中那样正常运行。
此时我们选择swift2.3:

选择swift版本后,选择你的工程target:

稍等片刻:

选择update,结束收工:

二、应用适配

1. 权限配置

随着人们对隐私保护的注重,似乎苹果的每一代产品都有相应的体现——更为严格的设备和数据访问权限控制。  
在iOS10中,如果你的App想要访问用户的相机、相册、麦克风、通讯录等等权限,都需要进行相关的配置,不然会直接crash。

你需要在info.plist中添加你App需要的一些设备权限。

首先找到info,可以直接在这里添加键值对:

也可以找到info.plist文件,然后使用SourceCode方式打开:


一些常用的权限配置选项:

```
// 相机
<key>NSCameraUsageDescription</key>
<string>App需要您的同意,才能访问相册</string>

// 相册
<key>NSPhotoLibraryUsageDescription</key>
<string>App需要您的同意,才能访问相机</string>

// 麦克风:
<key>NSMicrophoneUsageDescription</key>
<string>App需要您的同意,才能访问麦克风</string>

// 通信录
<key>NSContactsUsageDescription</key>
<string>App需要您的同意,才能访问通信录</string>
```

其它权限配置选项:

```
// 位置
<key>NSLocationUsageDescription</key> 
<string>App需要您的同意,才能访问位置</string> 

// 在使用期间访问位置
<key>NSLocationWhenInUseUsageDescription</key> 
<string>App需要您的同意,才能在使用期间访问位置</string> 

// 始终访问位置
<key>NSLocationAlwaysUsageDescription</key> 
<string>App需要您的同意,才能始终访问位置</string> 

// 日历
<key>NSCalendarsUsageDescription</key> 
<string>App需要您的同意,才能访问日历</string> 

// 提醒事项
<key>NSRemindersUsageDescription</key> 
<string>App需要您的同意,才能访问提醒事项</string> 

// 运动与健身
<key>NSMotionUsageDescription</key>
<string>App需要您的同意,才能访问运动与健身</string> 

// 健康更新
<key>NSHealthUpdateUsageDescription</key> 
<string>App需要您的同意,才能访问健康更新 </string> 

// 健康分享
<key>NSHealthShareUsageDescription</key> 
<string>App需要您的同意,才能访问健康分享</string> 

// 蓝牙
<key>NSBluetoothPeripheralUsageDescription</key> 
<string>App需要您的同意,才能访问蓝牙</string> 

// 媒体资料库
<key>NSAppleMusicUsageDescription</key> 
<string>App需要您的同意,才能访问媒体资料库</string>
```

2. 字体改变

iOS10的字体发生了变化,这导致一些文字在原有的宽高约束下可能会出现显示不完整或者留白的情况,需要逐一检查。

参考

IOS 10 适配系列2 IOS10 适配汇总:ATS、隐私数据、UserNotifications、UICollectionView汇总

作者:andanlan 发表于2016/9/11 21:56:10 原文链接
阅读:76 评论:0 查看评论

Android Multimedia框架总结(八)Stagefright框架之AwesomePlayer及数据解析器

$
0
0

转载请把头部出处链接和尾部二维码一起转载,本文出自逆流的鱼:http://blog.csdn.net/hejjunlin/article/details/52503057

前言:前面一篇分析了mediaplayerservice及MediaPlayer中的CS模型,但是对于如何能把数据解析出来,渲染到最终的SurfaceView上显示,并且播放起来,我们依然还不得而知,从今天开始,就开始介绍多媒体框架中数据解析->解码->渲染输出过程,也就是后面几篇都要介绍的stagefright框架。
先看下今天的Agenda:

  • Stagefright的整体结构中角色
  • Stagefright在playback中角色
  • Stagefright初识
  • StagefrightPlayer及AwesomePlayer初识
  • MediaExtractor数据解析流程

Stagefright的整体结构中角色

这里写图片描述

Stagefright在playback中角色

这里写图片描述

Stagefright初识

前面一篇中,分析到mediaplayerservice会调到Stagefright中,进行编码解码操作
在libsstagefright中,预设的多媒体解码是openCore,由于其过于庞大和复杂,需要成本较高,开始引进了另一个框架,也就是stagefright框架,以后默认情况android选择stagefright,但是并没有完全抛弃opencore,做了一个OMX层,仅仅是对 opencore的omx-component部分做了引用。stagefright是和opencore是并列的。Stagefright在 Android中是以shared library的形式存在(libstagefright.so),其中的module – AwesomePlayer可用来播放video/audio。 AwesomePlayer提供许多API,可以让上层的应用程序(Java/JNI)来调用。

先看下源头,从mediaplayerservice到Stagefright:

这里写图片描述

接着进入MediaPlayerFactory中的createPlayer方法中:

这里写图片描述

这里写图片描述

本文出自逆流的鱼:http://blog.csdn.net/hejjunlin/article/details/52503057

StagefrightPlayer及AwesomePlayer初识

开始进入StagefrightPlayer的构造函数

这里写图片描述

StageFrightPlayer中引用了AwesomePlayer,进而进行相关操作,
从上面代码可以看出,接着分析下StagefrightPlayer,如下代码:

这里写图片描述

StagefrightPlayer继承了MediaPlayerInterface接口,
同样以setDataSource为例,通常我们负责的模块或调用别人的模块时,在模块和模块之间,相关于一个黑盒一样。里面什么流程我们并不清楚,call后,返回对应的状态或数据,仅此而已。
AwesomePlayer,就像是StagefrightPlayer中一个小盒子一样。
当mediaplayerservice中setDataSource传下来之后,进入如下方法:

这里写图片描述

以上代码总结为:所以执行步骤都会调用 mPlayer->setDataSource(xxxx),而在前面的分析,我们知道,这个mPlayer是被定义成AwesomePlayer,因为文件的setDataSoure及uri方式的setData仅在实现有些区别,接着向下看:

这里写图片描述

以上代码总结为:当setDataSource好后,返回上层OK状态,

再看下文件类型的setDataSource,如下:

这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述

以上代码总结为:匹配不同的文件类型,并用数据解析器处理,Mime表示该资源的媒体类型,当出现以右边格式出现的时候,就能被播放器识别,如经常手机中文件,单击时,就会弹出一个用哪个软件打开一样。列举一些常用的Mime类型的资源,如下:

这里写图片描述

本文出自逆流的鱼:http://blog.csdn.net/hejjunlin/article/details/52503057

MediaExtractor数据解析流程

接着,我们看下上面一直在用的MediaExtractor(数据解析器)

这里写图片描述
这里写图片描述

以上代码总结:

  • 根据对文件解析的不同格式创建一个Extractor解析器,并解析,创建好解析器后,回到AwesomePlayer::setDataSource_l()中,继续执行setDataSource_l(extractor),对新建的这个解析器做处理,其实质是显示音视频A/V的分离。
  • setVideoSource(extractor->getTrack(i));//设置视频源mVideoTrack ;
  • setAudioSource(extractor->getTrack(i));//设置音频源mAudioTrack;
  • mVideoTrack和mAudioTrack的做为创建的AwesomePlay的成员函数,其类型为MPEG4Source,继承了MediaSource。

这里写图片描述

以上过程就进行了A\V的分离,对音频和视频资源进行分开处理,其过程是mediaplayerservice->Stagefrightplayer—>Awesomeplayer——>MPEG4Extractor——>MPEG4Source.这几个过程。StageFright的Parse,Decode过程下节分析。

第一时间获得博客更新提醒,以及更多android干货,源码分析,欢迎关注我的微信公众号,扫一扫下方二维码或者长按识别二维码,即可关注。

这里写图片描述

如果你觉得好,随手点赞,也是对笔者的肯定,也可以分享此公众号给你更多的人,原创不易

作者:hejjunlin 发表于2016/9/11 22:21:05 原文链接
阅读:61 评论:0 查看评论

开发者头条(二):侧滑菜单

$
0
0

看了别人的博客,学着自己写出来,原文:http://blog.csdn.net/lowprofile_coding/article/details/51186965

实现侧滑功能,点击侧拉栏条目,改变内容页的数据。

知识点 + 逻辑

效果图:

这里写图片描述

怎么实现的侧栏功能:

使用DrawerLayout实现侧滑功能,根布局是DrawerLayout,先是内容页布局,再是侧栏栏布局,顺序不可乱,侧栏栏是根据layout_gravity属性来决定的。*

注意:要去掉app的title,不要使用ActionBar或ToolBar,因为如果使用了,那么侧拉页会在Title的下方,而不会像效果图中的那样,覆盖顶部标题。顶部的布局是自定义的。
DrawerLayout:实现侧拉页出现除了调用方法openDrawer(..)外,还可以在边缘拉出。

详见:Android中级:ActionBar + DrawerLayout实现侧滑菜单 Android5.0:Toolbar + DrawerLayout 实现侧滑效果

怎么实现侧拉页在左边

drawerLayout.openDrawer(Gravity.LEFT);

或者侧拉页布局中设置

android:layout_gravity="left"

侧栏页的item点击时+点击后颜色的变化,item背景色的变化是怎么实现的?

item文字的变化是通过selector实现的,但如果只是设置selector,那么先点击item1,item的文字变蓝色,再点击item2,item2的文字变蓝色,但是item1的文字还是蓝色,我们应该只是允许当前点击的item变色,那么还需要做什么呢?

我们在item的点击事件中先把所有的item的selector设为false,再把当前的设为true。

代码:

setAllFalse();
rl_home.setSelected(true);

setAllFalse():

rl_home.setSelected(false);
rl_gift.setSelected(false);
rl_share.setSelected(false);

怎么实现点击item来改变内容页的数据

1主内容页是标题 + FramLayout ,用Fragment来替换内容页FrameLayout,并对外提供一个方法设置数据
2 通过findViewById(id)找到item,设置点击监听事件,在监听中调用fragment提供的方法实现数据的设置。

// 用HomeFragment替换framelayout
fm = getSupportFragmentManager();
homeFragment = new HomeFragment();
fm.beginTransaction().add(R.id.layout_content, homeFragment, "HomeFragment").commit();
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
        Bundle savedInstanceState) {
    tv = new TextView(context);
    tv.setGravity(Gravity.CENTER);
    tv.setTextSize(30);
    tv.setText("这是首页");
    return tv;
}

public void setText(String text){
    tv.setText(text);
}

导航栏 + 状态栏的半透明 、 状态栏的背景色是怎么设置的

通过给窗体添加flag,代码如下,我照着博主的写了一遍,但是有 bug,不知道怎么回事,页面直接跑到状态栏的立体面下方(Z轴),而不是屏幕的下方。前2张图是我的,后2张是原创作者的。

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {//4.4 API:19
    //设置透明状态栏
    getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
    //设置状态栏颜色
    getWindow().setBackgroundDrawableResource(R.color.title_bg);
    //设置导航栏透明
    getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
}

图一:
这里写图片描述
图二:
这里写图片描述
图三:
这里写图片描述
图四:
这里写图片描述

DrawerLayout常用的方法:

方法 含义
openDrawer(int gravity) 打开侧拉页
closeDrawer() 关闭侧栏页

完整代码;

activity_main.xml

<android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/drawerLayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity" >

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical" >

        <include layout="@layout/layout_title" />

        <FrameLayout
            android:id="@+id/layout_content"
            android:layout_width="match_parent"
            android:layout_height="match_parent" >
        </FrameLayout>
    </LinearLayout>


    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_gravity="left"
        android:orientation="vertical" >

      <include layout="@layout/layout_menu"/>
    </LinearLayout>

</android.support.v4.widget.DrawerLayout>

layout_menu.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/text_white"
    android:orientation="vertical" >

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="100dp"
        android:background="@color/title_bg"
        android:gravity="center_vertical" >

        <ImageView
            android:id="@+id/iv_avatar"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentLeft="true"
            android:layout_marginLeft="20dp"
            android:src="@drawable/default_avatar" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="20dp"
            android:layout_toRightOf="@id/iv_avatar"
            android:gravity="center_vertical"
            android:text="开发者头条账号"
            android:textSize="20sp" />
    </RelativeLayout>

    <RelativeLayout
        android:id="@+id/rl_home"
        style="@style/item_menu_style"
        android:layout_marginTop="20dp" >

        <ImageView
            android:id="@+id/iv_home"
            style="@style/iv_item_menu_style"
            android:src="@drawable/nav_icon_home" />

        <TextView
            android:id="@+id/tv_home"
            style="@style/tv_item_menu_style"
            android:layout_toRightOf="@id/iv_home"
            android:text="首页" />
    </RelativeLayout>

    <RelativeLayout
        android:id="@+id/rl_gift"
        style="@style/item_menu_style" >

        <ImageView
            android:id="@+id/iv_gift"
            style="@style/iv_item_menu_style"
            android:src="@drawable/nav_icon_gift" />

        <TextView
            android:id="@+id/tv_gift"
            style="@style/tv_item_menu_style"
            android:layout_toRightOf="@id/iv_gift"
            android:text="礼物兑换" />
    </RelativeLayout>

    <include layout="@layout/line" />

    <RelativeLayout
        android:id="@+id/rl_share"
        style="@style/item_menu_style" >

        <ImageView
            android:id="@+id/iv_share"
            style="@style/iv_item_menu_style"
            android:src="@drawable/nav_icon_my_shares" />

        <TextView
            android:id="@+id/tv_share"
            style="@style/tv_item_menu_style"
            android:layout_toRightOf="@id/iv_share"
            android:text="我的分享" />
    </RelativeLayout>
    <RelativeLayout
        android:id="@+id/rl_subjects"
        style="@style/item_menu_style" >

        <ImageView
            android:id="@+id/iv_subjects"
            style="@style/iv_item_menu_style"
            android:src="@drawable/nav_icon_subscribed_subjects" />

        <TextView
            android:id="@+id/tv_subjects"
            style="@style/tv_item_menu_style"
            android:layout_toRightOf="@id/iv_subjects"
            android:text="我的订阅" />
    </RelativeLayout>
    <RelativeLayout
        android:id="@+id/rl_favorites"
        style="@style/item_menu_style" >

        <ImageView
            android:id="@+id/iv_favorites"
            style="@style/iv_item_menu_style"
            android:src="@drawable/nav_icon_favorite" />

        <TextView
            android:id="@+id/tv_favorites"
            style="@style/tv_item_menu_style"
            android:layout_toRightOf="@id/iv_favorites"
            android:text="我的收藏" />
    </RelativeLayout>

    <include layout="@layout/line" />

     <RelativeLayout
        android:id="@+id/rl_create_subject"
        style="@style/item_menu_style" >

        <ImageView
            android:id="@+id/iv_create_subject"
            style="@style/iv_item_menu_style"
            android:src="@drawable/nav_icon_create_subject" />

        <TextView
            android:id="@+id/tv_create_subject"
            style="@style/tv_item_menu_style"
            android:layout_toRightOf="@id/iv_create_subject"
            android:text="立即创建主体" />
    </RelativeLayout>
    <RelativeLayout
        android:id="@+id/rl_my_subjects"
        style="@style/item_menu_style" >

        <ImageView
            android:id="@+id/iv_my_subjects"
            style="@style/iv_item_menu_style"
            android:src="@drawable/nav_icon_created_subjects" />

        <TextView
            android:id="@+id/tv_my_subjects"
            style="@style/tv_item_menu_style"
            android:layout_toRightOf="@id/iv_my_subjects"
            android:text="我创建的主题" />
    </RelativeLayout>

    <include layout="@layout/line" />


         <RelativeLayout
        android:id="@+id/rl_feedback"
        style="@style/item_menu_style" >

        <ImageView
            android:id="@+id/iv_feedback"
            style="@style/iv_item_menu_style"
            android:src="@drawable/nav_icon_feedback" />

        <TextView
            android:id="@+id/tv_feedback"
            style="@style/tv_item_menu_style"
            android:layout_toRightOf="@id/iv_feedback"
            android:text="意见反馈" />
    </RelativeLayout>
    <RelativeLayout
        android:id="@+id/rl_cooperation"
        style="@style/item_menu_style" >

        <ImageView
            android:id="@+id/iv_cooperation"
            style="@style/iv_item_menu_style"
            android:src="@drawable/nav_icon_cooperation" />

           <TextView
            android:id="@+id/tv_cooperation"
            style="@style/tv_item_menu_style"
            android:layout_toRightOf="@id/iv_cooperation"
            android:text="合作申请" />
    </RelativeLayout> 

</LinearLayout>

layout_title.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="50dp"
    android:background="@color/title_bg"
    android:orientation="horizontal" >

    <ImageView
        android:id="@+id/iv_navigation"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_marginLeft="10dp"
        android:src="@drawable/ic_menu_white_24dp" />

    <TextView 
         android:layout_width="0dp"
         android:layout_weight="1"
        android:layout_height="match_parent"
        android:gravity="center"
        android:layout_marginLeft="10dp"
        android:textColor="@color/text_white"
        android:textSize="20sp"
        android:text="开发者头条"/>


    <ImageView
        android:id="@+id/iv_seacher"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_marginLeft="10dp"
        android:layout_marginRight="10dp"
        android:src="@drawable/ic_search_white_24dp" />
</LinearLayout>

MainActivity.java

package com.cqc.developerheadlinecqc.activity;

import com.cqc.developerheadlinecqc.R;
import com.cqc.developerheadlinecqc.R.layout;
import com.cqc.developerheadlinecqc.R.menu;
import com.cqc.developerheadlinecqc.fragment.HomeFragment;
import com.cqc.developerheadlinecqc.utils.ToastUtil;

import android.os.Build;
import android.os.Bundle;
import android.app.Activity;
import android.content.Context;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager;
import android.support.v4.widget.DrawerLayout;
import android.util.Log;
import android.view.Gravity;
import android.view.Menu;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.WindowManager;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast;

public class MainActivity extends FragmentActivity implements OnClickListener {

    private DrawerLayout drawerLayout;
    private ImageView iv_navigation;
    private ImageView iv_seacher;
    private RelativeLayout rl_home;
    private RelativeLayout rl_gift;
    private RelativeLayout rl_share;
    private TextView tv_home;
    private TextView tv_gift;
    private TextView tv_share;
    private HomeFragment homeFragment;
    private FragmentManager fm;
    private Context context;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        context = this;
        findViews();
        initViews();
    }

    private void initViews() {
        // 实现侧拉
        iv_navigation.setOnClickListener(this);
        iv_seacher.setOnClickListener(this);

        // 点击改变字体颜色和内容页
        rl_home.setOnClickListener(this);
        rl_gift.setOnClickListener(this);
        rl_share.setOnClickListener(this);

        // 用HomeFragment替换framelayout
        fm = getSupportFragmentManager();
        homeFragment = new HomeFragment();
        fm.beginTransaction()
                .add(R.id.layout_content, homeFragment, "HomeFragment")
                .commit();

        // 默认选中“首页”
        rl_home.setSelected(true);

//      setWindowStatus();//有bug
    }

    private void setWindowStatus() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {//4.4 API:19
            //设置透明状态栏
            getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
            //设置状态栏颜色
            getWindow().setBackgroundDrawableResource(R.color.title_bg);
            //设置导航栏透明
            getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
        }
    }

    private void findViews() {
        drawerLayout = (DrawerLayout) findViewById(R.id.drawerLayout);
        iv_navigation = (ImageView) findViewById(R.id.iv_navigation);
        iv_seacher = (ImageView) findViewById(R.id.iv_seacher);

        rl_home = (RelativeLayout) findViewById(R.id.rl_home);
        rl_gift = (RelativeLayout) findViewById(R.id.rl_gift);
        rl_share = (RelativeLayout) findViewById(R.id.rl_share);

        tv_home = (TextView) findViewById(R.id.tv_home);
        tv_gift = (TextView) findViewById(R.id.tv_gift);
        tv_share = (TextView) findViewById(R.id.tv_share);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
        case R.id.iv_navigation:
            drawerLayout.openDrawer(Gravity.LEFT);
            break;
        case R.id.iv_seacher:
            ToastUtil.showShortToast(context, "搜索");
            break;
        case R.id.rl_home:
            setAllFalse();
            rl_home.setSelected(true);
            drawerLayout.closeDrawers();
            homeFragment.setText("这是首页");
            break;
        case R.id.rl_gift:
            setAllFalse();
            rl_gift.setSelected(true);
            drawerLayout.closeDrawers();
            homeFragment.setText("这是礼物兑换");
            break;
        case R.id.rl_share:
            setAllFalse();
            rl_share.setSelected(true);
            drawerLayout.closeDrawers();
            homeFragment.setText("这是我的分享");
            break;

        default:
            break;
        }

    }

    private void setAllFalse() {
        rl_home.setSelected(false);
        rl_gift.setSelected(false);
        rl_share.setSelected(false);
    }

}
作者:ss1168805219 发表于2016/9/11 22:26:57 原文链接
阅读:45 评论:0 查看评论

理解Volley -- Android 学习之路

$
0
0

sky-mxc总结 转载注明:http://blog.csdn.net/mxiaochao?viewmode=contents

  • 介绍
  • 优点
  • 工作原理
  • 请求类型
  • 使用步骤
  • 取消请求
  • 网络图片加载的三种方式
  • 代码实例

介绍

Android中的网络请求一般就是两种 HttpURLConnection 和HttpClient,不论是哪一种在使用的时候都是经过一系列的封装 很繁琐有没有,而Google在2013年推出的Volley网络请求框架 ,使网络请求更加的快捷,方便,只需创建队列,创建请求,将请求放入队列就可以了,volley所有的网络请求都是异步的,不需要再操作线程的问题,而且自带缓存,再也不担心OOM了 我们只需要关心逻辑代码就可以了,需要注意的是 Volley适用于频繁发送请求 但是数据量不大(小于3M)的情况 。

优点

  • 普通数据,json,图片的异步加载
  • 网络请求优先级处理
  • 硬盘缓存(普通数据,图片,json)
  • 与activity的生命周期联动 activity死了他就取消了

缺点

不适合数据量较大的网络操作

工作原理

先来看一张图
工作原理
在执行RequestQueue的add() 时 volley会开启一个缓存处理线程(cacheDispatcher)和一个网络调度线程池。
当request被添加到queue(队列)时,缓存处理线程会先在缓存中查看是否有缓存,如果有缓存就将缓存结果发送到主线程,没有缓存的话,就将request放到queue中执行请求
得到请求结果后将数据发送到主线程解析并将结果写入缓存,当然我们可以设置是否可以缓存
绿色的是 主线程,黄色的是缓存处理线程 ,橙色的是 网络请求线程

请求类型

  • StringRequest 返回字符串
  • JsonRequest 返回json对象
  • ImageRequest 返回Bitmap

使用步骤

  • 创建队列RquesetQueue
  • 创建请求
  • 将请求放入队列

取消请求

取消一个请求只需调用这个请求对象的cancel方法即可。请求取消后 ,响应将不会被调用(只是响应监听哦)。
如果我们有好多请求需要取消,那就得跟踪很多请求 ,岂不是很费精力,费内存吗,还好有另一种办法
volley中的每个请求都可以设置一个 tag (标签) 我们可以通过这个tag来取消一个或多个对应的请求 ,
调用queue的cancelAll(Tag);方法 就可以取消对应的请求了 ,(多个请求是可以设置相同的标签的)

示例代码

mLoginRequest.cancel();//取消单个请求
//为Request设置tag
 mLoginRequest.setTag("login");
 mNewsRequest.setTag("news");
 mMsgListRequest.setTag("msg");
 mMsgRequest.setTag("msg");
//添加到队列中
 mQueue.add(mLoginRequest);
 mQueue.add(mNewsRequest);
 mQueue.add(mMsgListRequest);
 mQueue.add(mMsgRequest);
    //取消队列中包含msg标签的请求
mQueue.cancelAll("msg");

使用注意:如果你要根据请求的响应去执行别的操作的话 就得慎重取消了,因为响应监听不会被调用

volley 加载图片的三种方式

  • NetworkImageView
  • ImageRequst
  • ImageLoader
    - ImageLoader 用到了缓存 ,对于LRU缓存我也不是理解的很透彻就不再赘述了
    - 使用loader 需要一个 ImageCache的缓存 ,上述的工作原理在这里就比较明显了,
    - 每当loader去get一个地址 都会先去ImageCahce访问 是否有缓存 ,如果缓存为空才会去网络加载
    在 ImageCache的getBitmap()方法返回为null的话就执行网络加载如果不为null就不执行网络请求了

代码实例

RequestQueue请求队列的创建
一个应用最好只有一个RequestQueue 实例,所有的请求都是在RequestQueue中进行的,为了更有效的执行请求,一个app中最好只有一个实例,这个我也不是理解的很清楚,看的麻烦指教一下。
RequestQueue 需要依赖于上文来创建的,这里使用简单的单例模式

/**
 * Created by sky-mxc
 */
public class Http {
    private Context context;
    private RequestQueue queue;
    private static Http http;

    private Http(Context context) {
        this.context = context;
        this.queue = Volley.newRequestQueue(context);
    }

    public static Http getInstance(Context context) {
        if (http == null) {
            http = new Http(context);
        }
        return http;
    }

}

StringRequest

StringRequest 的默认请求方式 是GET 如果需要 POST方式,指定即可 ,volley 重载了StringRequest的创建

get

 /**
*String请求get方式(default)
*@paramurl地址
*@paramsuccessListener成功监听
*@paramerrorListener失败监听
*/
public void execStringRequest(Stringurl,Response.Listener<String>successListener,Response.ErrorListenererrorListener){
 StringRequestgetRequest = new StringRequest(url,successListener,errorListener);
queue.add(getRequest);
}

调用

    /**
     * Get方式加载数据
     */
    private void loadData(){
        String url ="http://toolsmi.com/starclass/lessons";
        Http.getInstance(this).execStringRequest(url, new Response.Listener<String>() {
            @Override
            public void onResponse(String s) {
            if (!TextUtils.isEmpty(s)){
                Result<List<Lession>> result = JSON.parseObject(s,new TypeReference<Result<List<Lession>>>(){});
                Toast.makeText(MainActivity.this,result.describe,Toast.LENGTH_SHORT).show();
                lessions.addAll(result.data);
                adapter.notifyDataSetChanged();
            }
            }
        }, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError volleyError) {
            Toast.makeText(MainActivity.this,"网络访问出错,请稍后重试",Toast.LENGTH_SHORT).show();
            }
        });
    }

post 关于参数的传递 可以重写 getParams()方法 将参数封装为Map集合 返回即可

/**
  *  String 请求 post方式
  * @param url 地址
  * @param params 参数 Map结构
  * @param successListener 成功监听
  * @param errorListener 失败监听
  */
 public void execStringRequestPost(String url,final Map<String,String> params, Response.Listener<String> successListener, Response.ErrorListener errorListener){
     StringRequest getRequest = new StringRequest(Request.Method.POST,url,successListener ,errorListener){
         @Override
         protected Map<String, String> getParams() throws AuthFailureError {

             return params;
         }
     };
     queue.add(getRequest);

 }

调用

/**
 * 检查版本更新使用Post方式
 */
private void checkVersion(){
    String url ="http://toolsmi.com/starclass/ver";
    Map<String,String> params = new HashMap<>();
    params.put("ver",BuildConfig.VERSION_CODE+"");
    Http.getInstance(this).execStringRequestPost(url,params, new Response.Listener<String>() {
        @Override
        public void onResponse(String str) {
            Log.e("Tag","======onResponse=============="+str);
            //将字符串解析为对象
            if (!TextUtils.isEmpty(str)) {
                Result<VersionInfo> result = JSON.parseObject(str, new TypeReference<Result<VersionInfo>>() {
                });
                if (result.state ==1){
                    new AlertDialog.Builder(MainActivity.this).setMessage("目前版本"+BuildConfig.VERSION_NAME+",检查到新版本"+result.data.getVersionName()+",是否更新?")
                            .setPositiveButton("立即更新",null)
                            .setNegativeButton("下次再议",null)
                            .show();
                }else{
                    Toast.makeText(MainActivity.this,"目前已经是最新版本",Toast.LENGTH_SHORT).show();
                }
            }
        }
    }, new Response.ErrorListener() {
        @Override
        public void onErrorResponse(VolleyError volleyError) {
            Toast.makeText(MainActivity.this,"网络出错,请稍后重试",Toast.LENGTH_SHORT).show();
        }
    });
}

JsonRequest

个人感觉Android原生的json操作有点繁琐,所以一般不使用,所以遇到需要解析json的请求都是用StringRequest 返回数据后使用 fastjson或Gson解析,看上面就知道了

ImageRequest

声明

/**
  * 执行图片的加载
  * @param url 地址
  * @param successListener 成功监听
  * @param maxWidth 最大宽度 px
  * @param maxHeight 最大高度 px
  * @param config 清晰度
  * @param errorListener 错误监听
  */
 public void execImageRequest(String url, Response.Listener<Bitmap> successListener, int maxWidth, int maxHeight, Bitmap.Config config, Response.ErrorListener errorListener){
     ImageRequest request = new ImageRequest(url,successListener,maxWidth,maxHeight, config,errorListener);
     queue.add(request);
}

调用

/**
 * ImageRequest加载图片
 */
private void loadImageRequest() {
    String url =etUrl.getText().toString();
Http.getInstance(this).execImageRequest(url,
                new Response.Listener<Bitmap>() {
            @Override
            public void onResponse(Bitmap bitmap) {
                if (bitmap!=null){
                    image0.setImageBitmap(bitmap);
                }
            }
        },
        (int) getResources().getDimension(R.dimen.image_w),
        (int)getResources().getDimension(R.dimen.image_h),
        Bitmap.Config.ARGB_8888,
        new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError volleyError) {
                Toast.makeText(MainActivity.this,"网络访问出错,请稍后重试",Toast.LENGTH_SHORT).show();
            }
        });
}

ImageLoader

通过这个看一下Volley的工作模式
在有缓存的情况下 就不会去执行网络请求 在ImageCache的gitBitmap()返回一个Bitmap 当然也就不会执行放入缓存的操作(putBitmap())

ImageLoader 在Http类中的定义

/**
 * 获取ImageLoad 实例
 * @param context 上下文
 * @param cache ImageCache 缓存
 * @return ImageLoader
 */
public static ImageLoader getLoader(Context context, ImageLoader.ImageCache cache){
    if (loader==null){
        loader = new ImageLoader(getInstance(context).queue,cache);
    }
    return loader;
}

调用

/**
 * 使用Loader加载图片
 */
private void loadImageLoader() {
    String url = etUrl.getText().toString();
    ImageLoader.ImageCache cache = new ImageLoader.ImageCache() {
        @Override
        public Bitmap getBitmap(String s) {
            Log.e("Tag","====getBitmap()从缓存加载====="+s);
            //((BitmapDrawable) (image0.getDrawable())).getBitmap()
            return null;
        }

        @Override
        public void putBitmap(String s, Bitmap bitmap) {
            Log.e("Tag","====putBitmap()放入缓存====="+s);
        }
    };
    ImageLoader loader = Http.getLoader(this,cache);
    loader.get(url, new ImageLoader.ImageListener(){

        @Override
        public void onErrorResponse(VolleyError volleyError) {
            Toast.makeText(MainActivity.this,"网络访问出错,请稍后重试",Toast.LENGTH_SHORT).show();
            Log.e("Tag",volleyError+"");
            image1.setImageResource(R.mipmap.jiantou);
        }

  /**
     * 这个方法会被调用两次 缓存处理一次 网络加载一次
     * @param imageContainer 网络请求信息
     * @param b 区分是缓存(true) 还是网络加载
     */
    @Override
    public void onResponse(ImageLoader.ImageContainer imageContainer, boolean b) {
        Log.e("Tag","===onResponse()======缓存加载:"+b+"====bitmap:"+imageContainer.getBitmap()+"=====url:"+imageContainer.getRequestUrl());

        Bitmap bmp = imageContainer.getBitmap();
        if (bmp!=null){//这里需要注意 如果没有使用缓存 从缓存中读取的Bitmap就是空的,
            image1.setImageBitmap(bmp);
        }

    }
},(int)getResources().getDimension(R.dimen.image_w),(int)getResources().getDimension(R.dimen.image_h));


}

在有缓存的情况下 就不会去执行网络请求 ;在ImageCache的gitBitmap()返回一个Bitmap 当然也就不会执行放入缓存的操作
在 有缓存的情况下的log日志:

E/Tag: ====getBitmap()从缓存加载=====#W100#H90http://www.codexiu.cn/static/blog/ad/4.jpg
 E/Tag: ===onResponse()======缓存加载:true====bitmap:android.graphics.Bitmap@631b631=====url:http://www.codexiu.cn/static/blog/ad/4.jpg

在无缓存的情况再去执行网络请求, gitBitmap() 返回null

无缓存下的log 日志:

  E/Tag: ====getBitmap()从缓存加载=====#W100#H90http://www.codexiu.cn/static/blog/ad/4.jpg
E/Tag: ===onResponse()======缓存加载:true====bitmap:null=====url:http://www.codexiu.cn/static/blog/ad/4.jpg
E/Tag: ====putBitmap()放入缓存=====#W100#H90http://www.codexiu.cn/static/blog/ad/4.jpg
E/Tag: ===onResponse()======缓存加载:false====bitmap:android.graphics.Bitmap@225e93b5=====url:http://www.codexiu.cn/static/blog/ad/4.jpg

即使没有缓存 Response 方法还是会被调用两次 缓存处理一次,,网络处理一次

NetworkImageView

这个控件继承自 ImageView 只不过增加了几个好用的方法 让我们用着更方便
布局定义

  <com.android.volley.toolbox.NetworkImageView
            android:id="@+id/image2"
           android:layout_width="@dimen/image_h"
        android:layout_height="@dimen/image_h" />

加载图片 ,加载时用到了ImageLoader 和ImageCache

 /**
   * 使用 NetWorkImageView
   */
  private void loadNetWorkImageView() {
      String url = etUrl.getText().toString();
      image2.setDefaultImageResId(R.mipmap.ic_launcher);//  网络加载前的占位图
      image2.setErrorImageResId(R.mipmap.jiantou);        //加载错误时提示图
      image2.setImageUrl(url,Http.getLoader(this, new ImageLoader.ImageCache() {
          @Override
          public Bitmap getBitmap(String s) {
              return null;
          }

          @Override
          public void putBitmap(String s, Bitmap bitmap) {

          }
      }));
 }

关于Volley的使用 我写了个Demo github地址:https://github.com/sky-mxc/AndroidDemo/tree/master/practicenetwork_volley

作者:MXiaoChao 发表于2016/9/11 22:58:28 原文链接
阅读:155 评论:0 查看评论

Chromium扩展(Extension)加载过程分析

$
0
0

       Chromium在启动的时候,会根据当前用户的Profile创建一个Extension Service。Extension Service在创建过程中,会加载当前已经安装的所有Extension,并且将它们注册在一个Extension Registry中。以后通过这个Extension Registry,就可以得到当前可用的Extension的信息了。本文接下来就分析Extension的加载过程。

老罗的新浪微博:http://weibo.com/shengyangluo,欢迎关注!

       Chromium的启动,指的实际上是Chromium的Browser进程的启动。关于Chromium的多进程架构,可以参考前面Chromium多进程架构简要介绍和学习计划这个系列的文章。Chromium的Browser进程在启动之后,会创建一系列的Sartup Task。每一个Startup Task都会负责初始化相应的模块。上述的Extension Service就是在一个Startup Task中创建的,如图1所示:


图1 Extension的加载过程

       Extension Service在创建的过程中,会通过一个Installed Loader加载当前已经安装的所有Extension,并且将那些设置为Enabled的Extension注册到Extension Registry中,从而得到一个当前可用的Extension列表。

       接下来,我们就从Chromium的Browser进程的启动开始,分析它加载Extension的过程。

       在前面Chromium硬件加速渲染的OpenGL上下文绘图表面创建过程分析一文中,我们以Content Shell APK为例,分析了Browser进程的启动过程。在这个启动过程中,会创建一个BrowserStartupController对象,并且调用这个BrowserStartupController对象的成员函数startBrowserProcessesAsync异步启动和初始化Chromium的Content模块,如下所示:

public class BrowserStartupController {
    ......

    public void startBrowserProcessesAsync(final StartupCallback callback)
            throws ProcessInitException {
        ......

        // Browser process has not been fully started yet, so we defer executing the callback.
        mAsyncStartupCallbacks.add(callback);
        ......

        if (!mHasStartedInitializingBrowserProcess) {
            ......

            prepareToStartBrowserProcess(MAX_RENDERERS_LIMIT);
            ......

            if (contentStart() > 0) {
                // Failed. The callbacks may not have run, so run them.
                enqueueCallbackExecution(STARTUP_FAILURE, NOT_ALREADY_STARTED);
            }
        }
    }

    ......
}
      这个函数定义在文件external/chromium_org/content/public/android/java/src/org/chromium/content/browser/BrowserStartupController.java中。

      在Browser进程的Content模块还没有启动过的情况下,BrowserStartupController类的成员变量mHasStartedInitializingBrowserProcess的值会等于false。在这种情况下,BrowserStartupController类的成员函数startBrowserProcessesAsync会做两件事情:

      1. 调用成员函数prepareToStartBrowserProcess在VM中加载libcontent_shell_content_view.so。

      2. 调用成员函数contentStart启动和初始化Content模块。

      从Dalvik虚拟机JNI方法的注册过程分析这篇文章可以知道,VM在加载so的过程中,将会调用它导出的一个名称为JNI_OnLoad的函数。对libcontent_shell_content_view.so来说,它导出的JNI_OnLoad函数的实现如下所示:

// This is called by the VM when the shared library is first loaded.
JNI_EXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) {
  ......

  content::SetContentMainDelegate(new content::ShellMainDelegate());
  return JNI_VERSION_1_4;
}
      这个函数定义在文件external/chromium_org/content$ vi shell/android/shell_library_loader.cc中。

      函数JNI_OnLoad将会创建一个ShellMainDelegate对象,并且调用函数SetContentMainDelegate将它保存在一个全局变量g_content_main_delegate中,如下所示:

namespace {
......

LazyInstance<scoped_ptr > g_content_main_delegate =
    LAZY_INSTANCE_INITIALIZER;
}  // namespace

void SetContentMainDelegate(ContentMainDelegate* delegate) {
  DCHECK(!g_content_main_delegate.Get().get());
  g_content_main_delegate.Get().reset(delegate);
}
      这个函数定义在文件external/chromium_org/content/app/android/content_main.cc中。

      这一步执行完成后,回到前面分析的BrowserStartupController类的成员函数startBrowserProcessesAsync中,它接下来将会调用另外一个成员函数contentStart启动和初始化Content模块,如下所示:

public class BrowserStartupController {
    ......

    int contentStart() {
        return ContentMain.start();
    }

    ......
}
       这个函数定义在文件external/chromium_org/content/public/android/java/src/org/chromium/content/browser/BrowserStartupController.java中。

       BrowserStartupController类的成员函数contentStart调用ContentMain类的静态成员函数start在Browser进程中启动和初始化Content模块。在前面Chromium的Render进程启动过程分析一文中,我们已经分析过ContentMain类的静态成员函数start的实现了。它最终会调用到C++层的一个函数Start启动和初始化Content模块,如下所示:

namespace {
LazyInstance<scoped_ptr<ContentMainRunner> > g_content_runner =
    LAZY_INSTANCE_INITIALIZER;

......
}  // namespace

......

static jint Start(JNIEnv* env, jclass clazz) {
  ......

  if (!g_content_runner.Get().get()) {
    ContentMainParams params(g_content_main_delegate.Get().get());
    g_content_runner.Get().reset(ContentMainRunner::Create());
    g_content_runner.Get()->Initialize(params);
  }
  return g_content_runner.Get()->Run();
}

       这个函数定义在文件external/chromium_org/content/app/android/content_main.cc中。

       函数Start首先判断一个全局变量g_content_runner是否已经指向了一个ContentMainRunner对象。如果还没有指向,那么就会调用ContentMainRunner类的静态成员函数Create创建一个ContentMainRunner对象,如下所示:

ContentMainRunner* ContentMainRunner::Create() {
  return new ContentMainRunnerImpl();
}
       这个函数定义在文件external/chromium_org/content/app/content_main_runner.cc中。

       从这里可以看到,ContentMainRunner类的静态成员函数Create创建的实际上是一个ContentMainRunnerImpl对象。这个ContentMainRunnerImpl对象返回给前面分析的函数Start之后,就会保存在全局变量g_content_runner中。

       函数Start获得了新创建的ContentMainRunnerImpl对象之后,会调用它的成员函数Initialize,并且将全局变量g_content_main_delegate指向的ShellMainDelegate对象封装在一个类型为ContentMainParams的参数中传递给它,让它执行初始化工作。

      ContentMainRunnerImpl类的成员函数Initialize的实现如下所示:

class ContentMainRunnerImpl : public ContentMainRunner {
 public:
  ......

  virtual int Initialize(const ContentMainParams& params) OVERRIDE {
    ......

    delegate_ = params.delegate;

    ......
  }
 
  ......

 private:
  ......

  ContentMainDelegate* delegate_;
  
  ......
};
      这个函数定义在文件external/chromium_org/content/app/content_main_runner.cc中。

      ContentMainRunnerImpl类的成员函数Initialize将封装在参数params中的一个ShellMainDelegate对象保存在成员变量delegate_中,也就是ContentMainRunnerImpl类的成员变量delegate_指向了一个ShellMainDelegate对象。

      回到前面分析的函数Start中,它最后调用前面创建的ContentMainRunnerImpl对象的成员函数Run启动和初始化Content模块,如下所示:

class ContentMainRunnerImpl : public ContentMainRunner {
 public:
  ......

  virtual int Run() OVERRIDE {
    ......
    const CommandLine& command_line = *CommandLine::ForCurrentProcess();
    std::string process_type =
          command_line.GetSwitchValueASCII(switches::kProcessType);

    MainFunctionParams main_params(command_line);
    ......

#if !defined(OS_IOS)
    return RunNamedProcessTypeMain(process_type, main_params, delegate_);
#else
    return 1;
#endif
  }

  ......
};

       这个函数定义在文件external/chromium_org/content/app/content_main_runner.cc中。

       ContentMainRunnerImpl类的成员函数Run首先检查当前进程是否指定了switches::kProcessType启动选项。如果指定了,那么就会获取它的值。获取到的值保存在本地变量process_type中,表示当前进程的类型。Browser进程没有指定switches::kProcessType启动选项,因此本地变量process_type的值将为空,表示当前进程是Browser进程。

        ContentMainRunnerImpl类的成员函数Run接下来调用函数RunNamedProcessTypeMain启动和初始化Content模块,如下所示:

int RunNamedProcessTypeMain(
    const std::string& process_type,
    const MainFunctionParams& main_function_params,
    ContentMainDelegate* delegate) {
  static const MainFunction kMainFunctions[] = {
#if !defined(CHROME_MULTIPLE_DLL_CHILD)
    { "",                            BrowserMain },
#endif
#if !defined(CHROME_MULTIPLE_DLL_BROWSER)
#if defined(ENABLE_PLUGINS)
#if !defined(OS_LINUX)
    { switches::kPluginProcess,      PluginMain },
#endif
    { switches::kWorkerProcess,      WorkerMain },
    { switches::kPpapiPluginProcess, PpapiPluginMain },
    { switches::kPpapiBrokerProcess, PpapiBrokerMain },
#endif  // ENABLE_PLUGINS
    { switches::kUtilityProcess,     UtilityMain },
    { switches::kRendererProcess,    RendererMain },
    { switches::kGpuProcess,         GpuMain },
#endif  // !CHROME_MULTIPLE_DLL_BROWSER
  };

  ......

  for (size_t i = 0; i < arraysize(kMainFunctions); ++i) {
    if (process_type == kMainFunctions[i].name) {
      if (delegate) {
        int exit_code = delegate->RunProcess(process_type,
            main_function_params);
#if defined(OS_ANDROID)
        // In Android's browser process, the negative exit code doesn't mean the
        // default behavior should be used as the UI message loop is managed by
        // the Java and the browser process's default behavior is always
        // overridden.
        if (process_type.empty())
          return exit_code;
#endif
        if (exit_code >= 0)
          return exit_code;
      }
      return kMainFunctions[i].function(main_function_params);
    }
  }

  ......

  return 1;
}
       这个函数定义在文件external/chromium_org/content/app/content_main_runner.cc中。

       函数RunNamedProcessTypeMain在内部定义了一个静态数组kMainFunctions,它会根据参数process_type的值在这个数组中找到对应的函数执行,也就是不同的进程执行不同的函数来启动和初始化Content模块。

       从前面Chromium的Render进程启动过程分析Chromium的GPU进程启动过程分析Chromium的Plugin进程启动过程分析这三篇文章可以知道,Render进程、GPU进程和Plugin进程分别通过调用函数RendererMain、GpuMain和PluginMain启动和初始化Content模块。

       对于Browser进程来说,情况有点特殊,它并没有调用函数BrowserMain来启动和初始化Content模块。这是因为当参数process_type的值等于空时,函数RunNamedProcessTypeMain调用完成另外一个参数delegate指向的一个ShellMainDelegate对象的成员函数RunProcess后,就会直接直接返回,从而不会执行函数BrowserMain。

       这意味着Browser进程是通过调用ShellMainDelegate类的成员函数RunProcess来启动和初始化Content模块的,如下所示:

int ShellMainDelegate::RunProcess(
    const std::string& process_type,
    const MainFunctionParams& main_function_params) {
  ......

  browser_runner_.reset(BrowserMainRunner::Create());
  return ShellBrowserMain(main_function_params, browser_runner_);
}
       这个函数定义在文件external/chromium_org/content/shell/app/shell_main_delegate.cc中。

       ShellMainDelegate类的成员函数RunProcess首先调用BrowserMainRunner类的静态成员函数Create创建一个BrowserMainRunnerImpl对象,如下所示:

BrowserMainRunner* BrowserMainRunner::Create() {
  return new BrowserMainRunnerImpl();
}
       这个函数定义在文件external/chromium_org/content/browser/browser_main_runner.cc中。

       创建出来的BrowserMainRunnerImpl对象将会保存在ShellMainDelegate类的成员变量browser_runner_中,并且这个BrowserMainRunnerImpl对象会传递给另外一个函数ShellBrowserMain进行处理,如下所示:

// Main routine for running as the Browser process.
int ShellBrowserMain(
    const content::MainFunctionParams& parameters,
    const scoped_ptr<content::BrowserMainRunner>& main_runner) {
  ......

  int exit_code = main_runner->Initialize(parameters);
  ......

  if (exit_code >= 0)
    return exit_code;

  ......
    
  return exit_code;
}
       这个函数定义在文件external/chromium_org/content/shell/browser/shell_browser_main.cc中。

       函数ShellBrowserMain主要是调用参数main_runner指向的一个BrowserMainRunnerImpl对象的成员函数Initialize在Browser进程中初始化Content模块。BrowserMainRunnerImpl类的成员函数Initialize的返回值将会大于等于0,这时候函数ShellBrowserMain就会沿着调用路径一直返回到Java层去了,从而使得当前线程(Browser进程的主线程)在Java层进入到消息循环中去。

       以上就是Content Shell APK的Browsr进程的启动流程。Chrome APK的Browsr进程的启动流程也是类似的,它们最后都会通过调用BrowserMainRunnerImpl类的成员函数Initialize初始化Content模块。为了方便描述,接下来我们就将以Chrome APK为例,继续分析BrowserMainRunnerImpl类的成员函数Initialize的实现,从中就可以看到Extension的加载过程。

       BrowserMainRunnerImpl类的成员函数Initialize的实现如下所示:

class BrowserMainRunnerImpl : public BrowserMainRunner {
 public:
  ......

  virtual int Initialize(const MainFunctionParams& parameters) OVERRIDE {
    ......

    if (!initialization_started_) {
      initialization_started_ = true;
      ......

      main_loop_.reset(new BrowserMainLoop(parameters));

      main_loop_->Init();

      ......
    }

    main_loop_->CreateStartupTasks();
    int result_code = main_loop_->GetResultCode();
    if (result_code > 0)
      return result_code;

    // Return -1 to indicate no early termination.
    return -1;
  }

  ......
};
       这个函数定义在文件external/chromium_org/content/browser/browser_main_runner.cc中。

       BrowserMainRunnerImpl类的成员函数Initialize首先检查成员变量initialization_started_的值是否不等于true。如果不等于true,那么就说明Browser进程还没有执行过初始化操作。在这种情况下,BrowserMainRunnerImpl类的成员函数Initialize接下来就会创建一个BrowserMainLoop对象,并且调用这个BrowserMainLoop对象的成员函数Init执行初始化工作,如下所示:

void BrowserMainLoop::Init() {
  ......
  parts_.reset(
      GetContentClient()->browser()->CreateBrowserMainParts(parameters_));
}
       这个函数定义在文件external/chromium_org/content/browser/browser_main_loop.cc中。

       BrowserMainLoop对象的成员函数Init的主要任务是创建一个BrowserMainParts对象,并且保存在成员变量parts_中。后面会通过这个BrowserMainParts对象执行一些初始化工作。

       为了创建这个BrowserMainParts对象,BrowserMainLoop对象的成员函数Init首先调用函数GetContentClient获得一个ContentClient对象。对于Chrome APK来说,这个ContentClient对象的实际类型为ChromeContentClient,也就是这里调用函数GetContentClient获得的是一个ChromeContentClient对象。

       有了这个ChromeContentClient对象之后,就可以调用它的成员函数browser可以获得一个ChromeContentBrowserClient对象。有了这个ChromeContentBrowserClient对象,就可以调用它的成员函数CreateBrowserMainParts创建一个BrowserMainParts对象了,如下所示:

content::BrowserMainParts* ChromeContentBrowserClient::CreateBrowserMainParts(
    const content::MainFunctionParams& parameters) {
  ChromeBrowserMainParts* main_parts;
  // Construct the Main browser parts based on the OS type.
#if defined(OS_WIN)
  main_parts = new ChromeBrowserMainPartsWin(parameters);
#elif defined(OS_MACOSX)
  main_parts = new ChromeBrowserMainPartsMac(parameters);
#elif defined(OS_CHROMEOS)
  main_parts = new chromeos::ChromeBrowserMainPartsChromeos(parameters);
#elif defined(OS_LINUX)
  main_parts = new ChromeBrowserMainPartsLinux(parameters);
#elif defined(OS_ANDROID)
  main_parts = new ChromeBrowserMainPartsAndroid(parameters);
#elif defined(OS_POSIX)
  main_parts = new ChromeBrowserMainPartsPosix(parameters);
#else
  NOTREACHED();
  main_parts = new ChromeBrowserMainParts(parameters);
#endif

  ......

  return main_parts;
}
       这个函数定义在文件external/chromium_org/chrome/browser/chrome_content_browser_client.cc中。

       从这里可以看到,在Android平台上,ChromeContentBrowserClient类的成员函数CreateBrowserMainParts创建的是一个ChromeBrowserMainPartsAndroid对象。这个ChromeBrowserMainPartsAndroid对象是从ChromeBrowserMainParts类继承下来的。

       这一步执行完成之后,BrowserMainLoop对象的成员函数Init就创建了一个ChromeBrowserMainPartsAndroid对象,并且保存在成员变量parts_中。回到前面分析的BrowserMainRunnerImpl类的成员函数Initialize中,它最后会调用前面已经初始化好的BrowserMainLoop对象的成员函数CreateStartupTasks创建一系列Startup Tasks,用来初始化Content模块。这其中就包含了一个类型为PreMainMessageLoopRun的Startup Task,也就是在Browser进程的主线程进入消息循环前执行的Startup Task,如下所示:

void BrowserMainLoop::CreateStartupTasks() {
  .....

  // First time through, we really want to create all the tasks
  if (!startup_task_runner_.get()) {
#if defined(OS_ANDROID)
    startup_task_runner_ = make_scoped_ptr(new StartupTaskRunner(
        base::Bind(&BrowserStartupComplete),
        base::MessageLoop::current()->message_loop_proxy()));
#else
    startup_task_runner_ = make_scoped_ptr(new StartupTaskRunner(
        base::Callback<void(int)>(),
        base::MessageLoop::current()->message_loop_proxy()));
#endif
    ......

    StartupTask pre_main_message_loop_run = base::Bind(
        &BrowserMainLoop::PreMainMessageLoopRun, base::Unretained(this));
    startup_task_runner_->AddTask(pre_main_message_loop_run);

    ......
  }
#if defined(OS_ANDROID)
  if (!BrowserMayStartAsynchronously()) {
    // A second request for asynchronous startup can be ignored, so
    // StartupRunningTasksAsync is only called first time through. If, however,
    // this is a request for synchronous startup then it must override any
    // previous call for async startup, so we call RunAllTasksNow()
    // unconditionally.
    startup_task_runner_->RunAllTasksNow();
  }
#else
  startup_task_runner_->RunAllTasksNow();
#endif
}
       这个函数定义在文件external/chromium_org/content/browser/browser_main_loop.cc中。

       BrowserMainLoop类的成员函数CreateStartupTasks首先会检查成员变量startup_task_runner_是否还没有指向一个StartupTaskRunner对象。如果没有指向,那么就会创建一个StartupTaskRunner对象让它指向。这个StartupTaskRunner对象可以用来向当前线程(Browser进程的主线程)的消息队列发送消息,从而可以执行指定的Startup Task。

       类型为PreMainMessageLoopRun的Startup Task绑定了BrowserMainLoop类的成员函数PreMainMessageLoopRun。这意味着接下来BrowserMainLoop类的成员函数PreMainMessageLoopRun会在Browser进程的主线程执行,如下所示:

int BrowserMainLoop::PreMainMessageLoopRun() {
  if (parts_) {
    ......
    parts_->PreMainMessageLoopRun();
  }

  ......
  return result_code_;
}

       这个函数定义在文件external/chromium_org/content/browser/browser_main_loop.cc中。

       从前面的分析可以知道,BrowserMainLoop类的成员变量parts_指向的是一个ChromeBrowserMainPartsAndroid对象。BrowserMainLoop类的成员函数PreMainMessageLoopRun调用这个ChromeBrowserMainPartsAndroid对象的成员函数PreMainMessageLoopRun执行Browser进程在PreMainMessageLoopRun阶段的初始化工作。

       ChromeBrowserMainPartsAndroid类的成员函数PreMainMessageLoopRun是从父类ChromeBrowserMainParts继承下来的,它的实现如下所示:

void ChromeBrowserMainParts::PreMainMessageLoopRun() {
  ......

  result_code_ = PreMainMessageLoopRunImpl();

  ......
}
      这个函数定义在文件external/chromium_org/chrome/browser/chrome_browser_main.cc中。

      ChromeBrowserMainParts类的成员函数PreMainMessageLoopRun调用另外一个成员函数PreMainMessageLoopRunImpl执行Browser进程在PreMainMessageLoopRun阶段的初始化工作,如下所示:

int ChromeBrowserMainParts::PreMainMessageLoopRunImpl() {
  ......

  profile_ = CreatePrimaryProfile(parameters(),
                                  user_data_dir_,
                                  parsed_command_line());
  ......

  return result_code_;
}
       这个函数定义在文件external/chromium_org/chrome/browser/chrome_browser_main.cc中。

       ChromeBrowserMainParts类的成员函数PreMainMessageLoopRunImpl执行了一系列的初始化工作。其中的一个初始化工作是为当前登录的用户创建Profile。这是通过调用函数CreatePrimaryProfile实现的。在创建Profile的过程中,就会加载为当前登录的用户安装的Extension。

       接下来我们就继续分析函数CreatePrimaryProfile的实现,如下所示:

Profile* CreatePrimaryProfile(const content::MainFunctionParams& parameters,
                              const base::FilePath& user_data_dir,
                              const CommandLine& parsed_command_line) {
  ......

  Profile* profile = NULL;
#if defined(OS_CHROMEOS) || defined(OS_ANDROID)
  ......
  profile = ProfileManager::GetActiveUserProfile();
#else
  ......
#endif
  if (profile) {
    ......
    return profile;
  }

  ......

  return NULL;
}

       这个函数定义在文件external/chromium_org/chrome/browser/chrome_browser_main.cc中。

       在Android平台上,函数CreatePrimaryProfile调用ProfileManager类的静态成员函数GetActiveUserProfile获得当前用户的Profile,如下所示:

Profile* ProfileManager::GetActiveUserProfile() {
  ProfileManager* profile_manager = g_browser_process->profile_manager();
  ......
  Profile* profile =
      profile_manager->GetActiveUserOrOffTheRecordProfileFromPath(
          profile_manager->user_data_dir());
  ......
  return profile;
}
       这个函数定义在文件external/chromium_org/chrome/browser/profiles/profile_manager.cc中。

       ProfileManager类的静态成员函数GetActiveUserProfile首先在当前进程(Browser进程)中获得一个ProfileManager单例对象。通过调用这个ProfileManager单例对象的成员函数user_data_dir可以获得当前用户的数据目录。有了这个数据目录之后 ,再调用上述ProfileManager单例对象的成员函数GetActiveUserOrOffTheRecordProfileFromPath就可以获得当前用户的Profile,如下所示:

Profile* ProfileManager::GetActiveUserOrOffTheRecordProfileFromPath(
    const base::FilePath& user_data_dir) {
#if defined(OS_CHROMEOS)
  ......
#else
  base::FilePath default_profile_dir(user_data_dir);
  default_profile_dir = default_profile_dir.Append(GetInitialProfileDir());
  return GetProfile(default_profile_dir);
#endif
}
       这个函数定义在文件external/chromium_org/chrome/browser/profiles/profile_manager.cc中。

       ProfileManager类的成员函数GetActiveUserOrOffTheRecordProfileFromPath首先调用另外一个成员函数etInitialProfileDir获得Profile目录。这个Profile目录是相对参数user_data_dir描述的数据目录之下的。将Profile目录添加到数据目录之后,就得到Profile目录的绝对路径。有了这个绝对路径之后,ProfileManager类的成员函数GetActiveUserOrOffTheRecordProfileFromPath就调用成员函数GetProfile获得当前用户的Profile,如下所示:

Profile* ProfileManager::GetProfile(const base::FilePath& profile_dir) {
  TRACE_EVENT0("browser", "ProfileManager::GetProfile")
  // If the profile is already loaded (e.g., chrome.exe launched twice), just
  // return it.
  Profile* profile = GetProfileByPath(profile_dir);
  if (NULL != profile)
    return profile;

  profile = CreateProfileHelper(profile_dir);
  DCHECK(profile);
  if (profile) {
    bool result = AddProfile(profile);
    DCHECK(result);
  }
  return profile;
}
       这个函数定义在文件external/chromium_org/chrome/browser/profiles/profile_manager.cc中。

       ProfileManager类的成员函数GetActiveUserOrOffTheRecordProfileFromPath首先调用成员函数GetProfileInfoByPath检查是否已经为参数profile_dir描述的Profile目录创建过Profile。如果已经创建,那么就将该Profile返回给调用者。否则的话,就会调用成员函数CreateProfileHelper为参数profile_dir描述的Profile目录创建一个Profile,并且调用另外一个成员函数AddProfile将其保存在内部,以及返回给调用者。

       ProfileManager类的成员函数AddProfile在将当前用户的Profile保存在内部之后,会根据Profile的内容执行相应初始化工作,如下所示:

bool ProfileManager::AddProfile(Profile* profile) {
  ......

  RegisterProfile(profile, true);
  ......
  DoFinalInit(profile, ShouldGoOffTheRecord(profile));
  return true;
}
       这个函数定义在文件external/chromium_org/chrome/browser/profiles/profile_manager.cc中。

       ProfileManager类的成员函数AddProfile首先调用成员函数RegisterProfile将参数profile描述的Profile保存在内部,接下来调用另外一个成员函数DoFinalnit根据该Profile执行相应的初始化工作,其中就包括创建Extension Service,如下所示:

void ProfileManager::DoFinalInit(Profile* profile, bool go_off_the_record) {
  DoFinalInitForServices(profile, go_off_the_record);
  ......
}
      这个函数定义在文件external/chromium_org/chrome/browser/profiles/profile_manager.cc中。

      ProfileManager类的成员函数DoFinalnit是在调用成员函数DoFinalInitForServices的过程中创建Extension Service的,如下所示:

void ProfileManager::DoFinalInitForServices(Profile* profile,
                                            bool go_off_the_record) {
#if defined(ENABLE_EXTENSIONS)
  extensions::ExtensionSystem::Get(profile)->InitForRegularProfile(
      !go_off_the_record);
  ......
#endif
  ......
}
      这个函数定义在文件external/chromium_org/chrome/browser/profiles/profile_manager.cc中。

      从这里可以看到,在定义了宏ENABLE_EXTENSIONS的情况下,Chromium才会支持Extension。这时候ProfileManager类的成员函数DoFinalInitForServices首先根据参数profile描述的Profile获得一个ExtensionSystemImpl对象,然后再调用这个ExtensionSystemImpl对象的成员函数InitForRegularProfile创建一个Extension Service,如下所示:

void ExtensionSystemImpl::InitForRegularProfile(bool extensions_enabled) {
  ......

  process_manager_.reset(ProcessManager::Create(profile_));

  shared_->Init(extensions_enabled);
}
      这个函数定义在文件external/chromium_org/chrome/browser/extensions/extension_system_impl.cc中。

      ExtensionSystemImpl类的成员函数InitForRegularProfile首先会调用ProcessManager类的静态成员函数Create创建一个ProcessManager对象,并且保存在成员变量process_manager_。在接下来一篇文章中,我们就会看到,Extension的Background Page就是通过这个ProcessManager对象加载起来的。

      ExtensionSystemImpl类的成员变量shared_指向的是一个ExtensionSystemImpl::Shared对象。ExtensionSystemImpl类的成员函数InitForRegularProfile调用这个ExtensionSystemImpl::Shared对象的成员函数Init创建一个Extension Service,如下所示:

void ExtensionSystemImpl::Shared::Init(bool extensions_enabled) {
  ......

  user_script_master_ = new UserScriptMaster(profile_);
  ......

  extension_service_.reset(new ExtensionService(
      profile_,
      CommandLine::ForCurrentProcess(),
      profile_->GetPath().AppendASCII(extensions::kInstallDirectoryName),
      ExtensionPrefs::Get(profile_),
      blacklist_.get(),
      autoupdate_enabled,
      extensions_enabled,
      &ready_));
  ......

  extension_service_->Init();
 
  ......
}

       这个函数定义在文件external/chromium_org/chrome/browser/extensions/extension_system_impl.cc中。

       Extension Service通过一个ExtensionService对象描述。ExtensionSystemImpl::Shared类的成员函数Init创建了这个ExtensionService对象之后,会保存在成员变量extension_service_中,并且调用这个ExtensionService对象的成员函数对其描述的Extension Service进行初始化,如下所示:

       此外,我们还看到,ExtensionSystemImpl::Shared类的成员函数Init还创建了一个UserScriptMaster对象保存在成员变量user_script_master_中。这个UserScriptMaster是用来管理接下来要加载的Extension的Content Script的。这一点我们在后面的文章会进行详细分析。

       现在,我们主要关注Extension Service的初始化过程。因此,接下来我们继续分析ExtensionService类的成员函数Init的实现,如下所示:

void ExtensionService::Init() {
  ......

  const CommandLine* cmd_line = CommandLine::ForCurrentProcess();
  if (cmd_line->HasSwitch(switches::kInstallFromWebstore) ||
      cmd_line->HasSwitch(switches::kLimitedInstallFromWebstore)) {
    ......
  } else {
    ......

    // LoadAllExtensions() calls OnLoadedInstalledExtensions().
    ......
    extensions::InstalledLoader(this).LoadAllExtensions();
    ......

    // Attempt to re-enable extensions whose only disable reason is reloading.
    std::vector<std::string> extensions_to_enable;
    const ExtensionSet& disabled_extensions = registry_->disabled_extensions();
    for (ExtensionSet::const_iterator iter = disabled_extensions.begin();
        iter != disabled_extensions.end(); ++iter) {
      const Extension* e = iter->get();
      if (extension_prefs_->GetDisableReasons(e->id()) ==
          Extension::DISABLE_RELOAD) {
        extensions_to_enable.push_back(e->id());
      }
    }
    for (std::vector<std::string>::iterator it = extensions_to_enable.begin();
         it != extensions_to_enable.end(); ++it) {
      EnableExtension(*it);
    }
 
    ......
  }

  ......
}
       这个函数定义在文件external/chromium_org/chrome/browser/extensions/extension_service.cc中。

       ExtensionService类的成员函数Init首先检查Browser进程的启动参数是否包含有switches::kInstallFromWebstore或者switches::kLimitedInstallFromWebstore选项。如果包含有,那么就只会从Web Store上加载当前用户的Extension。我们假设没有包含这两个选项,那么ExtensionService类的成员函数Init将会从本地加载当前用户的Extension。

       ExtensionService类的成员函数Init首先构造一个InstalledLoader对象,然后再调用这个InstalledLoader对象的成员函数LoadAllExtensions加载当前用户安装的所有Extension。这些加载的Extension,即有Enabled的,也有Disabled的。

       加载后的Extension会保存在ExtensionService类的成员变量registry_描述的一个Extension Registry中。ExtensionService类的成员函数Init最后会从这个Extension Registry获得那些处于Enabled状态的Extension,并且调用另外一个成员函数EnableExtension启用它们。

       接下来,我们主要关注Extension的加载过程。因此,我们继续分析InstalledLoader类的成员函数LoadAllExtensions的实现,如下所示:

void InstalledLoader::LoadAllExtensions() {
  ......

  Profile* profile = extension_service_->profile();
  scoped_ptr<ExtensionPrefs::ExtensionsInfo> extensions_info(
      extension_prefs_->GetInstalledExtensionsInfo());

  ......

  for (size_t i = 0; i < extensions_info->size(); ++i) {
    if (extensions_info->at(i)->extension_location != Manifest::COMMAND_LINE)
      Load(*extensions_info->at(i), should_write_prefs);
  }

  ......
}

       这个函数定义在文件external/chromium_org/chrome/browser/extensions/installed_loader.cc中。

       InstalledLoader类的成员函数LoadAllExtensions首先获得当前用户安装的所有Extension。注意,这些Extension既包括用户在“chrome://extensions”页面中安装的Extension,也包括用户在启动Chromium时通过命令行参数“--load-extension”指定的Extension。不过,InstalledLoader类的成员函数LoadAllExtensions只会加载那些非命令行参数指定的Extension。对于命令行参数指定的Extension,在Extension Service初始化结束后,Extension System会通过另外一个Unpacked Installer来加载它们。

       InstalledLoader类的成员函数LoadAllExtensions是通过调用另外一个成员函数Load加载那些非命令行参数指定的Extension的,如下所示:

void InstalledLoader::Load(const ExtensionInfo& info, bool write_to_prefs) {
  std::string error;
  scoped_refptr<const Extension> extension(NULL);
  if (info.extension_manifest) {
    extension = Extension::Create(
        info.extension_path,
        info.extension_location,
        *info.extension_manifest,
        GetCreationFlags(&info),
        &error);
  } else {
    error = errors::kManifestUnreadable;
  }

  ......

  extension_service_->AddExtension(extension.get());
}
       这个函数定义在文件external/chromium_org/chrome/browser/extensions/installed_loader.cc中。

       InstalledLoader类的成员函数Load首先是根据参数info描述的Extension Info创建一个Extension对象,这是通过调用Extension类的静态成员函数Create实现的。这个Extension对象最终会交给前面创建的Extension Service处理。当Extension Service处理完毕,参数info描述的Extension就加载完毕。

       InstalledLoader类的成员变量extension_service_指向的是一个ExtensionService对象。这个ExtensionService对象描述的就是前面创建的Extension Service。通过调用这个ExtensionService对象的成员函数AddExtension即可以将参数info描述的Extension交给前面创建的Extension Service处理。处理过程如下所示:

void ExtensionService::AddExtension(const Extension* extension) {
  ......

  if (extension_prefs_->IsExtensionBlacklisted(extension->id())) {
    ......
    registry_->AddBlacklisted(extension);
  } else if (!reloading &&
             extension_prefs_->IsExtensionDisabled(extension->id())) {
    registry_->AddDisabled(extension);
    ......
  } else if (reloading) {
    ......
    EnableExtension(extension->id());
  } else {
    ......
    registry_->AddEnabled(extension);
    ......
    NotifyExtensionLoaded(extension);
  }
  ......
}
       这个函数定义在文件external/chromium_org/chrome/browser/extensions/extension_service.cc中。

       ExtensionService类的成员函数AddExtension会判断参数extension描述的Extension的状态,并且执行的操作:

       1. 如果它被用户列入黑名单,那么就将它记录在Extension Registry内部的Black List上。

       2. 如果它被用户禁用,那么将它记录在Extension Registry内部的Disabled List上。

       3. 如果它被重新加载,那么对它执行一个Enable操作。

       4. 如果它是第一次加载,那么将它记录在Extension Registry内部的Enabled List上。

       接下来我们主要关注第4种情况。这时候ExtensionService类的成员函数AddExtension首先会调用成员变量registry_指向的一个ExtensionRegistry对象的成员函数AddEnabled将参数extension描述的Extension记录在Extension Registry内部的Enabled List上,接下来又调用另外一个成员函数NotifyExtensionLoaded通知其它模块,有一个新的Extension被加载。

       接下来我们就继续分析ExtensionRegistry类的成员函数AddEnabled和ExtensionService类的成员函数NotifyExtensionLoaded的实现,以便完整了解Extension的加载过程。

       ExtensionRegistry类的成员函数AddEnabled的实现如下所示:

bool ExtensionRegistry::AddEnabled(
    const scoped_refptr<const Extension>& extension) {
  return enabled_extensions_.Insert(extension);
}
       这个函数定义在文件external/chromium_org/extensions/browser/extension_registry.cc中。

       ExtensionRegistry类的成员变量enabled_extensions_描述的就是一个Enabled List,因此ExtensionRegistry类的成员函数AddEnabled会将参数extension描述的Extension保存在里面。

       ExtensionService类的成员函数NotifyExtensionLoaded的实现如下所示:

void ExtensionService::NotifyExtensionLoaded(const Extension* extension) {
  ......

  registry_->TriggerOnLoaded(extension);
 
  ......
}
      这个函数定义在文件external/chromium_org/chrome/browser/extensions/extension_service.cc中。

      ExtensionService类的成员函数NotifyExtensionLoaded会通过Extension Registry通知其它模块有一个新的Extension被加载,这是通过调用成员变量registry_指向的一个ExtensionRegistry对象的成员函数TriggerOnLoaded实现的,如下所示:

void ExtensionRegistry::TriggerOnLoaded(const Extension* extension) {
  DCHECK(enabled_extensions_.Contains(extension->id()));
  FOR_EACH_OBSERVER(ExtensionRegistryObserver,
                    observers_,
                    OnExtensionLoaded(browser_context_, extension));
}
       这个函数定义在文件external/chromium_org/extensions/browser/extension_registry.cc中。

       一个模块如果需要关注新加载的Extension,那么就会注册一个Extension Registry Observer到Extension Registry的内部。这些Extension Registry Observer保存在ExtensionRegistry类的成员变量observers_描述的一个List中。

       ExtensionRegistry类的成员函数TriggerOnLoaded所做的事情就是调用每一个注册在Extension Registry中的Extension Registry Observer的成员函数OnExtensionLoaded,分别通知它们有一个新的Extension被加载。在后面的文章中,我们就会看到,负责管理Content Script的User Script Master模块会注册一个Extension Registry Observer到Extension Registry,目的就是获取每一个新加载的Extension指定的Content Script,以便在合适的时候注入到宿主网页中去执行。

       至此,我们就分析完成了Chromium加载Extension的过程。这是在Extension Service的初始化过程中执行的。这个Extension Service又是在Chromium为当前用户创建Profile的过程中启动的。最后,为当前用户创建Profile的操作是在Chromium启动的过程中执行的。这意味着Extension是在Chromium启动的时候加载的,也就是在Chromium的Browser进程启动时加载。在接下来的一篇文章中,我们继续分析Extension的Background Page和Popup Page的加载过程,敬请关注!更多的信息也可以关注老罗的新浪微博:http://weibo.com/shengyangluo

作者:Luoshengyang 发表于2016/9/12 0:59:50 原文链接
阅读:99 评论:1 查看评论

工作第九周:勇敢的少年,快去创造奇迹!

$
0
0

这一周

  1. 最近闹得沸沸扬扬的就是郭德纲曹云金师徒的恩怨情仇,这俩人我也不评价,只是觉得两句话挺应景:

    德不配位,必有殃灾;
    还没学会就红了,红了之后更不会好好学了。

  2. 上周末又去看牙医,因为左边牙肿完右边又肿了,医生说智齿没长好,得拔。拿不定主意。这周牙消肿了,感觉整个人都精神多了,战斗力爆表!

  3. 由于产品前段时间休假,回来后要整理各种需求,导致需求迟迟下不来。领导看见觉得不行啊,给我们开了个会布置了下当前方向,总结下来就是:

    .等待资源(需求、接口、切图)的时候尽量做一些 app 性能优化的工作。

  4. 上周五新版本上线,这周我进行了 develop, release, master 分支的合并,学习了些 Git Flow 的东西。Git 是个好东西,还是得实战才能理解。

  5. 调接口是个痛苦的过程,因为接口出来前需要假数据,而碰上不熟悉的服务端,你想的数据结构可能会和最终服务端给的数据结构差很多,到时候填数据会很蛋疼,所以要先确定数据模型再写假数据;

    • 有问题要耐心调试,按步跟进
    • 不要总觉得别人错了,一般都是你自己的问题
    • 质问别人之前一定要再三确定自己没看错!
  6. 外蒙古为什么独立出去?

    • 内部混乱,外敌趁乱打劫,无力顾暇
    • 攘外必先安内

技术上的收获

1.公司内部迁移 GitLab 地址,结果分支太多,命令行里一个个上传好累啊。同事推荐了小乌龟 TortoiseGit,原来小乌龟不仅可以用于 SVN,也可以 Git。用它一键上传所有分支,快得很!

这里写图片描述

2.升级 gradle 后记得更新根目录下 gradle plugin 的版本,要不然打包会报错

3.为了阻止点击DrawLayout时事件传递到下一层,可以给 Drawlayout 添加一个 OnClickListener

4.git add 的几种参数区别

  • git add -A 保存所有的修改
  • git add . 保存新的添加和修改,但是不包括删除
  • git add -u 保存修改和删除,但是不包括新建文件。

5.一次惊险的 branch merge 过程,【占位等填坑】

6.Git Tag
tag 指向一次 commit 的 id ,可以用来对一个节点进行标识,在分支很多的时候,可以把分支合并到一个,然后在不同版本打一些 tag,便于后续根据 tag 切换到指定版本。

  • 查看标签
    git tag

  • 打标签
    git tag -a v1.01 -m “Relase version 1.01”
    注解:git tag 是打标签的命令,-a 是添加标签,其后要跟新标签号,-m 及后面的字符串是对该标签的注释。

  • 提交标签到远程仓库
    git push origin –tags
    注解:就像git push origin master 把本地修改提交到远程仓库一样,-tags可以把本地的打的标签全部提交到远程仓库。

  • 获取远端 tag 对应的分支
    git checkout tag_name 就可以取得 tag 对应的代码了。但是这时候 git 可能会提示你当前处于一个“detached HEAD” 状态,因为 tag 相当于是一个快照,是不能更改它的代码的,如果要在 tag 代码的基础上做修改,你需要一个分支:
    git checkout -b branch_name tag_name
    这样会从 tag 创建一个分支,然后就和普通的 git 操作一样了。

  • 删除标签
    git tag -d v1.01
    注解:-d 表示删除,后面跟要删除的tag名字

  • 删除远程标签
    git push origin :refs/tags/v1.01
    注解:就像git push origin :branch_1 可以删除远程仓库的分支branch_1一样, 冒号前为空表示删除远程仓库的tag。

7.ImageView 的 tint 属性,可以修改图片的颜色!!

8.bug:

One or more layouts are missing the layout_width or layout_height attributes. These are required in most layouts.

法1:重启as,不管用
法2:更新依赖的 v4 包版本,不管用
法3:去掉 layout_height 中的 ?attr/actionBarSize

9.CDN:

Content Delivery Network 内容分发网络,通过在网络各处放置节点服务器所构成的在现有的互联网基础之上的一层智能虚拟网络,CDN系统能够实时地根据网络流量和各节点的连接、负载状况以及到用户的距离和响应时间等综合信息将用户的请求重新导向离用户最近的服务节点上。其目的是使用户可就近取得所需内容,解决 Internet网络拥挤的状况,提高用户访问网站的响应速度。

10.sp 和 dp 区别:

  • Scale-independent Pixels / Density-independent Pixels
  • sp 除了可以和 dp 一样随着屏幕密度缩放,还会跟随用户设置系统字体大小缩放;
  • 如果你的 App 没有整体为大字体做适配,单独为 TextView 设置 sp,一旦用户设置了大字体,界面排版、体验反而很糟糕,还不如用 dp。

总结

这周状态好收获还是蛮多的,不过还有一部分设计模式的知识点需要写博客才能加深理解,但是今天又加了班,所以只好推迟。总的来说收获还是很多的,勇敢的少年,快去创造奇迹!

作者:u011240877 发表于2016/9/12 1:32:16 原文链接
阅读:62 评论:0 查看评论

HG - Hexo + GitHub + Travis CI = 自动部署博客实现

$
0
0

背景

这里不要背景,就是因为懒,还想高大上!

hexo博客搭建先看文章 : 基于windows的实现 !

HG - 当Hexo遇到Github,擦出了什么样的火花


声明

本文如果你在windwos进行自动化部署的话,请绕道!

windows下加密SSH key 的 id_rsa ,生成的文件解密不了,解密不了,解密不了!

上文,基于windows下进行hexo的实现,可以在本地测试与实现,但不适合于自动部署实现,原因上面已经说了。


环境

(1)平台环境

VirtualBox 安装 Ubuntu Server 16.04 

(2)Ruby 安装其中的 坑,坑,坑

官方安装配置文档:

按照这里配置就完蛋了 : https://github.com/travis-ci/travis.rb

这里也不是完蛋了,毕竟平台都不一样,我的是Ubuntu Server16.04 !!! 安装Ruby成功 ,使用 Gem安装Travis 报错,折腾了一天!

报错如下:

root@ubuntu:~# gem install travis
Building native extensions.  This could take a while...
ERROR:  Error installing travis:
    ERROR: Failed to build gem native extension.

    current directory: /var/lib/gems/2.1.0/gems/ffi-1.9.14/ext/ffi_c
/usr/bin/ruby2.1 -r ./siteconf20160911-20807-1ijumtw.rb extconf.rb
checking for ffi.h... *** extconf.rb failed ***
Could not create Makefile due to some reason, probably lack of necessary
libraries and/or headers.  Check the mkmf.log file for more details.  You may
need configuration options.

Provided configuration options:
    --with-opt-dir
    --without-opt-dir
    --with-opt-include
    --without-opt-include=${opt-dir}/include
    --with-opt-lib
    --without-opt-lib=${opt-dir}/lib
    --with-make-prog
    --without-make-prog
    --srcdir=.
    --curdir
    --ruby=/usr/bin/ruby2.1
    --with-ffi_c-dir
    --without-ffi_c-dir
    --with-ffi_c-include
    --without-ffi_c-include=${ffi_c-dir}/include
    --with-ffi_c-lib
    --without-ffi_c-lib=${ffi_c-dir}/lib
    --with-libffi-config
    --without-libffi-config
    --with-pkg-config
    --without-pkg-config
/usr/lib/ruby/2.1.0/mkmf.rb:456:in `try_do': The compiler failed to generate an executable file. (RuntimeError)
You have to install development tools first.
    from /usr/lib/ruby/2.1.0/mkmf.rb:587:in `try_cpp'
    from /usr/lib/ruby/2.1.0/mkmf.rb:1061:in `block in have_header'
    from /usr/lib/ruby/2.1.0/mkmf.rb:912:in `block in checking_for'
    from /usr/lib/ruby/2.1.0/mkmf.rb:351:in `block (2 levels) in postpone'
    from /usr/lib/ruby/2.1.0/mkmf.rb:321:in `open'
    from /usr/lib/ruby/2.1.0/mkmf.rb:351:in `block in postpone'
    from /usr/lib/ruby/2.1.0/mkmf.rb:321:in `open'
    from /usr/lib/ruby/2.1.0/mkmf.rb:347:in `postpone'
    from /usr/lib/ruby/2.1.0/mkmf.rb:911:in `checking_for'
    from /usr/lib/ruby/2.1.0/mkmf.rb:1060:in `have_header'
    from extconf.rb:16:in `<main>'

To see why this extension failed to compile, please check the mkmf.log which can be found here:

  /var/lib/gems/2.1.0/extensions/x86_64-linux/2.1.0/ffi-1.9.14/mkmf.log

extconf failed, exit code 1

Gem files will remain installed in /var/lib/gems/2.1.0/gems/ffi-1.9.14 for inspection.
Results logged to /var/lib/gems/2.1.0/extensions/x86_64-linux/2.1.0/ffi-1.9.14/gem_make.out
root@ubuntu:~# cat /var/lib/gems/2.1.0/extensions/x86_64-linux/2.1.0/ffi-1.9.14/mkmf.log 
package configuration for libffi is not found
"gcc -o conftest -I/usr/include/x86_64-linux-gnu/ruby-2.1.0 -I/usr/include/ruby-2.1.0/ruby/backward -I/usr/include/ruby-2.1.0 -I. -Wdate-time -D_FORTIFY_SOURCE=2   -g -O2 -fstack-protector-strong -Wformat -Werror=format-security -fPIC conftest.c  -L. -L/usr/lib/x86_64-linux-gnu -L. -Wl,-Bsymbolic-functions -Wl,-z,relro -L/build/ruby2.1-zsMOW6/ruby2.1-2.1.9/debian/lib -fstack-protector -rdynamic -Wl,-export-dynamic     -lruby-2.1  -lpthread -lgmp -ldl -lcrypt -lm   -lc"
checked program was:
/* begin */
1: #include "ruby.h"
2: 
3: int main(int argc, char **argv)
4: {
5:   return 0;
6: }
/* end */

(3)Ruby 安装推荐 - Ubuntu Server

Ruby相关环境安装脚本:

Batch scripts for Ruby production environment install on Ubuntu Server.

怎样安装就自己看上面的文档吧!提醒一点,测试下是否已经安装过curl 了!

测试是否成功 :

yuan@ubuntu:~$ ruby -v
ruby 2.3.1p112 (2016-04-26) [x86_64-linux-gnu]
yuan@ubuntu:~$ gem -v
2.5.1

(4)安装 travis

成功如下:

yuan@ubuntu:~$ sudo gem install travis
[sudo] password for yuan: 
Building native extensions.  This could take a while...
Successfully installed ffi-1.9.14
Fetching: ethon-0.9.0.gem (100%)
Successfully installed ethon-0.9.0
Fetching: typhoeus-0.8.0.gem (100%)
Successfully installed typhoeus-0.8.0
Fetching: websocket-1.2.3.gem (100%)
Successfully installed websocket-1.2.3
Fetching: pusher-client-0.6.2.gem (100%)
Successfully installed pusher-client-0.6.2
Fetching: travis-1.8.2.gem (100%)
Successfully installed travis-1.8.2
Parsing documentation for ffi-1.9.14
Installing ri documentation for ffi-1.9.14
Parsing documentation for ethon-0.9.0
Installing ri documentation for ethon-0.9.0
Parsing documentation for typhoeus-0.8.0
Installing ri documentation for typhoeus-0.8.0
Parsing documentation for websocket-1.2.3
Installing ri documentation for websocket-1.2.3
Parsing documentation for pusher-client-0.6.2
Installing ri documentation for pusher-client-0.6.2
Parsing documentation for travis-1.8.2
Installing ri documentation for travis-1.8.2
Done installing documentation for ffi, ethon, typhoeus, websocket, pusher-client, travis after 14 seconds
6 gems installed

(5)Hexo 配置安装 - 自己安装咯,步骤一样

重温 :

  1. 下载node.js 配置环境变量;
  2. 安装hexo : npm install -g hexo-cli

配置Travis

(1)接入Travis CI

打开 Travis CI 网站,使用Github账号登录。将鼠标放在右上角的用户名上,点击Account选项,会显示github的项目。找到博客项目,点击前面带有 X 符号的按钮,开启travis支持。

这里写图片描述

自己玩玩吧

(2)新建配置文件

首先打开博客项目文件夹,在项目根目录新建.travis.yml配置文件。

$ cd 博客项目文件夹根目录
$ touch .travis.yml

(3)新建文件夹 .travis : 确保已经开启travis支持

yuan@ubuntu:~/doc/hexo/.travis$ mkdir .travis
yuan@ubuntu:~/doc/hexo/.travis$ cd .travis

(4)复制id_rsa 和 创建 ssh_config文件

yuan@ubuntu:~/doc/hexo/.travis$ cp ~/.ssh/id_rsa ./

创建 ssh_config 文件 :

yuan@ubuntu:~/doc/hexo/.travis$ sudo vim ssh_config 
[sudo] password for yuan: 

Host github.com
User git
StrictHostKeyChecking no
IdentityFile ~/.ssh/id_rsa
IdentitiesOnly yes

(5)travis 登陆

yuan@ubuntu:~/doc/hexo/.travis$ travis login --auto
We need your GitHub login to identify you.
This information will not be sent to Travis CI, only to api.github.com.
The password will not be displayed.

Try running with --github-token or --auto if you don't want to enter your password anyway.

Username: LABELNET
Password for LABELNET: ************
Successfully logged in as LABELNET!

(6)加密操作

加密操作
在博客项目文件夹下对ssh的私钥进行加密

yuan@ubuntu:~/doc/hexo/.travis$ travis encrypt-file id_rsa --add
encrypting id_rsa for LABELNET/blog
storing result as id_rsa.enc
storing secure env variables for decryption

Make sure to add id_rsa.enc to the git repository.
Make sure not to add id_rsa to the git repository.
Commit all changes to your .travis.yml.

此操作会生成加密之后的秘钥文件 id_rsa.enc,删除id_rsa密钥文件(私钥不能随便泄露)。

你的travis 目前上已经出现了下面加密解密key :

这里写图片描述

(7)我的 .travis.yml文件

yuan@ubuntu:~/doc/hexo$ cat .travis.yml 
language: node_js

node_js:
  - '4'

branches:
  only:
  - master

cache:
  directories:
  - node_modules

before_install:
- openssl aes-256-cbc -K $encrypted_xxxxxxx_key -iv $encrypted_xxxxxxxx_iv
  -in .travis/id_rsa.enc -out ~/.ssh/id_rsa -d
- chmod 600 ~/.ssh/id_rsa
- eval $(ssh-agent)
- ssh-add ~/.ssh/id_rsa
- cp .travis/ssh_config ~/.ssh/config
- git config --global user.name "LABELNET"
- git config --global user.email "1406046087@qq.com"

install:
- npm install hexo-cli -g
- npm install hexo-deployer-git --save
- npm install

script:
- hexo clean
- hexo g
- hexo g

after_success:
- hexo deploy

基本架构

这里写图片描述

_config.yml 配置如下:

deploy:
  type: git
  repo: git@github.com:LABELNET/labelnet.github.io.git
  branch: master

最后

(1)待解决问题

实力坑 - Hexo - Next 主题本地不报错,TravisCI 无内容生成!!!

(2)和我相关的资料

我的博客

博客源码

.travis.yml文件

作者:LABLENET 发表于2016/9/12 2:13:03 原文链接
阅读:40 评论:0 查看评论

Android知识架构 ·电话面试 · View的绘制流程

$
0
0

View的绘制流程:OnMeasure()——>OnLayout()——>OnDraw()

各步骤的主要工作:

  • OnMeasure():测量视图大小。从顶层父View到子View递归调用measure方法,measure方法又回调OnMeasure。

  • OnLayout():确定View位置,进行页面布局。从顶层父View向子View的递归调用view.layout方法的过程,即父View根据上一步measure子View所得到的布局大小和布局参数,将子View放在合适的位置上。

  • OnDraw():绘制视图。ViewRoot创建一个Canvas对象,然后调用OnDraw()。六个步骤:①、绘制视图的背景;②、保存画布的图层(Layer);③、绘制View的内容;④、绘制View子视图,如果没有就不用;⑤、还原图层(Layer);⑥、绘制滚动条。

OnMeasure

View树的绘制是从ViewRoot的performTraversals()方法开始,这个方法的主要作用是判断是否重新measure、是否重新layout、是否重新draw。

    private void performTraversals() {
        ......
        int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
        int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
        ......
        mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
        ......
        mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight());
        ......
        mView.draw(canvas);
        ......
    }

    private int getRootMeasureSpec(int windowSize, int rootDimension) {
        int measureSpec;
        switch (rootDimension) {
            case ViewGroup.LayoutParams.MATCH_PARENT:
                measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
                break;
            case ViewGroup.LayoutParams.WRAP_CONTENT:
                measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
                break;
            default:
                measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
                break;
        }
        return measureSpec;
    } 

    public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
        if ((mPrivateFlags & FORCE_LAYOUT) == FORCE_LAYOUT ||
                widthMeasureSpec != mOldWidthMeasureSpec ||
                heightMeasureSpec != mOldHeightMeasureSpec) {
            mPrivateFlags &= ~MEASURED_DIMENSION_SET;
            if (ViewDebug.TRACE_HIERARCHY) {
                ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_MEASURE);
            }
            onMeasure(widthMeasureSpec, heightMeasureSpec);
            if ((mPrivateFlags & MEASURED_DIMENSION_SET) != MEASURED_DIMENSION_SET) {
                throw new IllegalStateException("onMeasure() did not set the"
                        + " measured dimension by calling"
                        + " setMeasuredDimension()");
            }
            mPrivateFlags |= LAYOUT_REQUIRED;
        }
        mOldWidthMeasureSpec = widthMeasureSpec;
        mOldHeightMeasureSpec = heightMeasureSpec;

    //View的onMeasure默认实现方法,设置View的长和宽;如果写死,则外边变化时界面不变化
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }

    public static int getDefaultSize(int size, int measureSpec) {
        int result = size;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);
        switch (specMode) {
            case MeasureSpec.UNSPECIFIED:
                result = size;
                break;
            case MeasureSpec.AT_MOST:
            case MeasureSpec.EXACTLY:
                result = specSize;
                break;
        }
        return result;
    }  

如果是ViewGroup,还应该进行嵌套测量:

    protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
        final int size = mChildrenCount;
        final View[] children = mChildren;
        for (int i = 0; i < size; ++i) {
            final View child = children[i];
            if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
                measureChild(child, widthMeasureSpec, heightMeasureSpec);
            }
        }
    }

    protected void measureChild(View child, int parentWidthMeasureSpec,
                                int parentHeightMeasureSpec) {
        final LayoutParams lp = child.getLayoutParams();
        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                mPaddingLeft + mPaddingRight, lp.width);
        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                mPaddingTop + mPaddingBottom, lp.height);
        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }  

    public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
        int specMode = MeasureSpec.getMode(spec);
        int specSize = MeasureSpec.getSize(spec);
        int size = Math.max(0, specSize - padding);
        int resultSize = 0;
        int resultMode = 0;
        switch (specMode) {
            case MeasureSpec.EXACTLY:
                if (childDimension >= 0) {
                    resultSize = childDimension;
                    resultMode = MeasureSpec.EXACTLY;
                } else if (childDimension == LayoutParams.MATCH_PARENT) {
                    resultSize = size;
                    resultMode = MeasureSpec.EXACTLY;
                } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                    // Child wants to determine its own size. It can't be
                    // bigger than us.
                    resultSize = size;
                    resultMode = MeasureSpec.AT_MOST;
                }
                break;
            ......
        }
        //将mode与size通过MeasureSpec方法整合为32位整数返回
        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
    }

MeasureSpec 的值由specSize和specMode共同组成的,高2位代表specMode,低30代表spceSize,其中specSize记录的是大小,specMode记录的是规格。

specMode一共有三种类型:

  1. EXACTLY
    表示父视图希望子视图的大小应该是由specSize的值来决定的,系统默认会按照这个规则来设置子视图的大小,开发人员当然也可以按照自己的意愿设置成任意的大小。

  2. AT_MOST
    表示子视图最多只能是specSize中指定的大小,开发人员应该尽可能小得去设置这个视图,并且保证不会超过specSize。系统默认会按照这个规则来设置子视图的大小,开发人员当然也可以按照自己的意愿设置成任意的大小。

  3. UNSPECIFIED
    表示开发人员可以将视图按照自己的意愿设置成任意的大小,没有任何限制。这种情况比较少见。

每个View控件的实际宽高都是由父视图和自身决定的。实际的测量是在onMeasure方法进行,所以在View的子类需要重写onMeasure方法,这是因为measure方法是final的,不允许重载,所以View子类只能通过重载onMeasure来实现自己的测量逻辑。

OnLayout

测量完各个组件的大小之后,就可以排列他们的位置了。

    private void performTraversals() {
        ......
        int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
        int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
        ......
        mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
        ......
        mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight());
        ......
        mView.draw(canvas);
        ......
    }

    public void layout(int l, int t, int r, int b) {
        int oldL = mLeft;
        int oldT = mTop;
        int oldB = mBottom;
        int oldR = mRight;
        boolean changed = setFrame(l, t, r, b);//视图大小是否发生变化
        if (changed || (mPrivateFlags & LAYOUT_REQUIRED) == LAYOUT_REQUIRED) {
            if (ViewDebug.TRACE_HIERARCHY) {
                ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_LAYOUT);
            }
            onLayout(changed, l, t, r, b);
            mPrivateFlags &= ~LAYOUT_REQUIRED;
            if (mOnLayoutChangeListeners != null) {
                ArrayList<OnLayoutChangeListener> listenersCopy =
                        (ArrayList<OnLayoutChangeListener>) mOnLayoutChangeListeners.clone();
                int numListeners = listenersCopy.size();
                for (int i = 0; i < numListeners; ++i) {
                    listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
                }
            }
        }
        mPrivateFlags &= ~FORCE_LAYOUT;
    }  

View中的onLayout()方法就是一个空方法,因为onLayout()过程是为了确定视图在布局中所在的位置,而这个操作应该是由布局来完成的,即父视图决定子视图的显示位置。

@Override  
/**
*ViewGroupd的onLayout方法
*/
protected abstract void onLayout(boolean changed, int l, int t, int r, int b);  

然而,ViewGroup中的onLayout()方法是一个抽象方法,这就意味着所有ViewGroup的子类都必须重写这个方法。自定义ViewGroup控件中,onLayout配合onMeasure方法一起使用可以实现自定义View的复杂布局。自定义View首先调用onMeasure进行测量,然后调用onLayout方法动态获取子View和子View的测量大小,然后进行layout布局。重载onLayout的目的就是安排其children在父View的具体位置,重载onLayout通常做法就是写一个for循环调用每一个子视图的layout(l, t, r, b)函数,传入不同的参数l, t, r, b来确定每个子视图在父视图中的显示位置。

下面代码以LinearLayout举例:

public class LinearLayout extends ViewGroup {
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        if (mOrientation == VERTICAL) {
            layoutVertical(l, t, r, b);
        } else {
            layoutHorizontal(l, t, r, b);
        }
    }
}

void layoutVertical(int left, int top, int right, int bottom) {
        final int paddingLeft = mPaddingLeft;
        int childTop;
        int childLeft;
        final int width = right - left;
        int childRight = width - mPaddingRight;
        int childSpace = width - paddingLeft - mPaddingRight;
        final int count = getVirtualChildCount();
        final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
        final int minorGravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;
        switch (majorGravity) {
            case Gravity.BOTTOM:
                childTop = mPaddingTop + bottom - top - mTotalLength;
                break;
            case Gravity.CENTER_VERTICAL:
                childTop = mPaddingTop + (bottom - top - mTotalLength) / 2;
                break;
            case Gravity.TOP:
            default:
                childTop = mPaddingTop;
                break;
        }
        for (int i = 0; i < count; i++) {
            final View child = getVirtualChildAt(i);
            if (child == null) {
                childTop += measureNullChild(i);
            } else if (child.getVisibility() != GONE) {
                final int childWidth = child.getMeasuredWidth();
                final int childHeight = child.getMeasuredHeight();
                final LinearLayout.LayoutParams lp =
                        (LinearLayout.LayoutParams) child.getLayoutParams();
                int gravity = lp.gravity;
                if (gravity < 0) {
                    gravity = minorGravity;
                }
                final int layoutDirection = getLayoutDirection();
                final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
                switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
                    case Gravity.CENTER_HORIZONTAL:
                        childLeft = paddingLeft + ((childSpace - childWidth) / 2)
                                + lp.leftMargin - lp.rightMargin;
                        break;
                    case Gravity.RIGHT:
                        childLeft = childRight - childWidth - lp.rightMargin;
                        break;
                    case Gravity.LEFT:
                    default:
                        childLeft = paddingLeft + lp.leftMargin;
                        break;
                }
                if (hasDividerBeforeChildAt(i)) {
                    childTop += mDividerHeight;
                }
                childTop += lp.topMargin;
                setChildFrame(child, childLeft, childTop + getLocationOffset(child),
                        childWidth, childHeight);
                childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);
                i += getChildrenSkipCount(child, i);
            }
        }
    }

到这里就不得不提getWidth()、getHeight()和getMeasuredWidth()、getMeasuredHeight()这两对方法之间的区别(上面分析measure过程已经说过getMeasuredWidth()、getMeasuredHeight()必须在onMeasure之后使用才有效)。可以看出来getWidth()与getHeight()方法必须在layout(int l, int t, int r, int b)执行之后才有效。那我们看下View源码中这些方法的实现吧,如下:

     public final int getMeasuredWidth() {
        return mMeasuredWidth & MEASURED_SIZE_MASK;
    }

    public final int getMeasuredHeight() {
        return mMeasuredHeight & MEASURED_SIZE_MASK;
    }

    public final int getWidth() {
        return mRight - mLeft;
    }

    public final int getHeight() {
        return mBottom - mTop;
    }

    public final int getLeft() {
        return mLeft;
    }

    public final int getRight() {
        return mRight;
    }

    public final int getTop() {
        return mTop;
    }

    public final int getBottom() {
        return mBottom;
    }

OnDraw


public void draw(Canvas canvas) {  
    if (ViewDebug.TRACE_HIERARCHY) {  
        ViewDebug.trace(this, ViewDebug.HierarchyTraceType.DRAW);  
    }  
    final int privateFlags = mPrivateFlags;  
    final boolean dirtyOpaque = (privateFlags & DIRTY_MASK) == DIRTY_OPAQUE &&  
            (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);  
    mPrivateFlags = (privateFlags & ~DIRTY_MASK) | DRAWN;  
    // Step 1, draw the background, if needed  
    int saveCount;  
    if (!dirtyOpaque) {  
        final Drawable background = mBGDrawable;  
        if (background != null) {  
            final int scrollX = mScrollX;  
            final int scrollY = mScrollY;  
            if (mBackgroundSizeChanged) {  
                background.setBounds(0, 0,  mRight - mLeft, mBottom - mTop);  
                mBackgroundSizeChanged = false;  
            }  
            if ((scrollX | scrollY) == 0) {  
                background.draw(canvas);  
            } else {  
                canvas.translate(scrollX, scrollY);  
                background.draw(canvas);  
                canvas.translate(-scrollX, -scrollY);  
            }  
        }  
    }  
    final int viewFlags = mViewFlags;  
    boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;  
    boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;  
    if (!verticalEdges && !horizontalEdges) {  
        // Step 3, draw the content  
        if (!dirtyOpaque) onDraw(canvas);  
        // Step 4, draw the children  
        dispatchDraw(canvas);  
        // Step 6, draw decorations (scrollbars)  
        onDrawScrollBars(canvas);  
        // we're done...  
        return;  
    }  
}  

6个步骤在开始部分已经介绍。

View的invalidate和postInvalidate方法源码分析

参考博客:http://blog.csdn.net/yanbober/article/details/46128379

参考:http://blog.csdn.net/guolin_blog/article/details/16330267

作者:carter_yu 发表于2016/9/12 21:25:35 原文链接
阅读:127 评论:0 查看评论

AndroidStudio快捷键和使用技巧

$
0
0

最常用快捷键

快捷键 用途描述
ctrl+k commit到版本控制软件
ctrl+shift+K push到远程仓库
ctrl+T 拉取版本到本地
shift+F10 部署到模拟器运行
Ctrl+Shift+Space 自动补全代码
Ctrl+Alt+L 格式化代码
Ctrl + Shift + I 快速查看定义
Ctrl+E 可以显示最近编辑的文件列表
Shift+Click 可以关闭文件
Ctrl+[或] 可以跳到大括号的开头结尾
Ctrl+Shift+Backspace 可以跳转到上次编辑的地方
Ctrl+F12 可以显示当前文件的结构
Ctrl+F7 可以查询当前元素在当前文件中的引用,然后按F3可以选择
Ctrl+N 可以快速打开类
Ctrl+Shift+N 可以快速打开文件
Alt+Q 可以看到当前方法的声明
Ctrl+W 可以选择单词继而语句继而行继而函数
Alt+F1 可以将正在编辑的元素在各个面板中定位
Ctrl+P 可以显示参数信息
Ctrl+Shift+Insert 可以选择剪贴板内容并插入
Alt+Insert 可以生成构造器/Getter/Setter等
Ctrl+Alt+V 可以引入变量。例如把括号内的SQL赋成一个变量
Ctrl+Alt+T 可以把代码包在一块内,例如try/catch
Alt+Up and Alt+Down 可在方法间快速移动
Esc 该操作仅仅把光标移回编辑器。
Shift + Esc 该操作会关闭当前面板,然后把光标移回到编辑器
Ctrl + F12 展现当前类的大纲,并可以快速跳转
Ctrl + Tab 切换器,可以用来快速打开文件
Alt + ` 版本控制操作弹窗
Ctrl + Alt + M 提取方法
Ctrl + Alt + P 提取参数
Ctrl + Shift + J 合并行和文本
Ctrl + J 动态模版,非常好用
Ctrl + Shift + Up/Down 上下移动方法

常用快捷键

快捷键 用途描述
Ctrl + Shift + F12 隐藏所有面板
Ctrl + Shift + “+”/ “-“ 展开/折叠代码
Alt+回车 导入包,自动修正
Ctrl+N 查找类
Ctrl+Shift+N 查找文件
Ctrl+Alt+O 优化导入的类和包
Alt+Insert 生成代码(如get,set方法,构造函数等)
Ctrl+E或者Alt+Shift+C 最近访问的文件
Ctrl+shift+E 打开最近修改的文件
Ctrl+R 替换文本
Ctrl+F 查找文本
Ctrl+Shift+Space 自动补全代码
Ctrl+空格 代码提示
Ctrl+Alt+Space 类名或接口名提示
Ctrl+P 方法参数提示
Ctrl+Shift+Alt+N 查找类中的方法或变量
Alt+Shift+C 对比最近修改的代码
Shift+F6 重构-重命名
Ctrl+Shift+Up/Down 上下移动本行代码
Ctrl+X 剪切行
Ctrl+Y 删除行
Ctrl+D 复制行
Ctrl+/ 或 Ctrl+Shift+/ 注释(// 或者 /**/)
Ctrl+J 自动代码
Ctrl+E 最近访问的文件
Ctrl+H 显示类结构图
Ctrl+Q 显示注释文档
Alt+F1 查找代码所在位置
Alt+1 快速打开或隐藏工程面板
Ctrl+Alt+ left/right 返回至上次浏览的位置
Alt+ left/right 切换代码视图
Alt+ Up/Down 在方法间快速移动定位
Ctrl+Shift+Up/Down 代码向上/下移动

使用技巧

文章

书签

  • 添加/移除书签:F3(OS X) 、F11(Windows/Linux);

  • 添加/移除书签(带标记):Alt + F3(OS X)、Ctrl + F11(Windows/Linux);

  • 显示全部书签:Cmd + F3(OS X) 、Shift + F11(Windows/Linux),显示所有的书签列表,并且是可以搜索的

  • 上一个/下一个书签:无,可以在设置中设置快捷键

  • 更多:当你为某个书签指定了标记,你可以使用快捷键 Ctrl + 标记 来快速跳转到标记处,比如输入Ctrl + 1,跳到标记为1的书签处

与分支比对

  • 描述:假如你的项目是使用git来管理的,你可以将当前文件或者文件夹与其他的分支进行比对。比较有用的是可以让你了解到你与主分支有多少差别。

  • 调用:Menu → VCS → Git → Compare With Branch

在外部打开文件

  • 描述:通过这个快捷键,简单地点击Tab,就可以打开当前文件所在的位置或者该文件的任意上层路径。

  • 快捷键:Cmd + 单击Tab(OS X)、Ctrl + 点击Tab(Windows/Linux);

Select In

  • 描述:拿着当前文件然后问你在哪里选中该文件。恕我直言,最有用的就是在项目结构或者资源管理器中打开 该文件。每一个操作都有数字或者字母作为前缀,可以通过这个前缀来快速跳转。通常,我会 Alt + F1 然后 回车(Enter) 来打开项目视图,然后 再用 Alt + F1 在OS X的Finder里找到文件。你可以在文件中或者直接在项目视图里使用该操作

  • 快捷键:Alt + F1

Sublime Text式的多处选择

  • 描述:这个功能超级赞!该操作会识别当前选中字符串,选择下一个同样的字符串,并且添加一个光标。这意味着你可以在同一个文件里拥有多个光标,你可以同时在所有光标处输入任何东西

  • 快捷键:Ctrl + G(OS X)、Alt + J(Windows、Linux)

编写正则表达式

  • 描述:使用Java编写正则表达式是一件很困难的事,主要原因是:

  • 你必须得避开反斜杠;

  • 说实话,正则很难;

  • 看第二条。

  • IDE能帮我们干点啥呢?当然是一个舒服的界面来编写和测试正则啦~ - 快捷键:Alt + Enter → check regexp

使用Enter和Tab进行代码补全的差别

  • 描述:代码补全时,可以使用Enter或Tab来进行补全操作,但是两者是有差别的

  • 使用Enter时:从光标处插入补全的代码,对原来的代码不做任何操作

  • 使用Tab时:从光标处插入补全的代码,并删除后面的代码,直到遇到点号、圆括号、分号或空格为止

提取变量

  • 描述:这是一个提取变量的快捷操作。当你在没有写变量声明的直接写下值的时候,这是一个很方便生成变量声明的操作,同时还会给出一个建议的变量命名

  • 调用:Menu → Refactor → Extract → Variable

  • 快捷键:Cmd + Alt + V(OS X)、Ctrl + Alt + V(Windows/Linux);

  • 更多:当你需要改变变量声明的类型,例如使用 List 替代 ArrayList,可以按下Shift + Tab,就会显示所有可用的变量类型

取反补全

  • 描述:有时你自动补全一个布尔值,然后回到该值的前面添加一个感叹号来完成取反操作,现在通过使用输入!代替enter完成补全操作,就可以跳过这些繁琐的操作了

  • 快捷键:代码补全的时候,按下!即可(有时需要上下键选中候选项)

包裹代码

  • 描述: 该操作可以用特定代码结构包裹住选中的代码块,通常是if语句,循环,try/catch语句或者runnable语句。 如果你没有选中任何东西,该操作会包裹当前一整行

  • 快捷键:Cmd + Alt + T(OS X)、Ctrl + Alt + T(Windows/Linux)

移除包裹代码

  • 描述:该操作会移除周围的代码,它可能是一条if语句,一个while循环,一个try/catch语句甚至是一个runnable语句。该操作恰恰和包裹代码(Surround With)相反

  • 快捷键:Cmd + Shift + Delete(OS X)、Ctrl + Shift + Delete(Windows/Linux)

作者:qq_26891045 发表于2016/9/12 21:28:42 原文链接
阅读:80 评论:0 查看评论

Java反射

$
0
0

什么是反射

反射(Reflection)能够让运行于JVM中的程序检测和修改运行时的行为。”这个概念常常会和内省(Introspection)混淆,以下是这两个术语在Wikipedia中的解释:

  • 内省用于在运行时检测某个对象的类型和其包含的属性

  • 反射用于在运行时检测和修改某个对象的结构及其行为

从它们的定义可以看出,内省是反射的一个子集。有些语言支持内省,但并不支持反射,如C++

  • 内省示例:instanceof 运算符用于检测某个对象是否属于特定的类
if (obj instanceof Dog) {
    Dog d = (Dog) obj;
    d.bark();
}
  • 反射示例:Class.forName()方法可以通过类或接口的名称(一个字符串或完全限定名)来获取对应的Class对象。forName方法会触发类的初始化
// 使用反射
Class<?> c = Class.forName("classpath.and.classname");
Object dog = c.newInstance();
Method m = c.getDeclaredMethod("bark", new Class<?>[0]);
m.invoke(dog);
  • 在Java中,反射更接近于内省,因为你无法改变一个对象的结构。虽然一些API可以用来修改方法和属性的可见性,但并不能修改结构

为什么需要反射

反射能够让我们:

  • 在运行时检测对象的类型

  • 动态构造某个类的对象

  • 检测类的属性和方法

  • 任意调用对象的方法

  • 修改构造函数、方法、属性的可见性

  • 以及其他

反射是框架中常用的方法

  • 例如,JUnit 通过反射来遍历包含 @Test 注解的方法,并在运行单元测试时调用它们。(这个连接中包含了一些JUnit 的使用案例)

对于Web框架,开发人员在配置文件中定义他们对各种接口和类的实现。通过反射机制,框架能够快速地动态初始化所需要的类

  • 例如,Spring框架使用如下的配置文件:
<bean id="someID" class="com.programcreek.Foo">
    <property name="someField" value="someValue" />
</bean>
  • 当Spring容器处理元素时,会使用Class.forName(“com.programcreek.Foo”)来初始化这个类,并再次使用反射获取元素对应的setter方法,为对象的属性赋值

  • Servlet也会使用相同的机制:

<servlet>
    <servlet-name>someServlet</servlet-name>
    <servlet-class>com.programcreek.WhyReflectionServlet</servlet-class>
<servlet>

处理泛型

  • Java 5中引入了泛型的概念之后,Java反射API也做了相应的修改,以提供对泛型的支持。由于类型擦除机制的存在,泛型类中的类型参数等信息,在运行时刻是不存在的。JVM看到的都是原始类型。对此,Java 5对Java类文件的格式做了修订,添加了Signature属性,用来包含不在JVM类型系统中的类型信息。 在运行时刻,JVM会读取Signature属性的内容并提供给反射API来使用。
    比如在代码中声明了一个域是List类型的,虽然在运行时刻其类型会变成原始类型List,但是仍然可以通过反射来获取到所用的实际的类型参数
Field field = Pair.class.getDeclaredField("myList"); //myList的类型是List 
Type type = field.getGenericType(); 
if (type instanceof ParameterizedType) {     
    ParameterizedType paramType = (ParameterizedType) type;     
    Type[] actualTypes = paramType.getActualTypeArguments();     
    for (Type aType : actualTypes) {         
        if (aType instanceof Class) {         
            Class clz = (Class) aType;             
            System.out.println(clz.getName()); //输出java.lang.String         
        }     
    } 
}

Class类

在程序运行期间,Java运行时系统始终为所有的对象维护一个被称为运行时的类型标识。这个信息跟踪着这个对象所属的类。可以通过专门的Java类访问这些信息,保存这些信息的类被称为Class类
获取Class对象的三种方法:
1. 通过调用Object类中的getClass()方法返回一个Class对象

Object myObject = new Object();
Class myObjectClass = myObject.getClass();
  1. 通过调用静态方法forName获得类名对应的Class对象
String className = "java.util.Date";
Class cl = Class.forName(className);

注意:在使用Class.forName()方法时,必须提供一个类的全名,这个全名包括类所在的包的名字。

  1. 如果在调用该方法时,没有找到该路径的类,将会抛出ClassNotFoundException。
    获得Class对象的第三种方法非常简单,如果T是任意的Java类型,T.class将代表匹配的类对象
Class cl1 = Date.class;
Class cl2 = int.class;

补充:可通过下面方式访问类的父类

Object myObject = new Object();
Class myObjectClass = myObject.getSuperclass();

获取类名

通过Class对象可以获取两个版本的类名:
1. 通过getName()方法返回类的全限定类名(包含包名):

Class aClass = ... //获取Class对象
String className = aClass.getName();
  1. 通过getSimpleName()方法返回类名(不包含包名)
Class aClass = ... //获取Class对象
String className = aClass.getSimpleName();

获取修饰符

可以通过Class对象的getModifiers()方法来访问一个类的修饰符,该方法通过返回一个整型数值,用不同的位开关描述public/private/static等修饰符的使用状况。

Class aClass = ... //获取Class对象
int modifiers = aClass.getModifiers();

还可以使用Modifier类中的isPublic、isPrivate或isFinal判断方法或构造器是否是public、private或final。

Modifier.isAbstract(int modifiers);
Modifier.isFinal(int modifiers);
Modifier.isInterface(int modifiers);
Modifier.isNative(int modifiers);
Modifier.isPrivate(int modifiers);
Modifier.isProtected(int modifiers);
Modifier.isPublic(int modifiers);
Modifier.isStatic(int modifiers);
Modifier.isStrict(int modifiers);
Modifier.isSynchronized(int modifiers);
Modifier.isTransient(int modifiers);
Modifier.isVolatile(int modifiers);

获取包信息

可通过以下方式获取包信息:

...
Object object = new Object();
Class cl = object.getClass();
System.out.println(cl.getPackage());
...
/*output
package java.lang, Java Platform API Specification, version 1.7
*/

获取实现的接口集合

可通过调用Class对象的getInterfaces()方法获取一个类实现的接口集合

Class aClass = ... //获取Class对象
Class[] interfaces = aClass.getInterfaces();

注意:getInterfaces()方法仅仅只返回当前类所实现的接口,不包括当前类的父类所实现的接口。

获取构造器

可通过调用Class对象的getConstructors()方法获取一个类的构造函数

Class aClass = ... //获取Class对象
Constructor[] constructors = aClass.getConstructors();

返回的Constructor数组包含每一个声明为public的构造方法。


还可以通过给定的构造方法的参数类型获取指定的构造方法,如下:返回的构造方法的方法参数为String类型

Class aClass = ... //获取Class对象
Constructor constructor = aClass.getConstructor(new Class[]{String.class});

注意:如果没有指定的构造方法能匹配给定的方法参数,则会抛出NoSuchMethodException异常。


还可以获取指定构造方法的方法参数信息

Constructor constructor = ... //获取Constructor对象
Class[] parameterTypes = constructor.getParameterTypes();

利用Constructor对象实例化一个类

Constructor constructor = ... //获取Constructor对象
Class[] parameterTypes = constructor.getParameterTypes();

获取方法

可通过调用Class对象的getMethods()方法获取一个类的所有方法

Class aClass = ...//获取Class对象
Method[] methods = aClass.getMethods();

返回的Method对象数组包含了指定类中声明为public的所有变量集合。


还可以通过具体的参数类型来获取指定的方法

Class  aClass = ...//获取Class对象
Method method = aClass.getMethod("doSomething", new Class[]{String.class});

注意:如果根据给定的方法名称以及参数类型无法匹配到相应的方法,则会抛出NoSuchMethodException


获取指定方法的方法参数以及返回类型

Method method = ... //获取Class对象
Class[] parameterTypes = method.getParameterTypes();
Class returnType = method.getReturnType();

通过Method对象调用方法

//获取一个方法名为doSomesthing,参数类型为String的方法
Method method = MyObject.class.getMethod("doSomething", String.class);
Object returnValue = method.invoke(null, "parameter-value1");

获取变量

可通过调用Class对象的getFields()方法获取一个类的成员变量

Class aClass = ... //获取Class对象
Field[] method = aClass.getFields();

返回的Field对象数组包含了指定类中声明为public的所有变量集合


还可以通过具体的变量名称获取指定的变量

Class aClass = ... //获取Class对象
Field[] method = aClass.getFields("someField");

注意:在调用getField()方法时,如果根据给定的方法参数没有找到对应的变量,那么就会抛出NoSuchFieldException


通过调用Field对象的getName()方法获取它的变量名称

Field field = ... //获取Field对象
String fieldName = field.getName();

通过调用Field对象getType()方法来获取一个变量的类型(如String, int等等)

Class aClass = ... //获取Class对象
Field field = aClass.getField("someField");
Object fieldType = field.getType();

通过调用Field.get()Field.set()方法获取或设置(get/set)变量值

Class  aClass = MyObject.class
Field field = aClass.getField("someField");

MyObject objectInstance = new MyObject();

Object value = field.get(objectInstance);

field.set(objetInstance, value);

获取指定类的getters和setters

  • Getter:Getter方法的名字以get开头,没有方法参数,返回一个值
  • Setter:Setter方法的名字以set开头,有一个方法参数

一个获取getter方法和setter方法的例子:

public static void printGettersSetters(Class aClass){
  Method[] methods = aClass.getMethods();

  for(Method method : methods){
    if(isGetter(method)) System.out.println("getter: " + method);
    if(isSetter(method)) System.out.println("setter: " + method);
  }
}

public static boolean isGetter(Method method){
  if(!method.getName().startsWith("get"))      return false;
  if(method.getParameterTypes().length != 0)   return false;
  if(void.class.equals(method.getReturnType()) return false;
  return true;
}

public static boolean isSetter(Method method){
  if(!method.getName().startsWith("set")) return false;
  if(method.getParameterTypes().length != 1) return false;
  return true;
}

获取私有变量

可以通过调用Class.getDeclaredField(String name)方法或者Class.getDeclaredFields()方法获取私有变量和受保护变量,不包括超类的成员;Class.getField(String name)Class.getFields()只会返回公有变量,其中包括超类的公有变量,而无法获取私有变量。

public class PrivateObject {

  private String privateString = null;

  public PrivateObject(String privateString) {
    this.privateString = privateString;
  }
}
/**********************************************************************/
PrivateObject privateObject = new PrivateObject("The Private Value");

Field privateStringField = PrivateObject.class.
            getDeclaredField("privateString");

privateStringField.setAccessible(true);

String fieldValue = (String) privateStringField.get(privateObject);
System.out.println("fieldValue = " + fieldValue);

这个例子会输出”fieldValue = The Private Value”,The Private ValuePrivateObject实例的privateString私有变量的值,注意调用PrivateObject.class.getDeclaredField("privateString")方法会返回一个私有变量,这个方法返回的变量是定义在PrivateObject类中的而不是在它的父类中定义的变量。
注意:privateStringField.setAccessible(true)这行代码,通过调用setAccessible()方法会关闭指定类Field实例的反射访问检查,这行代码执行之后不论是私有的、受保护的以及包访问的作用域,你都可以在任何地方访问,即使你不在他的访问权限作用域之内。但是你如果你用一般代码来访问这些不在你权限作用域之内的代码依然是不可以的,在编译的时候就会报错。

获取私有方法

可以通过调用Class.getDeclaredMethod(String name, Class[] parameterTypes)或者Class.getDeclaredMethods()方法获取私有方法,不包括超类的成员;Class.getMethod(String name, Class[] parameterTypes)Class.getMethods()方法,只会返回公有的方法,其中包括超类的公有方法,而无法获取私有方法。

public class PrivateObject {

  private String privateString = null;

  public PrivateObject(String privateString) {
    this.privateString = privateString;
  }

  private String getPrivateString(){
    return this.privateString;
  }
}
/*******************************************************************/
PrivateObject privateObject = new PrivateObject("The Private Value");

Method privateStringMethod = PrivateObject.class.
        getDeclaredMethod("getPrivateString", null);

privateStringMethod.setAccessible(true);

String returnValue = (String)
        privateStringMethod.invoke(privateObject, null);

System.out.println("returnValue = " + returnValue);

这个例子会输出"returnValue = The Private Value"The Private ValuePrivateObject实例的getPrivateString()方法的返回值。PrivateObject.class.getDeclaredMethod("privateString")方法会返回一个私有方法,这个方法是定义在PrivateObject类中的而不是在它的父类中定义的。
注意:Method.setAcessible(true)这行代码,通过调用setAccessible()方法会关闭指定类的Method实例的反射访问检查,这行代码执行之后不论是私有的、受保护的以及包访问的作用域,你都可以在任何地方访问,即使你不在他的访问权限作用域之内。但是你如果你用一般代码来访问这些不在你权限作用域之内的代码依然是不可以的,在编译的时候就会报错。

获取注解

什么是注解?
注解是Java 5的一个新特性。注解是插入你代码中的一种注释或者说是一种元数据(meta data)。这些注解信息可以在编译期使用预编译工具进行处理(pre-compiler tools),也可以在运行期使用Java反射机制进行处理。
下面是一个类注解的例子:

@MyAnnotation(name="someName",  value = "Hello World")
public class TheClass {
}

在TheClass类定义的上面有一个@MyAnnotation的注解。注解的定义与接口的定义相似,下面是MyAnnotation注解的定义:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)

public @interface MyAnnotation {
  public String name();
  public String value();
}

在interface前面的@符号表名这是一个注解,一旦你定义了一个注解之后你就可以将其应用到你的代码中。

说明:在注解定义中的两个指示@Retention(RetentionPolicy.RUNTIME)和@Target(ElementType.TYPE),说明了这个注解该如何使用。
1. @Retention(RetentionPolicy.RUNTIME)表示这个注解可以在运行期通过反射访问。如果你没有在注解定义的时候使用这个指示那么这个注解的信息不会保留到运行期,这样反射就无法获取它的信息。
2. @Target(ElementType.TYPE) 表示这个注解只能用在类型上面(比如类跟接口)。你同样可以把Type改为Field或者Method,或者你可以不用这个指示,这样的话你的注解在类,方法和变量上就都可以使用了。


类注解
下是一个访问类注解的例子

Class aClass = TheClass.class;
Annotation[] annotations = aClass.getAnnotations();

for(Annotation annotation : annotations){
    if(annotation instanceof MyAnnotation){
        MyAnnotation myAnnotation = (MyAnnotation) annotation;
        System.out.println("name: " + myAnnotation.name());
        System.out.println("value: " + myAnnotation.value());
    }
}

你还可以像下面这样指定访问一个类的注解

Class aClass = TheClass.class;
Annotation annotation = aClass.getAnnotation(MyAnnotation.class);

if(annotation instanceof MyAnnotation){
    MyAnnotation myAnnotation = (MyAnnotation) annotation;
    System.out.println("name: " + myAnnotation.name());
    System.out.println("value: " + myAnnotation.value());
}

方法注解
下面是一个方法注解的例子

public class TheClass {
  @MyAnnotation(name="someName",  value = "Hello World")
  public void doSomething(){}
}

你可以像这样访问方法注解:

Method method = ... //获取方法对象
Annotation[] annotations = method.getDeclaredAnnotations();

for(Annotation annotation : annotations){
    if(annotation instanceof MyAnnotation){
        MyAnnotation myAnnotation = (MyAnnotation) annotation;
        System.out.println("name: " + myAnnotation.name());
        System.out.println("value: " + myAnnotation.value());
    }
}

你可以像这样访问指定的方法注解

Method method = ... // 获取方法对象
Annotation annotation = method.getAnnotation(MyAnnotation.class);

if(annotation instanceof MyAnnotation){
    MyAnnotation myAnnotation = (MyAnnotation) annotation;
    System.out.println("name: " + myAnnotation.name());
    System.out.println("value: " + myAnnotation.value());
}

参数注解
方法参数也可以添加注解,就像下面这样

public class TheClass {
  public static void doSomethingElse(
        @MyAnnotation(name="aName", value="aValue") String parameter){
  }
}

你可以通过Method对象来访问方法参数注解

Method method = ... //获取方法对象
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
Class[] parameterTypes = method.getParameterTypes();

int i=0;
for(Annotation[] annotations : parameterAnnotations){
  Class parameterType = parameterTypes[i++];

  for(Annotation annotation : annotations){
    if(annotation instanceof MyAnnotation){
        MyAnnotation myAnnotation = (MyAnnotation) annotation;
        System.out.println("param: " + parameterType.getName());
        System.out.println("name : " + myAnnotation.name());
        System.out.println("value: " + myAnnotation.value());
    }
  }
}

需要注意的是Method.getParameterAnnotations()方法返回一个注解类型的二维数组,每一个方法的参数包含一个注解数组。


变量注解
下面是一个变量注解的例子

public class TheClass {

  @MyAnnotation(name="someName",  value = "Hello World")
  public String myField = null;
}

你可以像这样来访问变量的注解

Field field = ... //获取方法对象</pre>
<pre>Annotation[] annotations = field.getDeclaredAnnotations();

for(Annotation annotation : annotations){
 if(annotation instanceof MyAnnotation){
 MyAnnotation myAnnotation = (MyAnnotation) annotation;
 System.out.println("name: " + myAnnotation.name());
 System.out.println("value: " + myAnnotation.value());
 }
}

你可以像这样访问指定的变量注解

Field field = ...//获取方法对象</pre>
<pre>
Annotation annotation = field.getAnnotation(MyAnnotation.class);

if(annotation instanceof MyAnnotation){
 MyAnnotation myAnnotation = (MyAnnotation) annotation;
 System.out.println("name: " + myAnnotation.name());
 System.out.println("value: " + myAnnotation.value());
}

参考资料

  1. Java Reflection教程
作者:qq_26891045 发表于2016/9/12 21:32:19 原文链接
阅读:79 评论:0 查看评论

Android 7.0 Vold工作流程

$
0
0

一、Vold工作机制

Vold是Volume Daemon的缩写,它是Android平台中外部存储系统的管控中心,是管理和控制Android平台外部存储设备的后台进程。其功能主要包括:SD卡的插拔事件检测、SD卡挂载、卸载、格式化等。

如上图所示,Vold中的NetlinkManager模块接收来自Linux Kernel的uevent消息。
NetlinkManager将这些消息转发给VolumeManager模块。VolumeManager会对应做一些操作,然后把相关信息通过CommandListener发送给MountService。
MountService根据收到的消息后,根据情况会利用CommandListener发送相关的处理命令给VolumeManager做进一步处理。
CommandListener模块内部封装了一个Socket用于跨进程通信。它一方面接收来自MountService的控制命令,另一方面VolumeManager通过它将消息发送给MountService。

Tips:
Netlink是Linux系统中用户空间进程和Kernel进行通信的一种机制,是基于Socket的异步通信机制。
通过这种机制,位于用户空间的进程可以接收来自Kernel的一些信息,同时用户空间进程也可以利用Netlink向Kernel发送一些控制命令。

二、Vold进程启动过程
Vold进程启动文件定义于system/vold/vold.rc文件中:

service vold /system/bin/vold \
        --blkid_context=u:r:blkid:s0 --blkid_untrusted_context=u:r:blkid_untrusted:s0 \
        --fsck_context=u:r:fsck:s0 --fsck_untrusted_context=u:r:fsck_untrusted:s0
    class core
    socket vold stream 0660 root mount
    socket cryptd stream 0660 root mount
    ioprio be 2
    writepid /dev/cpuset/foreground/tasks

被init进程启动后,将调用system/vold/main.cpp中的main函数:

int main(int argc, char** argv) {
    .............
    VolumeManager *vm;
    CommandListener *cl;
    .............
    NetlinkManager *nm;

    //解析vold.rc中定义的blkid和fsck相关的参数
    parse_args(argc, argv);
    ..............
    // Quickly throw a CLOEXEC on the socket we just inherited from init
    //这里的含义不是很明白
    //对于fcntl(fd, F_SETFD, FD_CLOEXEC)函数
    //FD_CLOEXEC表示当程序执行exec函数时, fd将被系统自动关闭, 不传递给exec创建的新进程,以免fd在子进程中仍然有效
    //但在init进程中,是先fork出子进程,然后在进程中创建出socket,才执行exec函数,有必要使用fcntl函数么?
    fcntl(android_get_control_socket("vold"), F_SETFD, FD_CLOEXEC);
    fcntl(android_get_control_socket("cryptd"), F_SETFD, FD_CLOEXEC);

    //创建文件夹/dev/block/vold
    mkdir("/dev/block/vold", 0755);
    .........
    //创建VolumeManager
    if (!(vm = VolumeManager::Instance())) {
        LOG(ERROR) << "Unable to create VolumeManager";
        exit(1);
    }

    //创建NetlinkManager
    if (!(nm = NetlinkManager::Instance())) {
        LOG(ERROR) << "Unable to create NetlinkManager";
        exit(1);
    }
    ...................
    //创建CommandListener
    cl = new CommandListener();
    ............
    vm->setBroadcaster((SocketListener *) cl);
    nm->setBroadcaster((SocketListener *) cl);

    //启动VolumeManager
    if (vm->start()) {
        PLOG(ERROR) << "Unable to start VolumeManager";
        exit(1);
    }

    //根据配置文件初始化VolumeManager
    if (process_config(vm)) {
        PLOG(ERROR) << "Error reading configuration... continuing anyways";
    }

    //启动NetlinkManager
    if (nm->start()) {
        PLOG(ERROR) << "Unable to start NetlinkManager";
        exit(1);
    }

    //与ueventd进程进行冷启动类似,此处通过往/sys/block目录下对应的uevent文件写"add\n"来触发内核发送uevent消息
    coldboot("/sys/block");

    //启动CommandListener
    if (cl->startListener()) {
        PLOG(ERROR) << "Unable to start CommandListener";
        exit(1);
    }
    .......
    // Eventually we'll become the monitoring thread
    while(1) {
        sleep(1000);
    }

    LOG(ERROR) << "Vold exiting";
    exit(0);
}

从上面的代码不难看出,Vold进程的main函数中,创建并启动其子模块VolumeManager、NetlinkManager和CommandListener后,就不再执行实际的工作了。
以后Vold进程具体的工作就会交付给子模块进行处理。

三、Vold进程中各模块分析
为了进一步了解整个Vold进程的主要工作流程,接下来我们分析一下其主要模块的工作流程。

1、NetlinkManager模块

1.1 NetlinkManager的创建和启动
在Vold的main函数中,调用NetlinkManager::Instance创建出NetlinkManager:

NetlinkManager *NetlinkManager::Instance() {
    if (!sInstance)
        sInstance = new NetlinkManager();
    return sInstance;
}

//mBroadcaster的类型为SocketListener
NetlinkManager::NetlinkManager() {
    mBroadcaster = NULL;
}

从上面的代码可以看到,NetlinkManager的创建比较简单。

在创建出NetlinkManager后,Vold调用了NetlinkManager的setBroadcaster函数:

void setBroadcaster(SocketListener *sl) { mBroadcaster = sl; }

依然言简意赅。
这里唯一需要说明的是,Android这里的设计看起来比较很奇怪,虽然NetlinkManager设置了CommandListener对象,但它并没有通过CommandListener发送消息和接收命令。

配置好NetlinkManager后,Vold就调用了NetlinkManger的start函数:

int NetlinkManager::start() {
    //以下定义并初始化socket的地址结构
    struct sockaddr_nl nladdr;
    int sz = 64 * 1024;
    int on = 1;

    memset(&nladdr, 0, sizeof(nladdr));
    nladdr.nl_family = AF_NETLINK;
    nladdr.nl_pid = getpid();
    nladdr.nl_groups = 0xffffffff;

    //创建PF_NETLINK地址簇的socket,NETLINK_KOBJECT_UEVENT表示该socket将接收内核的Uevent事件
    if ((mSock = socket(PF_NETLINK, SOCK_DGRAM | SOCK_CLOEXEC,
            NETLINK_KOBJECT_UEVENT)) < 0) {
        SLOGE("Unable to create uevent socket: %s", strerror(errno));
        return -1;
    }

    //setsockopt设置socket的选项,此处设置socket的接收缓冲区大小为64 * 1024
    if (setsockopt(mSock, SOL_SOCKET, SO_RCVBUFFORCE, &sz, sizeof(sz)) < 0) {
        SLOGE("Unable to set uevent socket SO_RCVBUFFORCE option: %s", strerror(errno));
        goto out;
    }

    //此处设置允许接收凭证相关的信息
    if (setsockopt(mSock, SOL_SOCKET, SO_PASSCRED, &on, sizeof(on)) < 0) {
        SLOGE("Unable to set uevent socket SO_PASSCRED option: %s", strerror(errno));
        goto out;
    }

    //将创建出的socket绑定到之前的地址上,此时socket可以收到Kernel的数据了
    if (bind(mSock, (struct sockaddr *) &nladdr, sizeof(nladdr)) < 0) {
        SLOGE("Unable to bind uevent socket: %s", strerror(errno));
        goto out;
    }

    //创建并启动一个NetlinkHandler
    mHandler = new NetlinkHandler(mSock);
    if (mHandler->start()) {
        SLOGE("Unable to start NetlinkHandler: %s", strerror(errno));
        goto out;
    }

    return 0;

out:
    close(mSock);
    return -1;
}

通过上面的代码不难看出,其实NetlinkManager启动后就是创建一个可以接收Kernel消息的socket,并以此socket构建并启动NetlinkHandler。
可以预见NetlinkHandler将用来处理socket收到的信息。

1.2 NetlinkHandler

NetlinkHandler::NetlinkHandler(int listenerSocket) :
                NetlinkListener(listenerSocket) {
}

NetlinkHandler初始化时,将与Kernel通信的socket描述符传入到父类NetlinkListener中。

NetlinkListener::NetlinkListener(int socket) :
                          SocketListener(socket, false) {
    mFormat = NETLINK_FORMAT_ASCII;
}

NetlinkListener又进一步调用其父类SocketListener:

SocketListener::SocketListener(int socketFd, bool listen) {
    init(NULL, socketFd, listen, false);
}

//socektName为null, listen和useCmdNum的值均为false
void SocketListener::init(const char *socketName, int socketFd, bool listen, bool useCmdNum) {
    mListen = listen;
    mSocketName = socketName;
    mSock = socketFd;
    mUseCmdNum = useCmdNum;
    //初始化一个mutex
    pthread_mutex_init(&mClientsLock, NULL);

    //SocketClientCollection用于存储与Socket服务端通信的客户端
    mClients = new SocketClientCollection();
}

从上面的代码可以看出,NetlinkHandler对应的继承体系如下图所示:

创建完NetlinkHandler后,NetlinkManager调用了NetlinkHandler的start方法:

int NetlinkHandler::start() {
    //根据继承体系,实际上调用了SocketListener的startListenr函数
    return this->startListener();
}

int SocketListener::startListener() {
    return startListener(4);
}

int SocketListener::startListener(int backlog) {
    //前面代码已经提及,构造NetlinkHandler时,mSocketName为null,略去部分代码
    ................
    //mListen的参数也为false,表明mSocket并不是一个服务器端
    if (mListen && listen(mSock, backlog) < 0) {
        ..............
    } else if (!mListen)
        //利用mSocket构造SocketClient加入到mClients中
        //这个SocketClient并不是真实客户端的代表,此处只是为了代码和操作的统一
        mClients->push_back(new SocketClient(mSock, false, mUseCmdNum));

    //pipe系统调用将创建一个匿名管道,mCtrlPipe是一个int类型的二元数组
    //其中mCtrlPipe[0]用于从管道读数据,mCtrlPipe[1]用于往管道写数据
    if (pipe(mCtrlPipe)) {
        SLOGE("pipe failed (%s)", strerror(errno));
        return -1;
    }

    //创建一个工作线程,线程的执行函数为threadStart
    if (pthread_create(&mThread, NULL, SocketListener::threadStart, this)) {
        SLOGE("pthread_create (%s)", strerror(errno));
        return -1;
    }

    return 0;
}

至此,我们知道了NetlinkHandler启动后,创建了一个工作线程,用于接收和处理数据。现在进一步看看threadStart函数:

void *SocketListener::threadStart(void *obj) {
    SocketListener *me = reinterpret_cast<SocketListener *>(obj);

    //调用SocketListener的runListener函数
    me->runListener();
    pthread_exit(NULL);
    return NULL;
}

void SocketListener::runListener() {
    SocketClientCollection pendingList;

    //无线循环,接收socket收到的数据
    while(1) {
        SocketClientCollection::iterator it;
        fd_set read_fds;
        int rc = 0;
        int max = -1;

        //将指定的文件描述符集清空,系统分配时默认是不清空的
        FD_ZERO(&read_fds);
        ..........
        //在文件描述符集中增加一个新的描述符
        FD_SET(mCtrlPipe[0], &read_fds);

        //max将与select函数
        if (mCtrlPipe[0] > max)
            max = mCtrlPipe[0];

        pthread_mutex_lock(&mClientsLock);
        for (it = mClients->begin(); it != mClients->end(); ++it) {
            // NB: calling out to an other object with mClientsLock held (safe)
            int fd = (*it)->getSocket();
            FD_SET(fd, &read_fds);
            if (fd > max) {
                max = fd;
            }
        }
        pthread_mutex_unlock(&mClientsLock);
        ...............
        //监听是否有数据到来
        if ((rc = select(max + 1, &read_fds, NULL, NULL, NULL)) < 0) {
            ............
        } else if (!rc) 
            continue;

        //FD_ISSET用于测试指定的文件描述符是否在该集合中,前面已经加入了
        if (FD_ISSET(mCtrlPipe[0], &read_fds)) {
            char c = CtrlPipe_Shutdown;
            TEMP_FAILURE_RETRY(read(mCtrlPipe[0], &c, 1));
            //如果从管道中读出CtrlPipe_Shutdown,则退出工作线程
            if (c == CtrlPipe_Shutdown) {
                break;
            }
            continue;
        }

        //如果mSock是服务器端,进入这个分支,NetlinkHandler中的mSock并不是服务器端,此处仅作了解
        if (mListen && FD_ISSET(mSock, &read_fds)) {
            sockaddr_storage ss;
            sockaddr* addrp = reinterpret_cast<sockaddr*>(&ss);
            socklen_t alen;
            int c;

            do {
                alen = sizeof(ss);
                //服务器端接收客户端请求
                c = accept(mSock, addrp, &alen);
                SLOGV("%s got %d from accept", mSocketName, c);
            } while (c < 0 && errno == EINTR);
            if (c < 0) {
                SLOGE("accept failed (%s)", strerror(errno));
                sleep(1);
                continue;
            }
            fcntl(c, F_SETFD, FD_CLOEXEC);
            pthread_mutex_lock(&mClientsLock);
            //将新的客户端请求加入到mClients中
            mClients->push_back(new SocketClient(c, true, mUseCmdNum));
            pthread_mutex_unlock(&mClientsLock);
        }

        /* Add all active clients to the pending list first */
        //这里引入了pendingList,主要是针对服务端提出的
        //当mSocket是服务端的时候,上面的代码将会增加新的mClient,但在下一次循环之前,这些mClient还未被加入到read_fds中
        pendingList.clear();
        pthread_mutex_lock(&mClientsLock);

        //由于NetlinkHandler中的mSocket不是服务端,因此mClients中实际上只有mSocket自己(前面startListener中加入的)
        for (it = mClients->begin(); it != mClients->end(); ++it) {
            SocketClient* c = *it;
            // NB: calling out to an other object with mClientsLock held (safe)
            int fd = c->getSocket();
            if (FD_ISSET(fd, &read_fds)) {
                //待处理的SocketClient加入到pendingList中
                pendingList.push_back(c);
                c->incRef();
            }
        }
        pthread_mutex_unlock(&mClientsLock);

        while (!pendingList.empty()) {
            it = pendingList.begin();
            SocketClient* c = *it;
            pendingList.erase(it);
            //调用子类的onDataAvailable函数处理收到的数据
            if (!onDataAvailable(c)) {
                //返回false时,需要关闭该SocketClient
                //关闭SocketClient将直接操作mClients对象
                release(c, false);
            }
            c->decRef();
        }
    }
}

上面的代码看起来比较复杂,主要是因为考虑到了Socket作为服务端的情况。
在NetlinkHandler中Socket仅作为客户端接收数据,因此在上面的代码中,其实就是利用子类的onDataAvailable函数处理收到的数据而已。

bool NetlinkListener::onDataAvailable(SocketClient *cli)
{
    int socket = cli->getSocket();
    ssize_t count;
    uid_t uid = -1;
    .................
    //这里用uevent_kernel_recv函数,从socket中取出Uevent数据
    count = TEMP_FAILURE_RETRY(uevent_kernel_recv(socket,
            mBuffer, sizeof(mBuffer), require_group, &uid));
    if (count < 0) {
        ........
        return false;
    }

    NetlinkEvent *evt = new NetlinkEvent();
    //mFormat初始化时指定为NETLINK_FORMAT_ASCII
    //此处将Uevent数据解码成NetlinkEvent,然后调用子类的onEvent进行处理
    if (evt->decode(mBuffer, count, mFormat)) {
        onEvent(evt);
    } else if (mFormat != NETLINK_FORMAT_BINARY) {
        ..........
    }

    delete evt;
    return true;
}

上面的代码比较简单,其实就是从socket中的字节流中取出Uevent事件,然后将这些事件解码成NetlinkEvent,然后利用子类的onEvent做进一步处理。

void NetlinkHandler::onEvent(NetlinkEvent *evt) {
    VolumeManager *vm = VolumeManager::Instance();
    const char *subsys = evt->getSubsystem();

    if (!subsys) {
        SLOGW("No subsystem found in netlink event");
        return;
    }

    if (!strcmp(subsys, "block")) {
        //将NetlinkEvent递交给VolumeManager处理
        vm->handleBlockEvent(evt);
    }
}

最后总结一下NetlinkManager模块的工作,如上图所示:
1、NetlinkManager启动后,将创建出与Kernel通信的socket,并用此socket创建出NetlinkHandler。
2、NetlinkHandler启动后,将创建出工作线程(其父类函数完成)。
3、工作线程启动后,将负责监听socket是否有数据到来。
4、当工作线程监听到数据到来后,负责将数据递交给NetlinkHandler。
5、NetlinkHandler负责从socket中的数据中解析出Uevent,并进一步解码成NetlinkEvent,以递交给VolumeManager。

2、VolumeManager模块

2.1 VolumeManager的创建和启动
在Vold的main函数中,调用VolumeManager的instance函数创建VolumeManager:

VolumeManager *VolumeManager::Instance() {
    if (!sInstance)
        sInstance = new VolumeManager();
    return sInstance;
}

VolumeManager::VolumeManager() {
    mDebug = false;
    mActiveContainers = new AsecIdCollection();
    mBroadcaster = NULL;
    mUmsSharingCount = 0;
    mSavedDirtyRatio = -1;
    // set dirty ratio to 0 when UMS is active
    mUmsDirtyRatio = 0;
}

容易看出VolumeManager也是单例模式创建的。
接着,Vold进程利用VolumeManager的setBroadcaster函数,将Commandlistener对象赋予VolumeManager。

void setBroadcaster(SocketListener *sl) { mBroadcaster = sl; }

完成VolumeManager的创建后,Vold进程调用start函数,启动VolumeManager:

int VolumeManager::start() {
    // Always start from a clean state by unmounting everything in
    // directories that we own, in case we crashed.
    unmountAll();

    // Assume that we always have an emulated volume on internal
    // storage; the framework will decide if it should be mounted.
    CHECK(mInternalEmulated == nullptr);
    mInternalEmulated = std::shared_ptr<android::vold::VolumeBase>(
            new android::vold::EmulatedVolume("/data/media"));
    mInternalEmulated->create();

    return 0;
}

从上面的代码可以看出,VolumeManager启动后就干了两件事:
1、清楚所有已挂载的设备。正如注释所说的,通过这种方式可以让VolumeManager每次都从一个确定的“干净”的状态启动,避免之前出现Vold进程出现过crash。
看看unmountAll函数:

int VolumeManager::unmountAll() {
    std::lock_guard<std::mutex> lock(mLock);

    // First, try gracefully unmounting all known devices
    if (mInternalEmulated != nullptr) {
        mInternalEmulated->unmount();
    }
    for (auto disk : mDisks) {
        disk->unmountAll();
    }

    // Worst case we might have some stale mounts lurking around, so
    // force unmount those just to be safe.
    FILE* fp = setmntent("/proc/mounts", "r");
    if (fp == NULL) {
        SLOGE("Error opening /proc/mounts: %s", strerror(errno));
        return -errno;
    }

    // Some volumes can be stacked on each other, so force unmount in
    // reverse order to give us the best chance of success.
    std::list<std::string> toUnmount;
    mntent* mentry;
    while ((mentry = getmntent(fp)) != NULL) {
        if (strncmp(mentry->mnt_dir, "/mnt/", 5) == 0
                || strncmp(mentry->mnt_dir, "/storage/", 9) == 0) {
            toUnmount.push_front(std::string(mentry->mnt_dir));
        }
    }
    endmntent(fp);

    for (auto path : toUnmount) {
        SLOGW("Tearing down stale mount %s", path.c_str());
        android::vold::ForceUnmount(path);
    }

    return 0;
}

unmountAll的内容比较简单,同时注释清晰,此处不再赘述。

2、创建一个内部的挂载设备。
mInternalEmulated是一个VolumeBase类型的对象,我们看看其create函数:

status_t VolumeBase::create() {
    CHECK(!mCreated);

    mCreated = true;
    //doCreate进行实际的创建
    status_t res = doCreate();
    //通过CommandListener通知框架中的MountService
    notifyEvent(ResponseCode::VolumeCreated,
            StringPrintf("%d \"%s\" \"%s\"", mType, mDiskId.c_str(), mPartGuid.c_str()));
    setState(State::kUnmounted);
    return res;
}

2.2 配置VolumeManager
当Vold创建并启动完VolumeManager后,就调用process_config函数对VolumeManager进行配置:

static int process_config(VolumeManager *vm) {
    //读取默认的fstab文件
    std::string path(android::vold::DefaultFstabPath());
    fstab = fs_mgr_read_fstab(path.c_str());
    ...........
    /* Loop through entries looking for ones that vold manages */
    ........
    for (int i = 0; i < fstab->num_entries; i++) {
        //是否能被被vold管理
        if (fs_mgr_is_voldmanaged(&fstab->recs[i])) {
            //根据fstab文件中设备的信息构造sysPattern、nickname和flags
            ...........
            //
            vm->addDiskSource(std::shared_ptr<VolumeManager::DiskSource>(
                    new VolumeManager::DiskSource(sysPattern, nickname, flags)));
        }
    }
    ................
}

结合代码,我们知道process_config其实就是解析fstab文件,然后设置一些存储设备的挂载点。

2.3 NetlinkManager与VolumeManager之间的交互
在前面介绍NetlinkManager时,我们知道当NetlinkManager收到Kernel的事件后,将利用NetlinkHandler通知VolumeManager:

void NetlinkHandler::onEvent(NetlinkEvent *evt) {
    VolumeManager *vm = VolumeManager::Instance();
    const char *subsys = evt->getSubsystem();

    if (!subsys) {
        SLOGW("No subsystem found in netlink event");
        return;
    }

    if (!strcmp(subsys, "block")) {
        vm->handleBlockEvent(evt);
    }
}

我们看看VolumeManager的handleBlockeEvent:

void VolumeManager::handleBlockEvent(NetlinkEvent *evt) {
    ..............
    //取出主设备号和次设备号
    int major = atoi(evt->findParam("MAJOR"));
    int minor = atoi(evt->findParam("MINOR"));
    dev_t device = makedev(major, minor);

    switch (evt->getAction()) {
    case NetlinkEvent::Action::kAdd: {
        //创建新的disk
        .........
        auto disk = new android::vold::Disk(eventPath, device,
                source->getNickname(), flags);
        disk->create();
        mDisks.push_back(std::shared_ptr<android::vold::Disk>(disk));
        break;
    }
    case NetlinkEvent::Action::kChange: {
        ..........
        //改变对应的disk信息
        for (auto disk : mDisks) {
            if (disk->getDevice() == device) {
                disk->readMetadata();
                disk->readPartitions();
            }
        }
        break;
    }
    case NetlinkEvent::Action::kRemove: {
        //移除对应的disk
        auto i = mDisks.begin();
        while (i != mDisks.end()) {
            if ((*i)->getDevice() == device) {
                (*i)->destroy();
                i = mDisks.erase(i);
            } else {
                ++i;
            }
        }
        break;
    }
    default: {
        LOG(WARNING) << "Unexpected block event action " << (int) evt->getAction();
        break;
    }
    }
}

至此VolumeManager的主要工作介绍完毕,从上面的代码可以看出VolumeManager使用Disk对象来抽象实际的存储设备。

我们现在可以结合上图总结一下VolumeManager的工作流程:
1、存储设备发生变化(如热插拔等),将导致Linux Kernel发出Uevent消息给NetlinkManager。
2、NetlinkManager将事件通知给VolumeManager。
3、VolumeManager根据事件的内容,判断是设备的变化情况,然后操作对应的Disk对象。例如新增存储设备,就利用事件中的内容创建出新的Disk(创建Disk时,就会进一步读取分区信息,创建出Volume对象,此处不再细分);设备被移除了,VolumeManager就负责移除对应的Disk。

到目前为止,我们分析的流程都停留在Vold进程中,并没有与Android框架发生实际的交互。为了引出交互的实际流程,我们需要先分析一下Vold进程与框架交互的桥梁,即CommandListener。

3、CommandListener

3.1 CommandListener的创建
Vold进程在main函数中创建出了CommandListener,然后调用了CommandListener的startListener函数。
我们先看看CommandListener的构造函数:

class CommandListener : public FrameworkListener {
........
}

从定义来看CommandListener继承于FrameworkListener。

CommandListener::CommandListener() :
                 FrameworkListener("vold", true) {
    //注册CommandListener支持的命令
    registerCmd(new DumpCmd());
    registerCmd(new VolumeCmd());
    registerCmd(new AsecCmd());
    registerCmd(new ObbCmd());
    registerCmd(new StorageCmd());
    registerCmd(new FstrimCmd());
    registerCmd(new AppFuseCmd());
}

在CommandListener的构造函数中,调用了父类的构造函数,同时利用其父类的registerCmd函数创建并注册了一些Cmd对象。

我们看看FrameworkListener:

FrameworkListener::FrameworkListener(const char *socketName, bool withSeq) :
                            SocketListener(socketName, true, withSeq) {
    init(socketName, withSeq);
}

void FrameworkListener::init(const char *socketName UNUSED, bool withSeq) {
    mCommands = new FrameworkCommandCollection();
    errorRate = 0;
    mCommandCount = 0;
    mWithSeq = withSeq;
}

void FrameworkListener::registerCmd(FrameworkCommand *cmd) {
    //将注册的Command保存到列表中
    mCommands->push_back(cmd);
}

根据前面的代码,我们可以得到上图的继承关系。
可以看到与之前分析NetlinkManager一样,CommandListener最终继承自SocketListener。不过与NetlinkManager不同的是,CommandListener传入到SocketListener的mListen参数为true,这意味着CommandListener中的socket将作为服务端存在。
从FrameworkListener的registerCmd函数来看,FrameworkListener仅仅是保存了新创建的Cmd对象。这里采用了设计模式中的Command模式,每个命令的处理函数都是runCommand。

3.2 CommandListener启动
当Vold进程创建出CommandListener后,同样调用了CommandListener的startListener函数。
根据继承关系,我们知道最终将会调用到SocketListener的startListener函数。

在分析NetlinkManager时,我们已经分析过SocketListener的startListener函数。在startListener函数中将启动一个工作线程,以监听对应socket的数据。
此处CommandListener监听的是init进程创建出Vold进程后,Vold进程创建的名为”vold”的socket,并且该socket是作为服务端存在的。当服务端收到注册请求后,将生成对应的SocketClient对象。然后,工作线程就可以监听SocketClient是否有数据到来。

“vold”的客户端是MountService。与之前分析的一样,当工作线程收到客户端数据时,将调用子类的onDataAvailable函数进行处理。
此时SocketListener的子类是FrameworkListener:

bool FrameworkListener::onDataAvailable(SocketClient *c) {
    char buffer[CMD_BUF_SIZE];
    int len;

    //将socket的数据读入到buffer中
    len = TEMP_FAILURE_RETRY(read(c->getSocket(), buffer, sizeof(buffer)));
    ............

    for (i = 0; i < len; i++) {
        if (buffer[i] == '\0') {
            /* IMPORTANT: dispatchCommand() expects a zero-terminated string */
            dispatchCommand(c, buffer + offset);
            offset = i + 1;
        }
    }

    return true;
}

//解析并分发Command
void FrameworkListener::dispatchCommand(SocketClient *cli, char *data) {
    //从data中解析出Command的参数
    ............
    for (i = mCommands->begin(); i != mCommands->end(); ++i) {
        FrameworkCommand *c = *i;

        //根据参数判断是否能被CommandListener中注册的命令处理
        if (!strcmp(argv[0], c->getCommand())) {
            //调用对应Command的runCommand函数
            if (c->runCommand(cli, argc, argv)) {
                SLOGW("Handler '%s' error (%s)", c->getCommand(), strerror(errno));
            }
            goto out;
        }
    }
    ............
}

以DumpCommand举例,看看runCommand函数:

int CommandListener::DumpCmd::runCommand(SocketClient *cli,
                                         int /*argc*/, char ** /*argv*/) {
    cli->sendMsg(0, "Dumping loop status", false);
    if (Loop::dumpState(cli)) {
        cli->sendMsg(ResponseCode::CommandOkay, "Loop dump failed", true);
    }
    cli->sendMsg(0, "Dumping DM status", false);
    if (Devmapper::dumpState(cli)) {
        cli->sendMsg(ResponseCode::CommandOkay, "Devmapper dump failed", true);
    }
    cli->sendMsg(0, "Dumping mounted filesystems", false);
    FILE *fp = fopen("/proc/mounts", "r");
    if (fp) {
        char line[1024];
        while (fgets(line, sizeof(line), fp)) {
            line[strlen(line)-1] = '\0';
            cli->sendMsg(0, line, false);;
        }
        fclose(fp);
    }

    cli->sendMsg(ResponseCode::CommandOkay, "dump complete", false);
    return 0;
}

从上面的代码容易看出,DumpCmd执行相应的操作后,都是通过SocketClient的sendMsg发送结果。在SocketClient的底层,就是靠”vold” socket将数据返回给MountService。

现在我们总结一下CommandListener涉及的工作流程:

如上图所示:
1、init进程启动Vold进程时,根据vold.rc创建了”vold” socket,”vold” socket作为server端存在于Vold进程中。
2、在Vold进程的main函数中,创建出了CommandListener(部分工作尤其父类完成);CommandListener创建一些Cmd。
3、调用CommandListener的startListener函数,尤其父类SocketListener中创建出实际的工作线程,监听”vold” socket是否有请求到来。
4、框架中的MountService启动后,间接利用socket与”vold”通信(通过NativeDaemonConnector封装)。初始时,将向”vold”发送connect请求。
5、当工作线程监听到”vold”有请求到来后,利用accept函数创建出与MountService端通信的server端,即上图的s。接下来,工作线程开始监听s上是否有数据到来。
6、当工作线程监听到s有数据到来后,将数据递交给CommandListener(实际是FrameworkListener处理)。
7、CommandListener根据数据的类型,调用对应的Command进行处理。
8、实际的Cmd根据参数进行实际的操作,然后将运行结果递交给s,s再将数据通过c递交给MountService。

接下来,我们看看运行在框架层中的MountService。

4 MountService
有些应用程序需要检测外部存储卡的插入/拔出事件,这些事件由MountService通过Intent广播发送。例如外部存储卡插入后,MountService就会发送Intent.ACTION_MEDIA_MOUNTED消息。
MountService由SystemServer启动,我们简单看看它的构造函数:

class MountService extends IMountService.Stub
        implements INativeDaemonConnectorCallbacks, Watchdog.Monitor {
    public MountService(Context context) {
        .........
        //与NetworkManagementService一样,MountService也是靠NativeDaemonConnector与底层守护进程通信
        //第一个参数传入回调接口,第二参数指明通信的server端
        mConnector = new NativeDaemonConnector(this, "vold", MAX_CONTAINERS * 2, VOLD_TAG, 25, null);
        .........
        //创建一个线程与server socket通信
        mConnectorThread = new Thread(mConnector, VOLD_TAG);
        ..........
    }

    private void start() {
        //NativeDaemonConnector是一个runnable对象,线程启动后将调用其run方法
        //在介绍Android7.0 数据业务长连接拨号过程时,我们提到过NetworkManagementService中NativeDaemonConnector连接netd的过程
        //MountService中的过程是一致的,只是这次连接的是vold
        mConnectorThread.start();
        .........
    }
}

从MountService的启动情况来看,对于Vold进程而言,我们需要关注的就是MountService利用NativeDaemonConnector建立与”vold”的连接,使得Vold进程能够与Android框架进行沟通了。

Android中的Service启动后,基本上都是靠事件驱动的,因此无法按一个有序的流程进行全面的介绍,比较好的方式还是了解整体架构后,分析一个具体的示例。
因此接下来我们以设备插入为例,分析一下MountService的主要工作。
根据上文的分析,我们知道当设备插入后,Kernel发送消息是的NetlinkManager能够收到Uevent。然后,NetlinkManager将会构造出NetlinkEvent,并通知VolumeManager进行处理。
在VolumeManager中,利用handleBlockEvent根据事件的类型进行相应的处理,我们截取设备添加时的处理代码:

void VolumeManager::handleBlockEvent(NetlinkEvent *evt) {
    ........
    switch (evt->getAction()) {
    case NetlinkEvent::Action::kAdd: {
        //前文已述,DiskSource是配置VolumeManager时,读取fstab文件得到的
        for (auto source : mDiskSources) {
            if (source->matches(eventPath)) {
                .......
                //创建设备对象
                auto disk = new android::vold::Disk(eventPath, device,
                        source->getNickname(), flags);
                disk->create();
                mDisks.push_back(std::shared_ptr<android::vold::Disk>(disk));
                break;
            }
        }
        break;
    }
    ........
}

我们看看Disk的代码:

Disk::Disk(const std::string& eventPath, dev_t device,
        const std::string& nickname, int flags) :
        mDevice(device), mSize(-1), mNickname(nickname), mFlags(flags), mCreated(
                false), mJustPartitioned(false) {
    mId = StringPrintf("disk:%u,%u", major(device), minor(device));
    mEventPath = eventPath;
    mSysPath = StringPrintf("/sys/%s", eventPath.c_str());
    mDevPath = StringPrintf("/dev/block/vold/%s", mId.c_str());
    //在CreateDeviceNode中,利用mknod创建设备节点
    CreateDeviceNode(mDevPath, mDevice);
}

status_t Disk::create() {
    CHECK(!mCreated);
    mCreated = true;
    //注意这里的notifyEvent
    notifyEvent(ResponseCode::DiskCreated, StringPrintf("%d", mFlags));
    readMetadata();
    readPartitions();
    return OK;
}

status_t Disk::readMetadata() {
    //读取设备信息
    ...........
    notifyEvent(ResponseCode::DiskSizeChanged, StringPrintf("%" PRIu64, mSize));
    notifyEvent(ResponseCode::DiskLabelChanged, mLabel);
    notifyEvent(ResponseCode::DiskSysPathChanged, mSysPath);
    return OK;
}

status_t Disk::readPartitions() {
    //读取分取信息
    .............
    Table table = Table::kUnknown;
    bool foundParts = false;
    for (auto line : output) {
        ..........
        if (!strcmp(token, "DISK")) {
            const char* type = strtok(nullptr, kSgdiskToken);
            if (!strcmp(type, "mbr")) {
                table = Table::kMbr;
            } else if (!strcmp(type, "gpt")) {
                    table = Table::kGpt;
            }
        } else if (!strcmp(token, "PART")) {
            .........
            dev_t partDevice = makedev(major(mDevice), minor(mDevice) + i);
            if (table == Table::kMbr) {
                ........
                createPublicVolume(partDevice);
                ........
            } else if (table == Table::kGpt) {
                const char* typeGuid = strtok(nullptr, kSgdiskToken);
                const char* partGuid = strtok(nullptr, kSgdiskToken);

                if (!strcasecmp(typeGuid, kGptBasicData)) {
                    //关注一下这个
                    createPublicVolume(partDevice);
                } else if (!strcasecmp(typeGuid, kGptAndroidExpand)) {
                    createPrivateVolume(partDevice, partGuid);
                }
            }
        }
    }
    ...............
}

void Disk::createPublicVolume(dev_t device) {
    auto vol = std::shared_ptr<VolumeBase>(new PublicVolume(device));
    if (mJustPartitioned) {
        LOG(DEBUG) << "Device just partitioned; silently formatting";
        vol->setSilent(true);
        vol->create();
        vol->format("auto");
        vol->destroy();
        vol->setSilent(false);
    }

    mVolumes.push_back(vol);
    vol->setDiskId(getId());
    vol->create();
}

status_t VolumeBase::create() {
    CHECK(!mCreated);

    mCreated = true;
    //子类实现
    status_t res = doCreate();
    notifyEvent(ResponseCode::VolumeCreated,
            StringPrintf("%d \"%s\" \"%s\"", mType, mDiskId.c_str(), mPartGuid.c_str()));
    setState(State::kUnmounted);
    return res;
}

上面列举了创建Disk和Volume的代码,注意到进行实际工作后,均会调用notifyEvent函数。

我们接下来就看看notifyEvent函数的用途:

void Disk::notifyEvent(int event, const std::string& value) {
    //还记得么?VolumeManager初始时指定其Broadcaster为CommandListener
    VolumeManager::Instance()->getBroadcaster()->sendBroadcast(event,
            StringPrintf("%s %s", getId().c_str(), value.c_str()).c_str(), false);
}

现在我们看看CommandListener的sendBroadcast函数(实际定义于父类的父类SocketListener中):

void SocketListener::sendBroadcast(int code, const char *msg, bool addErrno) {
    SocketClientCollection safeList;

    /* Add all active clients to the safe list first */
    //这应该算一种好习惯吧
    safeList.clear();
    pthread_mutex_lock(&mClientsLock);
    SocketClientCollection::iterator i;

    //注意将SocketListener当前所有的SocketClient均加入到safeList,所以函数名才叫sendBroadcast
    //当然,对于Vold进程而言,它的client只有MountService对应的socket
    for (i = mClients->begin(); i != mClients->end(); ++i) {
        SocketClient* c = *i;
        c->incRef();
        safeList.push_back(c);
    }
    pthread_mutex_unlock(&mClientsLock);

    while (!safeList.empty()) {
        /* Pop the first item from the list */
        i = safeList.begin();
        SocketClient* c = *i;
        safeList.erase(i);
        // broadcasts are unsolicited and should not include a cmd number
        //前面提到过SocketClient的sendMsg底层就是靠socket通信方式
        if (c->sendMsg(code, msg, addErrno, false)) {
            SLOGW("Error sending broadcast (%s)", strerror(errno));
        }
        c->decRef();
    }
}

从上面的代码,我们知道将由MountService来处理socket中的数据。
前面我们已经知道,MountService创建NativeDaemonConnector来封装socket相关的操作,在创建NativeDaemonConnector时需要传入回调接口。当NativeDaemonConnector收到数据后,通过回调接口进行通知。

MountService继承了INativeDaemonConnectorCallbacks,我们看看它的onEvent函数:

@Override
public boolean onEvent(int code, String raw, String[] cooked) {
    synchronized (mLock) {
        return onEventLocked(code, raw, cooked);
    }
}

private boolean onEventLocked(int code, String raw, String[] cooked) {
    switch (code) {
        case VoldResponseCode.DISK_CREATED: {
            if (cooked.length != 3) break;
            final String id = cooked[1];
            int flags = Integer.parseInt(cooked[2]);
            if (SystemProperties.getBoolean(StorageManager.PROP_FORCE_ADOPTABLE, false)
                    || mForceAdoptable) {
                flags |= DiskInfo.FLAG_ADOPTABLE;
            }
            mDisks.put(id, new DiskInfo(id, flags));
            break;
        }
        ...........
        case VoldResponseCode.VOLUME_CREATED: {
            final String id = cooked[1];
            final int type = Integer.parseInt(cooked[2]);
            final String diskId = TextUtils.nullIfEmpty(cooked[3]);
            final String partGuid = TextUtils.nullIfEmpty(cooked[4]);

            final DiskInfo disk = mDisks.get(diskId);
            final VolumeInfo vol = new VolumeInfo(id, type, disk, partGuid);
            mVolumes.put(id, vol);
            onVolumeCreatedLocked(vol);
            break;
        }
        ...........
    }

    return true;
}

从上面的代码可以看出,MountService收到DISK_CREATED消息后,仅会记录DiskInfo;收到VOLUME_CREATED消息后,还需要调用onVolumeCreatedLocked函数作进一步地处理。

private void onVolumeCreatedLocked(VolumeInfo vol) {
    .........
    if (vol.type == VolumeInfo.TYPE_EMULATED) {
        .......
    } else if (vol.type == VolumeInfo.TYPE_PUBLIC) {
        //以public type为例
        mHandler.obtainMessage(H_VOLUME_MOUNT, vol).sendToTarget();
    } else if (vol.type == VolumeInfo.TYPE_PRIVATE) {
        ........
    } else {
        .......
    }
}
class MountServiceHandler extends Handler {
    public MountServiceHandler(Looper looper) {
        super(looper);
    }

    @Override
    public void handleMessage(Message msg) {
        switch (msg.what) {
            ........
            final VolumeInfo vol = (VolumeInfo) msg.obj;
            if (isMountDisallowed(vol)) {
                Slog.i(TAG, "Ignoring mount " + vol.getId() + " due to policy");
                break;
            }
            try {
                //利用NativeDaemonConnector中socket将消息发送给CommandListenr
                mConnector.execute("volume", "mount", vol.id, vol.mountFlags,
                        vol.mountUserId);
            } catch (NativeDaemonConnectorException ignored) {
            }
            break;
            ........
        }
    }
}

注意消息重新发回到了CommandListener,根据前面的代码的分析我们知道,在FrameworkListener中将利用dispatchCommand根据类型,调用不同Command的runCommand方法,此处将调用volumeCommand的运行方法:

int CommandListener::VolumeCmd::runCommand(SocketClient *cli,
                                      int argc, char **argv) {
    //解析参数
    .........
    else if (cmd == "mount" && argc > 2) {
        // mount [volId] [flags] [user]
        std::string id(argv[2]);
        auto vol = vm->findVolume(id);
        if (vol == nullptr) {
            return cli->sendMsg(ResponseCode::CommandSyntaxError, "Unknown volume", false);
        }

        int mountFlags = (argc > 3) ? atoi(argv[3]) : 0;
        userid_t mountUserId = (argc > 4) ? atoi(argv[4]) : -1;

        vol->setMountFlags(mountFlags);
        vol->setMountUserId(mountUserId);

        //调用volumebase的mount方法,实际调用其子类的doMount
        int res = vol->mount();
        if (mountFlags & android::vold::VolumeBase::MountFlags::kPrimary) {
            vm->setPrimary(vol);
        }
        //执行成功后,将返回消息给MountService
        return sendGenericOkFail(cli, res);
    }
    .........
}

我们跟进一下VolumeBase的mount函数:

void VolumeBase::setState(State state) {
    mState = state;
    //再次通知到MountService
    notifyEvent(ResponseCode::VolumeStateChanged, StringPrintf("%d", mState));
}

在MountService的onEvent函数中,将再次处理VolumeStateChanged事件,实际上就是发送ACTION_MEDIA_MOUNTED广播。

上述的整个过程略去了大量的细节,但看起来仍很琐碎。不过,若是理解了前面介绍CommandListener时,分析的整个通信架构,那么这些流程的大致方向是比较好理解的。

结束语
Vold进程的主要内容基本上就是这些,在实际的工作中大多数人应该不会接触到这个进程。但是它整个架构是非常具有参考意义的,很清晰地阐释了Android中的框架层、Native层以及Kernel是如何交互的。Android中还有许多重要部分也采用了类似的架构,比较明显的就是netd。因此,以Vold入手进行分析,重在理解这种通信架构。

作者:Gaugamela 发表于2016/9/12 22:06:16 原文链接
阅读:101 评论:0 查看评论

让UITableView支持长按拖动排序

$
0
0

来自Leo的原创博客,转载请著名出处

我的StackOverflow

profile for Leo on Stack Exchange, a network of free, community-driven Q&A sites

我的Github
https://github.com/LeoMobileDeveloper

注意:本文的代码是用Swift 2.3写的


效果

项目地址
DraggableTableView

所有Cell都可以拖拽。

固定第一个Cell

限制长按区域


实现原理

  • 对UITableView添加LongPress手势
  • 在longPress的时候,对选中的Cell进行截图,添加到TableView作为subView,并且隐藏当前的选中的Cell
  • 随着手势移动,调整截图的位置,根据移动的位置,决定是否需要交换两个Cell的位置
  • 当截图移动到顶部/底部的时候,调用CADisplayLink来向上/向下滚动TableView

接口设计

最直接的方式,可能是继承UITableView,然后在子类中增加相关的逻辑来调整。但是,这种方式有明显的缺陷:对现有的代码影响较大。引入了由继承引起的耦合。

Swift中,继承并不是一个很好的设计方式,因为Swift是一个面向协议的语言。

本文采用extensionprotocol的方式,来设计接口。

定义一个协议

@objc public protocol DragableTableDelegate:AnyObject{
     //因为Cell拖动,必然要同步DataSource,所以这是个必须实现的方法
    func tableView(tableView:UITableView,dragCellFrom fromIndexPath:NSIndexPath,toIndexPath:NSIndexPath)

    //可选,返回长按的Cell是否可拖拽。用touchPoint来实现长按Cell的某一区域实现拖拽
    optional func tableView(tableView: UITableView,canDragCellFrom indexPath: NSIndexPath, withTouchPoint point:CGPoint) -> Bool

    //可选,返回cell是否可以停止在indexPath
    optional func tableView(tableView: UITableView,canDragCellTo indexPath: NSIndexPath) -> Bool

}

然后,我们用Objective C的关联属性,来给用户提用接口。

public extension UITableView{
    //关联属性用到的Key
    private struct OBJC_Key{
        static var dragableDelegateKey = 0
        static var dragableHelperKey = 1
        static var dragableKey = 2
    }
    // MARK: - 关联属性 -
    var dragableDelegate:DragableTableDelegate?{
        get{
            return objc_getAssociatedObject(self, &OBJC_Key.dragableDelegateKey) as? DragableTableDelegate
        }
        set{
            objc_setAssociatedObject(self, &OBJC_Key.dragableDelegateKey, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_ASSIGN)
        }
    }
    //是否可拖拽
    var dragable:Bool{
        get{
            let number = objc_getAssociatedObject(self, &OBJC_Key.dragableKey) as! NSNumber
            return number.boolValue
        }
        set{
            if newValue.boolValue {
                //进行必要的初始化
                setupDragable()
            }else{
                //清理必要的信息
                cleanDragable()
            }
            let number = NSNumber(bool: newValue)
            objc_setAssociatedObject(self, &OBJC_Key.dragableDelegateKey, number, objc_AssociationPolicy.OBJC_ASSOCIATION_ASSIGN)
        }
    }
    //因为拖拽的过程中,要存储ImageView,CADispalyLink等信息,所以需要一个辅助类
    private var dragableHelper:DragableHelper?{
        get{
            return objc_getAssociatedObject(self, &OBJC_Key.dragableHelperKey) as? DragableHelper
        }
        set{
            objc_setAssociatedObject(self, &OBJC_Key.dragableHelperKey, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN)
        }
    }
    //...
 }

Tips:

  • dragableDelegate在关联的时候,是OBJC_ASSOCIATION_ASSIGN,和weak var是一个效果。防止循环引用。

辅助类DragableHelper

因为用关联对象来存储数据,不得不为每一个属性提供get/set方法。所以,我们把需要的属性,存储到一个类DragableHelper
这个类如下

private class DragableHelper:NSObject,UIGestureRecognizerDelegate{

    //存储当前拖动的Cell
    weak var draggingCell:UITableViewCell?
    //这里的_DisplayLink是一个私有类,用来封装CADisplayLink
    let displayLink: _DisplayLink
    //长按手势
    let gesture: UILongPressGestureRecognizer
    //浮动的截图ImageView
    let floatImageView: UIImageView
    //当前操作的UITableView
    weak var attachTableView:UITableView?
    //当拖动到顶部/底部的时候,tableView向上或者向下滚动的速度
    var scrollSpeed: CGFloat = 0.0
    //初始化方法
    init(tableView: UITableView, displayLink:_DisplayLink, gesture:UILongPressGestureRecognizer,floatImageView:UIImageView) {
        self.displayLink = displayLink
        self.gesture = gesture
        self.floatImageView = floatImageView
        self.attachTableView = tableView
        super.init()
        self.gesture.delegate = self
    }
    //判断手势是否要begin,用来限制长按区域的
    @objc func gestureRecognizerShouldBegin(gestureRecognizer: UIGestureRecognizer) -> Bool {
        guard let attachTableView = attachTableView else{
            return false
        }
        return 
//返回值代理给TableView本身        attachTableView.lh_gestureRecognizerShouldBegin(gestureRecognizer)
    }
}

_DisplayLink类

CADisplayLink是一个用来在每一帧到来的时候,调整视图状态来生成动画的类。但是,有一点要注意,就是CADisplayLink必须显示的调用

_link.invalidate()

才能断掉循环引用,相关资源才能得到释放?
那么,能在dealloc中调用,来保证释放吗?
正常情况下是不行的,因为都没被释放,dealloc也就不会被调用.

那么,如何破坏掉这种循环引用呢?OC中,我们可以使用NSProxy,详情可见我这篇博客
Swift中,则可以这么实现一个基于block的displayLink

private class _DisplayLink{
    var paused:Bool{
        get{
            return _link.paused
        }
        set{
           _link.paused = newValue
        }
    }
    private init (_ callback: Void -> Void) {
        _callback = callback
        _link = CADisplayLink(target: _DisplayTarget(self), selector: #selector(_DisplayTarget._callback))
        _link.addToRunLoop(NSRunLoop.mainRunLoop(), forMode: NSRunLoopCommonModes)
        _link.paused = true
    }

    private let _callback: Void -> Void

    private var _link: CADisplayLink!

    deinit {
        _link.invalidate()
    }
}

/// 弱引用CADisplayLink,断掉循环引用
private class _DisplayTarget {

    init (_ link: _DisplayLink) {
        _link = link
    }

    weak var _link: _DisplayLink!

    @objc func _callback () {
        _link?._callback()
    }
}

Cell截图

最基础的CoreGraphics

private extension UIView{
    /**
     Get the screenShot of a UIView

     - returns: Image of self
     */
    func lh_screenShot()->UIImage?{
        UIGraphicsBeginImageContextWithOptions(CGSize(width: frame.width, height: frame.height), false, 0.0)
        layer.renderInContext(UIGraphicsGetCurrentContext()!)
        let image = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext();
        return image
    }
}

初始化和清除

上文讲到了,我们在设置dragable的时候,会进行必要的设置和初始化工作

private func setupDragable(){
    if dragableHelper != nil{
        cleanDragable()
    }
    //初始化手势
    let longPressGesture = UILongPressGestureRecognizer(target: self, action: #selector(UITableView.handleLongPress))
    addGestureRecognizer(longPressGesture)
    //初始化_DisplayLink
    let displayLink = _DisplayLink{ [unowned self] in
      //displayLink的回调
    }
    //初始化显示截图的UIImageView
    let imageView = UIImageView()
    let helper = DragableHelper(tableView:self,displayLink: displayLink, gesture: longPressGesture, floatImageView: imageView)
    dragableHelper = helper
}
private func cleanDragable(){
    guard let helper = dragableHelper else{
        return
    }
    removeGestureRecognizer(helper.gesture)
    dragableHelper = nil
}

处理长按手势

是否开始手势

通过这个代理方法,来限制长按的区域

func lh_gestureRecognizerShouldBegin(gestureRecognizer: UIGestureRecognizer) -> Bool {
    let location = gestureRecognizer.locationInView(self)
    guard let currentIndexPath = indexPathForRowAtPoint(location),let currentCell = cellForRowAtIndexPath(currentIndexPath) else{
        return false
    }
    let pointInCell = convertPoint(location, toView: currentCell)
    //通过代理,检查是否需要触发手势
    guard let canDrag = dragableDelegate?.tableView?(self, canDragCellFrom: currentIndexPath, withTouchPoint: pointInCell) else{
        return true
    }
    return canDrag
}

手势开始

整个处理过程如下

  1. 获取当前Cell
  2. 截图,作为subView添加到tableView中,设置好初始位置
  3. 设置transform和alpha,设置阴影
  4. 隐藏当前Cell
guard let currentIndexPath = indexPathForRowAtPoint(location),let currentCell = cellForRowAtIndexPath(currentIndexPath)else{
    return
}
if let selectedRow = indexPathForSelectedRow{
    deselectRowAtIndexPath(selectedRow, animated: false)
}
allowsSelection = false
currentCell.highlighted = false
dragableHelper.draggingCell = currentCell
//Configure imageview
let screenShot = currentCell.lh_screenShot()
dragableHelper.floatImageView.image = screenShot

dragableHelper.floatImageView.frame = currentCell.bounds
dragableHelper.floatImageView.center = currentCell.center

dragableHelper.floatImageView.layer.shadowRadius = 5.0
dragableHelper.floatImageView.layer.shadowOpacity = 0.2
dragableHelper.floatImageView.layer.shadowOffset = CGSizeZero
dragableHelper.floatImageView.layer.shadowPath = UIBezierPath(rect: dragableHelper.floatImageView.bounds).CGPath
addSubview(dragableHelper.floatImageView)

UIView.animateWithDuration(0.2, animations: { 
    dragableHelper.floatImageView.transform = CGAffineTransformMakeScale(1.1, 1.1)
    dragableHelper.floatImageView.alpha = 0.5
})
currentCell.hidden =  true

手势拖动

拖动的过程中,处理如下

  1. 调用方法adjusFloatImageViewCenterY方法.这个方法会调整截图ImageView的中心,并且根据位置,决定是否要交换两个Cell
  2. 根据拖动的位置,来设置dragableHelper.scrollSpeed.和displayLink是否停止,当displayLink启动的时候,会以一定的速度,来调整contentOffset.y。从而,看起来显示tableView,向上或则向下滚动
adjusFloatImageViewCenterY(location.y)
dragableHelper.scrollSpeed = 0.0
if contentSize.height > frame.height {
    let halfCellHeight = dragableHelper.floatImageView.frame.size.height / 2.0
    let cellCenterToTop = dragableHelper.floatImageView.center.y - bounds.origin.y
    if cellCenterToTop < halfCellHeight {
        dragableHelper.scrollSpeed = 5.0*(cellCenterToTop/halfCellHeight - 1.1)
    }
    else if cellCenterToTop > frame.height - halfCellHeight {
        dragableHelper.scrollSpeed = 5.0*((cellCenterToTop - frame.height)/halfCellHeight + 1.1)
    }
    dragableHelper.displayLink.paused = (dragableHelper.scrollSpeed == 0)
}

手势结束

  1. 停止CADisplayLink
  2. 截图ImageView移动到终止位置,并且移除
allowsSelection = true
dragableHelper.displayLink.paused = true
UIView.animateWithDuration(0.2,
                           animations: { 
        dragableHelper.floatImageView.transform = CGAffineTransformIdentity
        dragableHelper.floatImageView.alpha = 1.0
        dragableHelper.floatImageView.frame = dragableHelper.draggingCell!.frame
    },
                           completion: { (completed) in
        dragableHelper.floatImageView.removeFromSuperview()
        dragableHelper.draggingCell?.hidden = false
        dragableHelper.draggingCell = nil
})

adjusFloatImageViewCenterY 方法

这个方法首先会将截图ImageView移动到触摸中心,然后检查是否需要交换cell。

// MARK: - Private method -
func adjusFloatImageViewCenterY(newY:CGFloat){
    guard let floatImageView = dragableHelper?.floatImageView else{
        return
    }
    floatImageView.center.y = min(max(newY, bounds.origin.y), bounds.origin.y + bounds.height)
    adjustCellOrderIfNecessary()
}

func adjustCellOrderIfNecessary(){
    guard let dragableDelegate = dragableDelegate,floatImageView = dragableHelper?.floatImageView,toIndexPath = indexPathForRowAtPoint(floatImageView.center) else{
        return
    }
    guard let draggingCell = dragableHelper?.draggingCell,dragingIndexPath = indexPathForCell(draggingCell) else{
        return
    }
    guard dragingIndexPath.compare(toIndexPath) != NSComparisonResult.OrderedSame else{
        return
    }
    if let canDragTo = dragableDelegate.tableView?(self, canDragCellTo: toIndexPath){
        if !canDragTo {

            return
        }
    }
    draggingCell.hidden = true
    beginUpdates()
    dragableDelegate.tableView(self, dragCellFrom: dragingIndexPath, toIndexPath: toIndexPath)
    moveRowAtIndexPath(dragingIndexPath, toIndexPath: toIndexPath)
    endUpdates()
}

总结

看到这里了,给个Star吧。项目地址
DraggableTableView

作者:Hello_Hwc 发表于2016/9/12 22:32:27 原文链接
阅读:191 评论:0 查看评论

WebService理解和代码解析

$
0
0

/** WebServcie 概念  

多个系统数据交换: 跨平台语言的相互通信;
	如:java 的客户端 和dotnet的服务器端的接口调用:
		得到接口和方法	: 基于标准的协议,可编程语言;  服务器开发 api;
	
	特点:
		自包含:只要客户端支持http和xml就可以;
		自描述:只需要知道请求响应的类型;  其他的不需要考虑
		跨平台:不同语言可以相互通信。
		通过网络:发布查找使用;
		松耦合;
	
	术语:
		XML:扩展型可标记语言;
		SOAP:简单对象访问协议;用来描述传递信息的格式;  xml方法的调用规范;支持http等协议
		WSDL:web描述性语言,xml文档,自动生成;;  说明一组soap消息,和如何交互消息,是软件自动生成的xml文档;
		UDDI:英文为 "Universal Description, Discovery and Integration"通用描述、发现与集成服务;  引导系统查找响应服务的机制;
			 根据描述文档,来引导系统查找响应服务的机制。  提供自出服务,其他厂商根据服务;
			 
			 
		【公司的举例:】		
			soap 协议、合同,约束双方   
			wsdl  说明书,提供什么服务    
			uddi 工商注册,方便别人查询

		

*/

/* wsdl  
	wsdl 文档包含
	
		详细学习:http://www.w3school.com.cn/wsdl/wsdl_documents.asp
		
		某种类型系统:
		
		WSDL 文档是利用这些主要的元素来描述某个 web service 	
		
		元素 		定义
		<portType>	 web service 执行的操作
		<message> 	web service 使用的消息
		<types> 	web service 使用的数据类型
		<binding> 	web service 使用的通信协议

		
		
*/

/* soap  
	简单对象的访问协议;
		基于xml协议,以xml形式提供了一个简单的数据交换的协议;
		访问web服务的简单对象的访问协议
		通过http实现信息传递
		1)soap封装:描述消息中的内容;
		2)soap编码规则:定义序列化的机制、应用程序类型实例;
		3)soap rpc : 远程应答的协议  远程过程调用
		4)soap绑定: 节点间交互 soap的封装
	
	格式如:
POST /webservices/EnglishChinese.asmx HTTP/1.1
Host: fy.webxml.com.cn
Content-Type: text/xml; charset=utf-8
Content-Length: length
SOAPAction: "http://WebXml.com.cn/Translator"

<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <Translator xmlns="http://WebXml.com.cn/">
      <wordKey>string</wordKey>
    </Translator>
  </soap:Body>
</soap:Envelope>	

	规则:
		需要使用xml编码
		<soap:Body>是必须的
		Type传输数据类型
		soap:Envelope 必须使用命名空间

	优点:1)可扩展;2)简单的;3)和厂商无关;4)编程语言无关;

*/

//***********【soap1.1 解析的过程】**************
/**
	soap 1.1  xmlpull  conn  URL   
	
	1.代码和注解
	2.xml文件
	
*/

/**  2016年9月  整理webService的解析  (单词以外的链接有问题)
	 * WebService POST 请求 Soap1.1协议、 xml解析
	 * 
	 * @see 单词翻译查询网址:
	 * @see 1.网址:http://fy.webxml.com.cn/webservices/EnglishChinese.asmx
	 * @see 2.选方法: Translator 查询单词
	 * @see 3.方法中看soap协议,1.1或者1.2。对应post 中,xml部分复制,在asset中建立xml文件保存。
	 * @see 4.修改xml中 ,参数部分,如:<wordKey>string</wordKey> 。其中string修改为
	 *      #word,方便后续代码替换
	 * @see 步骤
	 * @see 1、xml参数转 流,替换单词参数, 并创建HttpURLConnection链接
	 * @see 2、conn设置链接的 协议,参考 soap文件 conn.setRequestProperty("Content-Type",
	 *      "text/xml; charset=utf-8"); Content-Type Content-Length SOAPAction
	 * @see 3、os 输出流 写 刷新
	 * @see 4、输入流 并 xml解析
	 * 
	 * 
	 * @throws IOException
	 * @throws XmlPullParserException
	 */
	public void WspostSoap11() throws IOException, XmlPullParserException {

		/*
		 * ( 1.inputstream 2.url 3.outputstaam 4.inputsteeem 5.xmlpull
		 */

		String path = "word.xml";

		InputStream is = getAssets().open(path);// MainActivity.class.getClassLoader().getResourceAsStream(path);
		InputStreamReader reader = new InputStreamReader(is, "utf-8");
		BufferedReader bfReader = new BufferedReader(reader);
		String str = "";
		StringBuilder sb = new StringBuilder();
		while ((str = bfReader.readLine()) != null) {
			// str += str.replace("#word", "girl");
			sb.append(str.replace("#word", "girl"));
		}
		byte[] data = sb.toString().getBytes();

		String url = "http://fy.webxml.com.cn/webservices/EnglishChinese.asmx";
		URL urls = new URL(url);
		HttpURLConnection conn = (HttpURLConnection) urls.openConnection();
		conn.setRequestMethod("POST");
		conn.setConnectTimeout(5 * 1000);
		conn.setReadTimeout(3 * 1000);
		conn.setRequestProperty("Content-Type", "text/xml; charset=utf-8");
		conn.setRequestProperty("Content-Length", data.length + "");
		conn.setRequestProperty("SOAPAction", "http://WebXml.com.cn/Translator");
		conn.setDoOutput(true);

		OutputStream os = conn.getOutputStream();
		os.write(data, 0, data.length);

		os.flush();

		InputStream isRusult = conn.getInputStream();

		XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
		XmlPullParser parser = factory.newPullParser();

		parser.setInput(isRusult, "utf-8");

		String result = "";
		while (parser.getEventType() != XmlPullParser.END_DOCUMENT) {
			if (parser.getEventType() == XmlPullParser.START_TAG && parser.getName().equals("Translation")) {
				result = parser.nextText();
			}
			if ("Trans".equals(parser.getName())) {
				Log.e(TAG, " Trans  " + parser.toString());
			}
			parser.next();

		}

		Log.e(TAG, " result  " + result);

	}
//=====================word.xml
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <Translator xmlns="http://WebXml.com.cn/">
      <wordKey>#word</wordKey>
    </Translator>
  </soap:Body>
</soap:Envelope>

//=====================参考的 SOAP 1.1
POST /webservices/EnglishChinese.asmx HTTP/1.1
Host: fy.webxml.com.cn
Content-Type: text/xml; charset=utf-8
Content-Length: length
SOAPAction: "http://WebXml.com.cn/Translator"

<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <Translator xmlns="http://WebXml.com.cn/">
      <wordKey>string</wordKey>
    </Translator>
  </soap:Body>
</soap:Envelope>

//***********【soap1.2 解析的过程】**************
/**
	 * WebService POST 请求 Soap1.2协议、 xml解析
	 * 
	 * @see 单词翻译查询网址:
	 * @see 1.网址:http://fy.webxml.com.cn/webservices/EnglishChinese.asmx
	 * @see 2.选方法: Translator 查询单词
	 * @see 3.方法中看soap协议,1.1或者1.2。对应post 中,xml部分复制,在asset中建立xml文件保存。
	 * @see 4.修改xml中 ,参数部分,如:<wordKey>string</wordKey> 。其中string修改为
	 *      #word,方便后续代码替换
	 * @see 步骤
	 * @see 1、xml参数转 流,替换单词参数, 并创建HttpURLConnection链接
	 * @see 2、conn设置链接的 协议,参考 soap文件 conn.setRequestProperty("Content-Type",
	 *      "text/xml; charset=utf-8"); Content-Type Content-Length SOAPAction
	 * @see 3、os 输出流 写 刷新
	 * @see 4、输入流 并 xml解析
	 * 
	 * @category soap 1.1 与  1.2 不同地方两个:
	 * 			1、setRequestProperty   1.2 设置协议中没有 SOAPAction
	 * 			2、1.2 协议的 xml文件不太一样。
	 * @throws IOException
	 * @throws XmlPullParserException
	 */
	public void WspostSoap12() throws IOException, XmlPullParserException {

		/*
		 * ( 1.inputstream 2.url 3.outputstaam 4.inputsteeem 5.xmlpull
		 */

		String path = "word12.xml";

		InputStream is = getAssets().open(path);// MainActivity.class.getClassLoader().getResourceAsStream(path);
		InputStreamReader reader = new InputStreamReader(is, "utf-8");
		BufferedReader bfReader = new BufferedReader(reader);
		String str = "";
		StringBuilder sb = new StringBuilder();
		while ((str = bfReader.readLine()) != null) {
			sb.append(str.replace("#word", "boy"));
		}
		byte[] data = sb.toString().getBytes();

		String url = "http://fy.webxml.com.cn/webservices/EnglishChinese.asmx";
		URL urls = new URL(url);
		HttpURLConnection conn = (HttpURLConnection) urls.openConnection();
		conn.setRequestMethod("POST");
		conn.setConnectTimeout(5 * 1000);
		conn.setReadTimeout(3 * 1000);
		conn.setRequestProperty("Content-Type", "application/soap+xml; charset=utf-8");
		conn.setRequestProperty("Content-Length", data.length + "");
//		conn.setRequestProperty("SOAPAction", "http://WebXml.com.cn/Translator");
		conn.setDoOutput(true);

		OutputStream os = conn.getOutputStream();
		os.write(data, 0, data.length);

		os.flush();

		InputStream isRusult = conn.getInputStream();

		XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
		XmlPullParser parser = factory.newPullParser();

		parser.setInput(isRusult, "utf-8");

		String result = "";
		while (parser.getEventType() != XmlPullParser.END_DOCUMENT) {
			if (parser.getEventType() == XmlPullParser.START_TAG && parser.getName().equals("Translation")) {
				result = parser.nextText();
			}
			if ("Trans".equals(parser.getName())) {
				Log.e(TAG, " Trans  " + parser.getName());
			}
			parser.next();
		}
		Log.e(TAG, " result  " + result);
	}

//**********【使用jar包的解析方法soap】**********************
	/**
	 * soap 解析
	 * 
	 * @see 1.导包 ksoap2-android-assembly-2.5.2-jar-with-dependencies.jar
	 * @see 2.定义网址、方法、命名空间
	 * @see 3.HttpTransportSE soap请求对象
	 * @see 4.SoapSerializationEnvelope soap序列化后的封装对象: soap信封
	 * @see 5.SoapObject 参数对象 并设置 addProperty
	 * @see 6.htse 调用 call方法 client.call(namespace + method, envelope);
	 * @see 7.信封中  查看响应 envelope.getResponse,返回bodyIn信息:字符串替换查找
	 * 
	 */
	public void soapDictionaryWord() {
		try {
			// 要访问的网址
			String wsdl = "http://fy.webxml.com.cn/webservices/EnglishChinese.asmx";
			// webservice 的功能名称
			String method = "Translator";
			// 响应数据的namespace
			String namespace = "http://WebXml.com.cn/";

			// soap客户端 http传输协议对象 send envelope
			HttpTransportSE client = new HttpTransportSE(wsdl);
			// soap序列化后的封装对象: soap信封
			SoapSerializationEnvelope envelope = new SoapSerializationEnvelope(SoapEnvelope.VER11);
			// 参数对象 目标
			SoapObject params = new SoapObject(namespace, method);

			// 添加要翻译的单词 添加属性
			params.addProperty("wordKey", "Everybody");
			// 设置参数对象 放入信封
			envelope.bodyOut = params;
			// 设置支持dotNet 服务器
			envelope.dotNet = true;

			// 访问webService 1.设置所需的SOAPAction头字段 2.包含soap调用信息的信封
			client.call(namespace + method, envelope);
			// 有响应结果 ; api 从包装对象中拉取一个对象,并返回,不为空表示得到了对象
			if (envelope.getResponse() != null) {
				// 返回响应对象 接收信封中的内容
				SoapObject response = (SoapObject) envelope.bodyIn;
				// 处理响应数据 : 返回指定位置的特定属性 0;得到body中的内容
				SoapObject obj = (SoapObject) response.getProperty(0);

				System.out.println("------" + obj);
				// 获取响应的字符串
				String result = obj.toString();
				// 找截断的起始位置
				int firstIndex = result.indexOf("Translation=");
				// 找截断的终止位置
				int endIndex = result.indexOf(";", firstIndex);
				// 翻译后的字符串
				String transWord = result.substring(firstIndex + "Translation=".length(), endIndex);

				System.out.println("------" + transWord);
				System.out.println("------" + getResult(result, "Trans=别", "。"));
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	//测试解析字符串的方法
	public String getResult(String resu, String starStr, String endStr) {
		int first = resu.indexOf(starStr);
		int end = resu.indexOf("。", first);
		return resu.substring(first + starStr.length() - 1, end);
	}





















作者:flyingsir_zw 发表于2016/9/12 22:49:48 原文链接
阅读:82 评论:0 查看评论

高仿微信5.2.1主界面及消息提醒

$
0
0

好久没更新博客了,最近在做公司的项目,这也算是我接触的第一个正式项目。通过项目的检验,发现自己积累了一年的知识还是远远不够,想要提高,好的方法是 :项目+书+视频+博客。最重要一点:勤动手。最近发现了慕课网的视频,居然都是高清无码免费的!而且满满的干货!我用业余时间跟着视频中大神的讲解学习了不少知识,下面就将这些小demo与大家分享,当然,我做了一些优化,代码与视频中有些出入,但功能可以完全实现。


这是一个模仿5.2.1版本的显示界面,如下图所示:
这里写图片描述这里写图片描述

功能及实现思路简介


主要功能很简单:
1、上面有一个自定义的标题栏;
2、往下是聊天、发现、通讯录选项卡;
3、手指滑动时,文字下方蓝色的indicator可以跟随滑动;
4、在聊天的右侧,有一个未读消息的红色提醒圆点。


自定义的标题栏就是一个LinearLayout,同时将系统自带的TitleBar(或是ActionBar)隐藏;

由于是选项卡,自然想到了Fragment;

手指可以滑动,显然,黑色的区域是一个ViewPager,数据源就是Fragment组成的集合,并通过FragmentPagerAdapter进行管理;

要实现蓝色的indicator随选项卡的滑动而滑动,可以为ViewPager设置监听,并根据回调方法的回传值控制该Indicator的marginLeft属性值可以实现该效果。

最后消息提醒的小圆点是一个BadgeView ,它是一个第三方开源控件。


主布局

MainActivity布局如下,首先是自定义的TitleBar:

<!-- top1.xml -->

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"

    android:layout_width="match_parent"
    android:layout_height="50dp"
    android:background="@drawable/topone_bg"
    android:paddingLeft="12dp"
    android:paddingRight="12dp">

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerVertical="true"
        android:gravity="center"
        android:orientation="horizontal">

        <ImageView
            android:layout_width="30dp"
            android:layout_height="30dp"
            android:background="@drawable/actionbar_icon" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="12dp"
            android:text="微信"
            android:textColor="#D3D3D3"
            android:textSize="18sp" />
    </LinearLayout>

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


        <ImageView
            android:layout_width="30dp"
            android:layout_height="30dp"
            android:background="@drawable/actionbar_search_icon" />

        <ImageView
            android:layout_width="30dp"
            android:layout_height="30dp"
            android:background="@drawable/actionbar_add_icon" />

        <ImageView
            android:layout_width="30dp"
            android:layout_height="30dp"
            android:background="@drawable/actionbar_more_icon" />


    </LinearLayout>


</RelativeLayout>

效果如下所示:
这里写图片描述


接着是三个选项卡的布局:

<!-- top2.xml -->
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="40dp"
    android:background="#EEEEEE"
    android:orientation="vertical">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="37dp"
        android:orientation="horizontal">

        <LinearLayout
            android:id="@+id/ll_chat"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:gravity="center"
            android:orientation="horizontal">

            <TextView
                android:id="@+id/tv_tab_chat"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"

                android:layout_gravity="center"
                android:text="聊天"
                android:textColor="#008000"
                android:textSize="16sp" />


        </LinearLayout>

        <LinearLayout
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:gravity="center">

            <TextView
                android:id="@+id/tv_tab_discover"
                android:layout_width="wrap_content"

                android:layout_height="wrap_content"
                android:text="发现"
                android:textColor="@android:color/black"
                android:textSize="16sp" />


        </LinearLayout>

        <LinearLayout
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:gravity="center">

            <TextView
                android:id="@+id/tv_tab_contacts"

                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="通讯录"
                android:textColor="@android:color/black"
                android:textSize="16sp" />


        </LinearLayout>

    </LinearLayout>

    <ImageView
        android:id="@+id/iv_tab_line"
        android:layout_width="100dp"
        android:layout_height="3dp"
        android:background="@drawable/tabline" />

</LinearLayout>


效果如下:
这里写图片描述

由于Indicator还需要在代码中动态设置其长度,故在xml中可以附一个任意值。


最后将top1.xml、top2.xml加入至主布局中,并在主布局中引入ViewPager:

<!-- activity_main.xml -->
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:orientation="vertical"
    tools:context="com.demo.lenovo.myapplication.MainActivity">

    <include layout="@layout/top1" />

    <include layout="@layout/top2" />

    <android.support.v4.view.ViewPager
        android:id="@+id/vp_content"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1" />
</LinearLayout>

效果如下:
这里写图片描述


注:如您的Activity继承于ActionBarActivity,可以在setContentView()方法之前调用requestWindowFeature(Window.FEATURE_NO_TITLE);隐藏标题栏;如继承于AppCompactActivity,可以在AndroidMainfest
中的Application标签中设置主题为:android:theme="@style/Theme.AppCompat.NoActionBar",也可以实现隐藏标题栏的目的。


使用FragmentPagerAdapter为ViewPager适配数据

在MainActivity.java 中,加入FragmentPagerAdapter逻辑:(在此略去三个Fragment的布局及代码)

 private FragmentPagerAdapter adapter;
 private List<Fragment> mData;

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        setContentView(R.layout.activity_main);
        initView();

在initView()中,初始化Fragment,并将Fragment实例一次装入List中,接着,在初始化FragmentPagerAdapter时管理List的数据。最后调用ViewPager的setAdapter方法将FragmentPagerAdapter实例传入。

  mData = new ArrayList<>();
        mData.add(new ChatFragment());
        mData.add(new DiscoverFragment());
        mData.add(new ContactsFragment());
        adapter = new FragmentPagerAdapter(getSupportFragmentManager()) {
            @Override
            public Fragment getItem(int position) {
                return mData.get(position);
            }

            @Override
            public int getCount() {
                return mData.size();
            }
        };
        vp_content.setAdapter(adapter);

设置滑动时字体颜色的变化

为实现该功能,需要为ViewPager设置setOnPageChangeListener监听,并通过OnPageChangeListener接口的回调方法onPagerSelected(int position),监听当前滑动到了第几页:

@Override
            public void onPageSelected(int position) {
                Log.e(TAG, "onPageSelected: " + position);
                resetTextViewColor();
                switch (position) {
                    case 0:             
                    addBadgeView();   tv_tab_chat.setTextColor(Color.parseColor("#008000"));
                        break;

                    case 1:
                        tv_tab_discover.setTextColor(Color.parseColor("#008000"));
                        break;

                    case 2:
                        tv_tab_contacts.setTextColor(Color.parseColor("#008000"));
                        break;
                }

            }
//首先将每个选项卡的文字颜色置为黑色
 private void resetTextViewColor() {
        tv_tab_contacts.setTextColor(Color.BLACK);
        tv_tab_chat.setTextColor(Color.BLACK);
        tv_tab_discover.setTextColor(Color.BLACK);

添加BadgeView

在addBadgeView();方法中首先判断BadgeView是否为空,若不为空,首先将其移除,再添加新的BadgeView,代码如下:

 private void addBadgeView()
 {
  if (mBadgeView != null) {
                            ll_chat.removeView(mBadgeView);
                        }
                        mBadgeView = new BadgeView(MainActivity.this);
                        ll_chat.addView(mBadgeView);
                        mBadgeView.setBadgeCount(9);



}

indicator的滑动

为了实现该Indicator随手指的滑动而跟随的效果,需要在OnPageChangeListener接口中的onPageScrolled()方法中编写逻辑,该方法的文档如下:
这里写图片描述


其中,第一个参数position表示滑动到了第几页,比如说,若从第0页滑动至第一页,那么position将一直为0,直到松手以后,滑动至第一页,position将变为1,第二个参数positionOffset表示滑动的百分比,取值范围是0-1,最后一个参数positionOffsetPixels表示滑动的像素数。

下面是从0—>1页面时打印的log,如下所示:
这里写图片描述


从1—->2页面时打印的log:
这里写图片描述


从2—->1页面时打印的log:
这里写图片描述


最后,可以根据(position+positionOffset)*1/3,来设置该Indicator的marginLeft。


首先,应为Indicator设置宽度,其宽度应为屏幕宽度的1/3:

 WindowManager manager = getWindow().getWindowManager();
        Display display = manager.getDefaultDisplay();
        DisplayMetrics outMetrics = new DisplayMetrics();
        display.getMetrics(outMetrics);
        mScreenOneThird = outMetrics.widthPixels / 3;

其中int型参数mScreenOneThird 的单位是像素px。


设置到Indicator上:

LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) iv_tab_line.getLayoutParams();
        lp.width = mScreenOneThird;
        iv_tab_line.setLayoutParams(lp);

最终在onPageScrolled方法中动态改变Indicator的marginLeft属性:

@Override
            public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
                LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) iv_tab_line.getLayoutParams();
                lp.leftMargin = (int) ((positionOffset * mScreenOneThird) + (mScreenOneThird * position));
                iv_tab_line.setLayoutParams(lp);

可实现最终效果。

作者:vanpersie_9987 发表于2016/9/12 23:43:31 原文链接
阅读:64 评论:0 查看评论

值类型 与引用的 copy

$
0
0

结构体和枚举是值类型

值类型被赋予给一个变量,常数或者本身被传递给一个函数的时候,实际上操作的是其的拷贝。

在之前的章节中,我们已经大量使用了值类型。实际上,在 Swift 中,所有的基本类型:整数(Integer)、浮点数(floating-point)、布尔值(Booleans)、字符串(string)、数组(array)和字典(dictionaries),都是值类型,并且都是以结构体的形式在后台所实现。

在 Swift 中,所有的结构体和枚举都是值类型。这意味着它们的实例,以及实例中所包含的任何值类型属性,在代码中传递的时候都会被复制。

请看下面这个示例,其使用了前一个示例中Resolution结构体:
  1. let hd = Resolution(width: 1920, height: 1080)
  2. var cinema = hd
在以上示例中,声明了一个名为hd的常量,其值为一个初始化为全高清视频分辨率(1920 像素宽,1080 像素高)的Resolution实例。

然后示例中又声明了一个名为cinema的变量,其值为之前声明的hd。因为Resolution是一个结构体,所以cinema的值其实是hd的一个拷贝副本,而不是hd本身。尽管hd和cinema有着相同的宽(width)和高(height)属性,但是在后台中,它们是两个完全不同的实例。

下面,为了符合数码影院放映的需求(2048 像素宽,1080 像素高),cinema的width属性需要作如下修改:
  1. cinema.width = 2048
这里,将会显示cinema的width属性确已改为了2048:
  1. println("cinema is now \(cinema.width) pixels wide")
  2. // 输出 "cinema is now 2048 pixels wide"
然而,初始的hd实例中width属性还是1920:
  1. println("hd is still \(hd.width ) pixels wide")
  2. // 输出 "hd is still 1920 pixels wide"
在将hd赋予给cinema的时候,实际上是将hd中所储存的值(values)进行拷贝,然后将拷贝的数据储存到新的cinema实例中。结果就是两个完全独立的实例碰巧包含有相同的数值。由于两者相互独立,因此将cinema的width修改为2048并不会影响hd中的宽(width)。

枚举也遵循相同的行为准则:
  1. enum CompassPoint {
  2. case North, South, East, West
  3. }
  4. var currentDirection = CompassPoint.West
  5. let rememberedDirection = currentDirection
  6. currentDirection = .East
  7. if rememberDirection == .West {
  8. println("The remembered direction is still .West")
  9. }
  10. // 输出 "The remembered direction is still .West"
上例中rememberedDirection被赋予了currentDirection的值(value),实际上它被赋予的是值(value)的一个拷贝。赋值过程结束后再修改currentDirection的值并不影响rememberedDirection所储存的原始值(value)的拷贝。

类是引用类型

与值类型不同,引用类型在被赋予到一个变量,常量或者被传递到一个函数时,操作的并不是其拷贝。因此,引用的是已存在的实例本身而不是其拷贝。

请看下面这个示例,其使用了之前定义的VideoMode类:
  1. let tenEighty = VideoMode()
  2. tenEighty.resolution = hd
  3. tenEighty.interlaced = true
  4. tenEighty.name = "1080i"
  5. tenEighty.frameRate = 25.0
以上示例中,声明了一个名为tenEighty的常量,其引用了一个VideoMode类的新实例。在之前的示例中,这个视频模式(video mode)被赋予了HD分辨率(1920*1080)的一个拷贝(hd)。同时设置为交错(interlaced),命名为“1080i”。最后,其帧率是25.0帧每秒。

然后,tenEighty 被赋予名为alsoTenEighty的新常量,同时对alsoTenEighty的帧率进行修改:
  1. let alsoTenEighty = tenEighty
  2. alsoTenEighty.frameRate = 30.0
因为类是引用类型,所以tenEight和alsoTenEight实际上引用的是相同的VideoMode实例。换句话说,它们只是同一个实例的两种叫法。

下面,通过查看tenEighty的frameRate属性,我们会发现它正确的显示了基本VideoMode实例的新帧率,其值为30.0:
  1. println("The frameRate property of tenEighty is now \(tenEighty.frameRate)")
  2. // 输出 "The frameRate property of theEighty is now 30.0"
需要注意的是tenEighty和alsoTenEighty被声明为常量(constants)而不是变量。然而你依然可以改变tenEighty.frameRate和alsoTenEighty.frameRate,因为这两个常量本身不会改变。它们并不储存这个VideoMode实例,在后台仅仅是对VideoMode实例的引用。所以,改变的是被引用的基础VideoMode的frameRate参数,而不改变常量的值。

恒等运算符

因为类是引用类型,有可能有多个常量和变量在后台同时引用某一个类实例。(对于结构体和枚举来说,这并不成立。因为它们作值类型,在被赋予到常量,变量或者传递到函数时,总是会被拷贝。)

如果能够判定两个常量或者变量是否引用同一个类实例将会很有帮助。为了达到这个目的,Swift 内建了两个恒等运算符:
  • 等价于 ( === )
  • 不等价于 ( !== )

以下是运用这两个运算符检测两个常量或者变量是否引用同一个实例:
  1. if tenEighty === alsoTenTighty {
  2. println("tenTighty and alsoTenEighty refer to the same Resolution instance.")
  3. }
  4. //输出 "tenEighty and alsoTenEighty refer to the same Resolution instance."
请注意“等价于”(用三个等号表示,===) 与“等于”(用两个等号表示,==)的不同:
  • “等价于”表示两个类类型(class type)的常量或者变量引用同一个类实例。
  • “等于”表示两个实例的值“相等”或“相同”,判定时要遵照类设计者定义定义的评判标准,因此相比于“相等”,这是一种更加合适的叫法。


集合(Collection)类型的赋值和拷贝行为

Swift 中数组(Array)和字典(Dictionary)类型均以结构体的形式实现。然而当数组被赋予一个常量或变量,或被传递给一个函数或方法时,其拷贝行为与字典和其它结构体有些许不同。

以下对数组和结构体的行为描述与对NSArray和NSDictionary的行为描述在本质上不同,后者是以类的形式实现,前者是以结构体的形式实现。NSArray和NSDictionary实例总是以对已有实例引用,而不是拷贝的方式被赋值和传递。
注意:以下是对于数组,字典,字符串和其它值的拷贝的描述。 在你的代码中,拷贝好像是确实是在有拷贝行为的地方产生过。然而,在Swift 的后台中,只有确有必要,实际(actual)拷贝才会被执行。Swift 管理所有的值拷贝以确保性能最优化的性能,所以你也没有必要去避免赋值以保证最优性能。(实际赋值由系统管理优化)

字典类型的赋值和拷贝行为

无论何时将一个字典实例赋给一个常量或变量,或者传递给一个函数或方法,这个字典会即会在赋值或调用发生时被拷贝。在章节结构体和枚举是值类型中将会对此过程进行详细介绍。

如果字典实例中所储存的键(keys)和/或值(values)是值类型(结构体或枚举),当赋值或调用发生时,它们都会被拷贝。相反,如果键(keys)和/或值(values)是引用类型,被拷贝的将会是引用,而不是被它们引用的类实例或函数。字典的键和值的拷贝行为与结构体所储存的属性的拷贝行为相同。

下面的示例定义了一个名为ages的字典,其中储存了四个人的名字和年龄。ages字典被赋予了一个名为copiedAges的新变量,同时ages在赋值的过程中被拷贝。赋值结束后,ages和copiedAges成为两个相互独立的字典。
  1. var ages = ["Peter": 23, "Wei": 35, "Anish": 65, "Katya": 19]
  2. var copiedAges = ages
这个字典的键(keys)是字符串(String)类型,值(values)是整(Int)类型。这两种类型在Swift 中都是值类型(value types),所以当字典被拷贝时,两者都会被拷贝。

我们可以通过改变一个字典中的年龄值(age value),检查另一个字典中所对应的值,来证明ages字典确实是被拷贝了。如果在copiedAges字典中将Peter的值设为24,那么ages字典仍然会返回修改前的值23:
  1. copiedAges["Peter"] = 24 println(ages["Peter"]) // 输出 "23"

数组的赋值和拷贝行为

在Swift 中,数组(Arrays)类型的赋值和拷贝行为要比字典(Dictionary)类型的复杂的多。当操作数组内容时,数组(Array)能提供接近C语言的的性能,并且拷贝行为只有在必要时才会发生。

如果你将一个数组(Array)实例赋给一个变量或常量,或者将其作为参数传递给函数或方法调用,在事件发生时数组的内容不会被拷贝。相反,数组公用相同的元素序列。当你在一个数组内修改某一元素,修改结果也会在另一数组显示。

对数组来说,拷贝行为仅仅当操作有可能修改数组长度时才会发生。这种行为包括了附加(appending),插入(inserting),删除(removing)或者使用范围下标(ranged subscript)去替换这一范围内的元素。只有当数组拷贝确要发生时,数组内容的行为规则与字典中键值的相同,参见章节[集合(collection)类型的赋值与复制行为](#assignment_and_copy_behavior_for_collection_types。

下面的示例将一个整数(Int)数组赋给了一个名为a的变量,继而又被赋给了变量b和c:
  1. var a = [1, 2, 3]
  2. var b = a
  3. var c = a
我们可以在a,b,c上使用下标语法以得到数组的第一个元素:
  1. println(a[0])
  2. // 1
  3. println(b[0])
  4. // 1
  5. println(c[0]) // 1
如果通过下标语法修改数组中某一元素的值,那么a,b,c中的相应值都会发生改变。请注意当你用下标语法修改某一值时,并没有拷贝行为伴随发生,因为下表语法修改值时没有改变数组长度的可能:
  1. a[0] = 42
  2. println(a[0])
  3. // 42
  4. println(b[0])
  5. // 42
  6. println(c[0])
  7. // 42
然而,当你给a附加新元素时,数组的长度会改变。 当附加元素这一事件发生时,Swift 会创建这个数组的一个拷贝。从此以后,a将会是原数组的一个独立拷贝。

拷贝发生后,如果再修改a中元素值的话,a将会返回与b,c不同的结果,因为后两者引用的是原来的数组:
  1. a.append(4)
  2. a[0] = 777
  3. println(a[0])
  4. // 777
  5. println(b[0])
  6. // 42
  7. println(c[0])
  8. // 42

确保数组的唯一性

在操作一个数组,或将其传递给函数以及方法调用之前是很有必要先确定这个数组是有一个唯一拷贝的。通过在数组变量上调用unshare方法来确定数组引用的唯一性。(当数组赋给常量时,不能调用unshare方法)

如果一个数组被多个变量引用,在其中的一个变量上调用unshare方法,则会拷贝此数组,此时这个变量将会有属于它自己的独立数组拷贝。当数组仅被一个变量引用时,则不会有拷贝发生。

在上一个示例的最后,b和c都引用了同一个数组。此时在b上调用unshare方法则会将b变成一个唯一个拷贝:
  1. b.unshare()
在unshare方法调用后再修改b中第一个元素的值,这三个数组(a,b,c)会返回不同的三个值:
  1. b[0] = -105
  2. println(a[0])
  3. // 77
  4. println(b[0])
  5. // -105
  6. println(c[0])
  7. // 42

判定两个数组是否共用相同元素

我们通过使用恒等运算符(identity operators)( === and !==)来判定两个数组或子数组共用相同的储存空间或元素。

下面这个示例使用了“恒等于(identical to)” 运算符(===) 来判定b和c是否共用相同的数组元素:
  1. if b === c {
  2. println("b and c still share the same array elements.")
  3. } else {
  4. println("b and c now refer to two independent sets of array elements.")
  5. }
  6. // 输出 "b and c now refer totwo independent sets of array elements."
此外,我们还可以使用恒等运算符来判定两个子数组是否共用相同的元素。下面这个示例中,比较了b的两个相等的子数组,并且确定了这两个子数组都引用相同的元素:
  1. if b[0...1] === b[0...1] {
  2. println("These two subarrays share the same elements.")
  3. } else {
  4. println("These two subarrays do not share the same elements.")
  5. }
  6. // 输出 "These two subarrays share the same elements."

强制复制数组

我们通过调用数组的copy方法进行强制显性复制。这个方法对数组进行了浅拷贝(shallow copy),并且返回一个包含此拷贝的新数组。

下面这个示例中定义了一个names数组,其包含了七个人名。还定义了一个copiedNames变量,用以储存在names上调用copy方法所返回的结果:
  1. var names = ["Mohsen", "Hilary", "Justyn", "Amy", "Rich", "Graham", "Vic"]
  2. var copiedNames = names.copy
我们可以通过修改一个数组中某元素,并且检查另一个数组中对应元素的方法来判定names数组确已被复制。如果你将copiedNames中第一个元素从"Mohsen"修改为"Mo",则names数组返回的仍是拷贝发生前的"Mohsen":
  1. copiedName[0] = "Mo"
  2. println(name[0])
  3. // 输出 "Mohsen"
注意:如果你仅需要确保你对数组的引用是唯一引用,请调用unshare方法,而不是copy方法。unshare方法仅会在确有必要时才会创建数组拷贝。copy方法会在任何时候都创建一个新的拷贝,即使引用已经是唯一引用。
作者:agonie201218 发表于2016/9/13 0:05:05 原文链接
阅读:84 评论:0 查看评论

[读书笔记]Android LayoutInflater.inflate方法参数详解(源码分析)

$
0
0

LayoutInflater

在看inflate()方法时,我们随便看下如何获得 LayoutInflater ,获得LayoutInflater 实例有三种方式

  1. LayoutInflater inflater =
    getLayoutInflater();//调用Activity的getLayoutInflater()
  2. LayoutInflater inflater = LayoutInflater.from(context);
  3. LayoutInflater inflater = (LayoutInflater)context.getSystemService
    (Context.LAYOUT_INFLATER_SERVICE);

其实,这三种方式本质是相同的,从源码中可以看出:

  • getLayoutInflater()

Activity 的 getLayoutInflater() 方法是调用 PhoneWindow 的getLayoutInflater()方法,看一下该源代码:

public PhoneWindow(Context context)
{
 super(context);
    mLayoutInflater = LayoutInflater.from(context);
}

可以看出它其实是调用 LayoutInflater.from(Context context)方法。

  • LayoutInflater.from(Context context)
public static LayoutInflater from(Context context)
{
 LayoutInflater LayoutInflater = (LayoutInflater) context.getSystemService
         (Context.LAYOUT_INFLATER_SERVICE);
    if (LayoutInflater == null)
    {
     throw new AssertionError("LayoutInflater not found.");
    }
    return LayoutInflater;
}

可以看出它其实调用 context.getSystemService()。
所以这三种方式最终本质是都是调用的Context.getSystemService()。
接下来我们来看下inflate()方法,

inflate()

LayoutInflater的inflate方法一共有四种

  • public View inflate(int, ViewGroup)

  • public View inflate(XmlPullParser, ViewGroup)

  • public View inflate(int, ViewGroup, boolean)

  • public View inflate(XmlPullParser, ViewGroup, boolean)

查看源码我们会发现inflate(int, ViewGroup)调用的是inflate(int, ViewGroup, boolean)方法

  public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
        return inflate(resource, root, root != null);
    }

inflate(int, ViewGroup, boolean)调用的是inflate(XmlPullParser, ViewGroup, boolean)方法

    public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
        final Resources res = getContext().getResources();
        if (DEBUG) {
            Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
                    + Integer.toHexString(resource) + ")");
        }

        final XmlResourceParser parser = res.getLayout(resource);
        try {
            return inflate(parser, root, attachToRoot);
        } finally {
            parser.close();
        }
    }

再看View inflate(XmlPullParser, ViewGroup)我们会发现它调用的也是inflate(XmlPullParser, ViewGroup, boolean)方法

    public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
        final Resources res = getContext().getResources();
        if (DEBUG) {
            Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
                    + Integer.toHexString(resource) + ")");
        }

        final XmlResourceParser parser = res.getLayout(resource);
        try {
            return inflate(parser, root, attachToRoot);
        } finally {
            parser.close();
        }
    }

所以呢?这四个方法都是

public View inflate(XmlPullParser, ViewGroup, boolean)方法,那其他三个我们就不看了,我们来分析这个方法。
看下别人对这个方法的参数是怎样描述的:
  • 返回值View:

返回的值是View指向的根节点。大家可能会有个疑问,第二个参数root不就是根结点吗?那返回根结点的话,不就是我们传进去的root吗?这可不一定,大家知道返回根结点的VIEW之后,继续往下看参数的具体讲解。

  • 第一个参数XmlPullParser:
    也就说根据其他几个方法传进来的xml布局文件在这里会被用传进来的parser进行解析
  • 第二个参数root:

表示根结点,它的作用主要取决于第三个参数

  • 第三个参数attachToRoot:

表示是否将转换后的VIEW直接添加在根结点上,如果是TRUE,那么在转换出来VIEW之后,内部直接会调用root.addView()来将其添加到root结点上,然后返回根结点,当然是我们传进去的ROOT结点。如果设为FALSE,那只会将XML布局转换成对应的VIEW,不会将其添加的我们传进去的root结点上。

第三个参数可能这样说比较难理解,我们来举个例子:

1 . 创建一个activity_root.xml文件,一个垂直的线性布局id为root,只有一个TextView

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
              tools:context=".MainActivity"
              android:orientation="vertical"
              android:id="@+id/root">

    <TextView
        android:text="@string/hello_world"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
</LinearLayout>

2 . 然后再建一个布局:add_layout.xml,也是一个垂直的线性布局背景颜色是红色,里面有个TextView

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              android:background="#f00">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:textSize="30dp"
        android:gravity="center"
        android:text="我是附加的" />
</LinearLayout> 

我们先来试试TRUE这个参数

public class InflateDomeActivity extends Activity{
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        //显示activity_root布局
        setContentView(R.layout.activity_root);
        //通过LayoutInflater.from(Context context);来获取LayoutInflater的实例
        LayoutInflater layoutInflater = LayoutInflater.from(this);
        //获取根结点的控件实例
        LinearLayout linearLayout = (LinearLayout)findViewById(R.id.root);
        //将activity_add加载到activity_root布局中
        layoutInflater.inflate(R.layout.activity_add,linearLayout,true);
    }
}

效果:
这里写图片描述
那如果将TRUE换为FALSE呢?

public class InflateDomeActivity extends Activity{
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        setContentView(R.layout.activity_root);
        LayoutInflater layoutInflater = LayoutInflater.from(this);
        LinearLayout linearLayout = (LinearLayout)findViewById(R.id.root);
        layoutInflater.inflate(R.layout.activity_add,linearLayout,false);
    }
}

效果:
这里写图片描述
可以看到,我们的主布局没有任何变化,也就是说add_layout.xml的布局没有被添加到activity_mian.xml中;
我们开头就讲过,如果attachToRoot设为false,那转换后的布局是不会被添加到root中的,会作为结果返回。
其实attachToRoot设为TRUE的代码与下面的代码是等价的:

public class InflateDomeActivity extends Activity{
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        setContentView(R.layout.activity_root);
        LayoutInflater layoutInflater = LayoutInflater.from(this);
        LinearLayout linearLayout = (LinearLayout)findViewById(R.id.root);
        View view = layoutInflater.inflate(R.layout.activity_add, linearLayout, false);
        linearLayout.addView(view);
    }
}

透过源码分析LayoutInflater.inflate()的过程

public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot)方法源码:

 public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
        synchronized (mConstructorArgs) {
            Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");

            final Context inflaterContext = mContext;
            final AttributeSet attrs = Xml.asAttributeSet(parser);
            Context lastContext = (Context) mConstructorArgs[0];
            mConstructorArgs[0] = inflaterContext;
            View result = root;

            try {
                // Look for the root node.
                int type;
                while ((type = parser.next()) != XmlPullParser.START_TAG &&
                        type != XmlPullParser.END_DOCUMENT) {
                    // Empty
                }

                if (type != XmlPullParser.START_TAG) {
                    throw new InflateException(parser.getPositionDescription()
                            + ": No start tag found!");
                }

                final String name = parser.getName();

                if (DEBUG) {
                    System.out.println("**************************");
                    System.out.println("Creating root view: "
                            + name);
                    System.out.println("**************************");
                }

                if (TAG_MERGE.equals(name)) {
                    if (root == null || !attachToRoot) {
                        throw new InflateException("<merge /> can be used only with a valid "
                                + "ViewGroup root and attachToRoot=true");
                    }

                    rInflate(parser, root, inflaterContext, attrs, false);
                } else {
                    // Temp is the root view that was found in the xml
                    final View temp = createViewFromTag(root, name, inflaterContext, attrs);

                    ViewGroup.LayoutParams params = null;

                    if (root != null) {
                        if (DEBUG) {
                            System.out.println("Creating params from root: " +
                                    root);
                        }
                        // Create layout params that match root, if supplied
                        params = root.generateLayoutParams(attrs);
                        if (!attachToRoot) {
                            // Set the layout params for temp if we are not
                            // attaching. (If we are, we use addView, below)
                            temp.setLayoutParams(params);
                        }
                    }

                    if (DEBUG) {
                        System.out.println("-----> start inflating children");
                    }

                    // Inflate all children under temp against its context.
                    rInflateChildren(parser, temp, attrs, true);

                    if (DEBUG) {
                        System.out.println("-----> done inflating children");
                    }

                    // We are supposed to attach all the views we found (int temp)
                    // to root. Do that now.
                    if (root != null && attachToRoot) {
                        root.addView(temp, params);
                    }

                    // Decide whether to return the root that was passed in or the
                    // top view found in xml.
                    if (root == null || !attachToRoot) {
                        result = temp;
                    }
                }

            } catch (XmlPullParserException e) {
                InflateException ex = new InflateException(e.getMessage());
                ex.initCause(e);
                throw ex;
            } catch (Exception e) {
                InflateException ex = new InflateException(
                        parser.getPositionDescription()
                                + ": " + e.getMessage());
                ex.initCause(e);
                throw ex;
            } finally {
                // Don't retain static reference on context.
                mConstructorArgs[0] = lastContext;
                mConstructorArgs[1] = null;
            }

            Trace.traceEnd(Trace.TRACE_TAG_VIEW);

            return result;
        }
    }

我们一点点来分析:
再源码的基础上我们保留下一些核心代码来分析下

public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) {  
    synchronized (mConstructorArgs) {  
        //第一步:初始化  
        final AttributeSet attrs = Xml.asAttributeSet(parser);  
        Context lastContext = (Context)mConstructorArgs[0];  
        mConstructorArgs[0] = mContext;  
        //注意这里,在初始化时,result表示要返回的视图,默认是返回root  
        View result = root;  

        …………  

        final String name = parser.getName();  

        //第二步:创建XML对应的空白VIEW:temp  
        if (TAG_MERGE.equals(name)) {  
        //如果是merge标签:抛出异常  (因为merge标签能够将该标签中的所有控件直接连在上一级布局上面,从而减少布局层级,假如一个线性布局替换为merge标签,那么原线性布局下的多个控件将直接连在上一层结构上,也就是如果加载进来的root根节点是root的话,那么将来无法知道布局的根节点是什么)
            …………  
        } else {  
            View temp;  
            if (TAG_1995.equals(name)) {  
                temp = new BlinkLayout(mContext, attrs);  
            } else {  
                temp = createViewFromTag(root, name, attrs);  
            }  

            //第三步:从根结点中,获取布局参数,设置到temp中  
            ViewGroup.LayoutParams params = null;  
            if (root != null) {  
                // Create layout params that match root, if supplied  
                params = root.generateLayoutParams(attrs);  
                if (!attachToRoot) {  
                    // Set the layout params for temp if we are not  
                    // attaching. (If we are, we use addView, below)  
                    temp.setLayoutParams(params);  
                }  
            }  

            //第四步:初始化temp中的子控件  
            rInflate(parser, temp, attrs, true);  

            //第五步:如果root不为空,而且attachToRoot设为TRUE,则将其视图通过addView添加到root中  
            if (root != null && attachToRoot) {  
                root.addView(temp, params);  
            }  

            //第六步:如果root为空,或者attachToRoot设为FALSE,那么就将TEMP视图做为result返回  
            if (root == null || !attachToRoot) {  
                result = temp;  
            }  
        }  

        return result;  
    }  
} 

我们来逐步分析一下:

  • 第一步:一进来是初始化部分:
final AttributeSet attrs = Xml.asAttributeSet(parser);  
Context lastContext = (Context)mConstructorArgs[0];  
mConstructorArgs[0] = mContext;  

//注意这里,在初始化时,result表示要返回的视图,默认是返回root
View result = root;
通过XML文件获取布局中各个控件的属性集:AttributeSet,注意:这里通过XML只能知道布局里每个控件的布局参数。那整个LAYOUT的布局参数呢,虽然我们在XML的根结点的布局可能写的是layout_width:fill_parent,layout_height:fill_parent。但这只是很笼统的,系统要确实计算出它的宽高数来。这个宽高数的计算就是通过root中的属性来得出来的,下面代码中会提到。
一个很重要的部分在于最后一句话!!!
[java] view plain copy
View result = root;
result表示最后返回的视图VIEW,所以这表示在默认情况下,返回root的视图!!注意这只是在默认情况下,下面会对result进行赋值的!
第二步:创建XML对应的空白视图temp
[java] view plain copy

  • //第二步:创建XML对应的空白VIEW:temp
if (TAG_MERGE.equals(name)) {  
//如果是merge标签,抛出异常  
    …………  
} else {  
    View temp;  
    if (TAG_1995.equals(name)) {  
        temp = new BlinkLayout(mContext, attrs);  
    } else {  
        temp = createViewFromTag(root, name, attrs);  
    } 

在这里首先判断XML的根结点是不是merge标签,大家知道我们的merge标签的主要作用是用来将merge标签下的所有控件合并到其上层布局上,也就是说merge标签下是没有根控件的!因为merge标签下的控件都是并列关系,所以如果merge标签使用的inflate函数,那我们根本没有办法给它返回根视图,所以肯定是要抛出异常的
如果不是merge标签,就创建一个空白视图,返回给temp,这里的temp就是我们XML所对应的布局!

  • 第三步:获取root的布局参数,设置到temp中
//第三步:从根结点中,获取布局参数,设置到temp中  
ViewGroup.LayoutParams params = null;  
if (root != null) {  
    params = root.generateLayoutParams(attrs);  
    if (!attachToRoot) {  
        temp.setLayoutParams(params);  
    }  
}  

在这里看到,首先获取到了ROOT的布局参数赋值为params,然后当attachToRoot为FALSE时,将参数设置到temp中;

  • 第四步:初始化temp中的子控件

[java] view plain copy
//第四步:初始化temp中的子控件

rInflate(parser, temp, attrs, true);  
//rInflate()其实是一个递归函数,用来递归建立temp下的所有控件的视图
  • 第五步:如果root不为空,而且attachToRoot设为TRUE,则将其视图通过addView添加到root中
if (root != null && attachToRoot) {  
    root.addView(temp, params);  
}  

在这里,就是当root不为空、attachToRoot为TRUE时,将构建好的temp视图添加到root中,注意这里的参数仍然从root中获取的布局参数params!!!所以,无论attachToRoot值为如何,temp的布局参数都是从ROOT里获取的!!!!

  • 第六步:如果root为空,或者attachToRoot设为FALSE,那么就将TEMP视图做为result返回
if (root == null || !attachToRoot) {  
    result = temp;  
}  

从这里可以看到,如果root为空,获取attachToRoot为FALSE,就会将temp做为结果返回!

到这里整个过程就分析结束了,下面我们总结一下:

  1. root的最基本作用,就是给我们传进去的Layout提供布局参数信息
  2. 如果attachToRoot为TRUE,那么会将Layout产生的布局视图添加到root中,返回root,如果attachToRoot为FALSE,那么会将Layout产生的布局视图直接返回

    参考博文后整理: ListView滑动删除实现之一——merge标签与LayoutInflater.inflate()

我的博客网站:http://huyuxin.top/欢迎大家访问!评论!

作者:DayDayPlayPhone 发表于2016/9/13 0:22:09 原文链接
阅读:75 评论:0 查看评论

开发者头条(三):实现tab与viewpager的联动

$
0
0

学习Ansen的博客,原文:http://blog.csdn.net/lowprofile_coding/article/details/51194577

知识点:

第一:实现首页的3个tab,让tab与viewpager实现联动
第二:轮播图的无限次自动循环滚动。

先看效果图:

这里写图片描述

项目结构图:

这里写图片描述

我们在捋顺一下逻辑:

每一个侧拉页的item对应一个fragment,用这个fragment替换内容页。其中第一个item对应的是首页,首页中又有3个tab,分别对应3个fragment(精选 + 订阅 + 发现 :如果效果一样的话,可以只写一个fragment)。精选:上面是轮播图,下面是listview。

tab与viewpager的联动是怎么实现的?

工具类PageSlidingTab实现的(还有colors.xml等),调用方法 setViewPager(viewPager)实现联动。
下载地址:http://download.csdn.net/detail/ss1168805219/9628964

类中还有一些参数,可以设置下划线 + 分割线 + 底部线 的颜色 高度等。

tab只有3个,没有充满屏幕宽度,怎么实现充满宽度?

调用PageSlidingTab的方法:setShouldExpand(true),实现充满屏幕宽度。

轮播图的实现

见图:
这里写图片描述

布局的实现

LinearLayout包裹RelativeLayout,里面有ViewPager + TextView + TextView + LinearLayout(指示器)

我把最外层的LiearLayout去掉,TextView与LinearLayout就是水平排列了,再设置above或below无效,不知道为什么?
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >
<!-- 把最外层的linearlayout直接去掉,,marginbottom就失效了,不知道为什么-->
    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="200dp" >

        <android.support.v4.view.ViewPager
            android:id="@+id/viewpager_banner"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
        <!-- 图片描述文字 -->

        <TextView
            android:id="@+id/tv_desc"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentBottom="true"
            android:layout_marginBottom="20dp"
            android:layout_marginLeft="10dp"
            android:text="这是图片描述部分" />

        <TextView
            android:id="@+id/tv_ratio"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentBottom="true"
            android:layout_alignParentRight="true"
            android:layout_marginBottom="20dp"
            android:layout_marginRight="10dp"
            android:text="1/3" />

        <!-- 存放圆点 -->

        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content" >

            <LinearLayout
                android:id="@+id/ll_dots"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_alignParentBottom="true"
                android:layout_marginBottom="5dp"
                android:gravity="center"
                android:orientation="horizontal" >
            </LinearLayout>
        </RelativeLayout>
    </RelativeLayout>

</LinearLayout>

代码的实现

轮播图先改变viewpager(即图片),再在viewpager的页面切换监听中实现2个textview内容的而转变,和指示器指向下一个。

改变viewpager:

通过handler发送消息。灾handleMessage()中设置viewpager的当期页是当前页+1,并再次发送消息,这样形成死循环。记得要移除消息,放置内存泄漏.

轮播图的详细介绍,请看我的另外2篇博客:[轮播图(一):实现ViewPager的无线自动循环 ](http://blog.csdn.net/ss1168805219/article/details/52282750) + [轮播图(二):ViewPager实现indicator的滚动](http://blog.csdn.net/ss1168805219/article/details/52294657)
handler.sendEmptyMessageDelayed(0, 2000);
private Handler handler = new Handler(){

    public void handleMessage(android.os.Message msg) {
        if (msg.what == 0) {
            viewPager.setCurrentItem((viewPager.getCurrentItem() + 1) % imagesList.size());
            handler.sendEmptyMessageDelayed(0, 2000);
        }
    }
};
//移除消息,放置内存泄漏
@Override
public void onDestroyView() {
    super.onDestroyView();
    handler.removeMessages(0);
}

怎么改变描述文字 + 指示器的?

在viewpager的监听事件中,设置下一页的数据。
遍历 指示器集合,当viewpager的position与集合的i相同的时候,就显示蓝色,否则是灰色。

@Override
public void onPageSelected(int position) {
    tv_desc.setText(titleList.get(position));
    tv_ratio.setText(1 + position + "/" + titleList.size());
    for (int i = 0; i < dotsList.size(); i++) {
        if (i == position) {
            dotsList.get(i).setBackgroundResource(
                    R.drawable.page_indicator_focused);
        } else {
            dotsList.get(i).setBackgroundResource(
                    R.drawable.page_indicator_unfocused);
        }
    }
}

指示器

跟导航页是一样的,记得设置marginLeft

private void initDots() {
    ll_dots.removeAllViews();
    dotsList.clear();
    LayoutParams params = new LinearLayout.LayoutParams(
            LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
    params.setMargins(10, 0, 0, 0);
    for (int i = 0; i < titleList.size(); i++) {
        ImageView iv = new ImageView(context);
        if (i == 0) {
            iv.setBackgroundResource(R.drawable.page_indicator_focused);
        } else {
            iv.setBackgroundResource(R.drawable.page_indicator_unfocused);
        }

        ll_dots.addView(iv, params);
        dotsList.add(iv);
    }
}

完成的代码是

首页:HomeFragment.java

package com.cqc.developerheadlinecqc.fragment;

import java.util.ArrayList;
import java.util.List;

import com.cqc.developerheadlinecqc.R;
import com.cqc.developerheadlinecqc.adapter.HomeFragmentPagerAdapter;
import com.cqc.developerheadlinecqc.fragment.home.Frag1;
import com.cqc.developerheadlinecqc.fragment.home.Frag2;
import com.cqc.developerheadlinecqc.fragment.home.Frag3;
import com.cqc.developerheadlinecqc.view.PagerSlidingTab;

import android.content.Context;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.view.ViewPager;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

public class HomeFragment extends Fragment {

    private Context context;
    private View view;
    private PagerSlidingTab pagerTab;
    private ViewPager viewPager;
    private List<Fragment> list = new ArrayList<Fragment>();
    private HomeFragmentPagerAdapter adapter;
    private Frag1 frag1;
    private Frag2 frag2;
    private Frag3 frag3;
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        context = getActivity();
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        view = inflater.inflate(R.layout.frag_home, container,false);
        pagerTab = (PagerSlidingTab) view.findViewById(R.id.pagerTab_home);
        viewPager = (ViewPager) view.findViewById(R.id.viewPager_home);

        initData();
        adapter = new HomeFragmentPagerAdapter(getActivity().getSupportFragmentManager(), list);
        viewPager.setAdapter(adapter);
        viewPager.setCurrentItem(0);
        pagerTab.setShouldExpand(true);//若tab的宽度没有充满屏幕的宽度,则设置true
        pagerTab.setViewPager(viewPager);
        return view;
    }

    private void initData() {
        list.clear();
        frag1 = new Frag1();
        frag2 = new Frag2();
        frag3 = new Frag3();

        list.add(frag1);
        list.add(frag2);
        list.add(frag3);
    }
}

精选:Frag1.java

package com.cqc.developerheadlinecqc.fragment.home;

import java.util.ArrayList;
import java.util.List;

import com.cqc.developerheadlinecqc.R;
import com.cqc.developerheadlinecqc.adapter.Frag1Adapter;
import com.cqc.developerheadlinecqc.adapter.GuildPagerAdapter;
import com.cqc.developerheadlinecqc.bean.Frag1Bean;

import android.content.Context;
import android.os.Bundle;
import android.os.Handler;
import android.support.v4.app.Fragment;
import android.support.v4.view.ViewPager;
import android.support.v4.view.ViewPager.OnPageChangeListener;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.LinearLayout.LayoutParams;
import android.widget.ListView;
import android.widget.TextView;

/**
 * @author cqc 精选
 */
public class Frag1 extends Fragment {

    private ListView listView;
    public List<Frag1Bean> list = new ArrayList<Frag1Bean>();
    public List<String> titleList = new ArrayList<String>();
    public List<ImageView> dotsList = new ArrayList<ImageView>();
    public List<ImageView> imagesList = new ArrayList<ImageView>();
    private Context context;
    private View headView;
    private ViewPager viewPager;
    private TextView tv_desc;
    private TextView tv_ratio;
    private LinearLayout ll_dots;
    private Frag1Adapter adapter;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        context = getActivity();
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.frag1, container, false);
        listView = (ListView) view.findViewById(R.id.listView_frag1);
        initData();
        adapter = new Frag1Adapter(getActivity(), list);
        listView.setAdapter(adapter);

        initHeadView();
        handler.sendEmptyMessageDelayed(0, 2000);
        return view;
    }

    private Handler handler = new Handler(){

        public void handleMessage(android.os.Message msg) {
            if (msg.what == 0) {
                viewPager.setCurrentItem((viewPager.getCurrentItem() + 1) % imagesList.size());
                handler.sendEmptyMessageDelayed(0, 2000);
            }
        }
    };
    private void initHeadView() {
        initTitleList();
        initImagesList();

        headView = View.inflate(context, R.layout.layout_banner, null);
        viewPager = (ViewPager) headView.findViewById(R.id.viewpager_banner);
        tv_desc = (TextView) headView.findViewById(R.id.tv_desc);
        tv_ratio = (TextView) headView.findViewById(R.id.tv_ratio);
        ll_dots = (LinearLayout) headView.findViewById(R.id.ll_dots);
        initDots();

        viewPager.setCurrentItem(0);
        GuildPagerAdapter pagerAdapter = new GuildPagerAdapter(context,
                imagesList);
        viewPager.setAdapter(pagerAdapter);
        viewPager.setOnPageChangeListener(new OnPageChangeListener() {

            @Override
            public void onPageSelected(int position) {
                tv_desc.setText(titleList.get(position));
                tv_ratio.setText(1 + position + "/" + titleList.size());
                for (int i = 0; i < dotsList.size(); i++) {
                    if (i == position) {
                        dotsList.get(i).setBackgroundResource(
                                R.drawable.page_indicator_focused);
                    } else {
                        dotsList.get(i).setBackgroundResource(
                                R.drawable.page_indicator_unfocused);
                    }
                }
            }

            @Override
            public void onPageScrolled(int position, float positionOffset,
                    int positionOffsetPixels) {
            }

            @Override
            public void onPageScrollStateChanged(int state) {
            }
        });

        listView.addHeaderView(headView);
    }

    private void initDots() {
        ll_dots.removeAllViews();
        dotsList.clear();
        LayoutParams params = new LinearLayout.LayoutParams(
                LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
        params.setMargins(10, 0, 0, 0);
        for (int i = 0; i < titleList.size(); i++) {
            ImageView iv = new ImageView(context);
            if (i == 0) {
                iv.setBackgroundResource(R.drawable.page_indicator_focused);
            } else {
                iv.setBackgroundResource(R.drawable.page_indicator_unfocused);
            }

            ll_dots.addView(iv, params);
            dotsList.add(iv);
        }
    }

    private void initImagesList() {
        imagesList.clear();

        ImageView iv1 = new ImageView(context);
        ImageView iv2 = new ImageView(context);
        ImageView iv3 = new ImageView(context);

        iv1.setBackgroundResource(R.drawable.icon_selected_carousel_one);
        iv2.setBackgroundResource(R.drawable.icon_selected_carousel_two);
        iv3.setBackgroundResource(R.drawable.icon_selected_carousel_three);

        imagesList.add(iv1);
        imagesList.add(iv2);
        imagesList.add(iv3);
    }

    private void initTitleList() {
        titleList.clear();
        titleList.add("这是第一张图的标题");
        titleList.add("这是第二张图的标题");
        titleList.add("这是第三张图的标题");
    }

    private void initData() {
        list.clear();
        for (int i = 0; i < 10; i++) {
            Frag1Bean bean = new Frag1Bean();
            bean.id = i;
            bean.title = "Android开发666";
            bean.likeNumbers = i;
            bean.commentsNumbers = i;
            list.add(bean);
        }
    }

    //移除消息,放置内存泄漏
    @Override
    public void onDestroyView() {
        super.onDestroyView();
        handler.removeMessages(0);
    }

}

源码:http://download.csdn.net/detail/ss1168805219/9628965

作者:ss1168805219 发表于2016/9/13 1:08:02 原文链接
阅读:70 评论:0 查看评论

混淆必知必会

$
0
0

日常开发,打包上线App的时候,混淆几乎不可避免,除非你想裸奔。
混淆的命令不多,但容易弄混。翻译小结一下,方便以后查阅。
官方文档
http://proguard.sourceforge.net/index.html#manual/usage.html

目录

输入输出选项(Input/Output Options)

@filename 是-include filename简写

-include filename
从给定的文件名中递归的读取配置选项

-basedirectory directoryname
为在这些配置参数里面的后续的相对文件名或这些配置文件指定一个目录。

-injars class_path
指定应用程序的要处理的jars(or wars, ears, zips, or directories)路径,这些jar里面的class文件将被处理和写入到输出jar中。默认的,所有的没有包含.class的文件将会被原风不动的复制,请注意任何临时文件,特别是你直接从目录里导入文件。class路径中的条目会像filters描述的那样被过滤掉。为了更好的可读性,可以使用多个指定的类路径条目 -injars 选项。

-outjars class_path
指定处理完后要输出的jar,war,ear和目录的名称 ,在先前-injars 输入的文件将会被重写到指定的jar中,这将允许你选择输入的jar到指定输出的jar。另外,输出条目可以按filters的规则过滤掉,每一个被处理过的class文件和资源文件会被写入到第一个匹配过滤规则的输入文件中。你必须避免让输出文件重写任何的输入文件中。为了更好的可读性,可以使用多个类路径条目 -outjars选项,如果没有任何的-outjars 选项,将没有jar会被重写。

-libraryjars class_path
指定要程序所引用的 jar(比如jdk的rt.jar)( 或 war, ears,zips,或者目录)。这些jar里面的文件不会输入到输出jar中,指定的library jars 至少包含application的子类。Library class files 只被调用而不必呈现,虽然它们的存在可以提升优化的结果,这些class路径会filter的要求来过滤。为了更好的可读性,可以使用多个类路径条目 -libraryjars选项。请注意当寻找library class 时,引导路径和progurad的运行路径是不会被考虑在内的,这就意味着你要明确地指出你的代码会用到的run-time jar。虽然这会看起来比较笨重,但是它允许你运行自己的程序在不同的运行时环境中。比如,你可以通过指定合适的run-time jar就可以运行J2SE applications和JME midlets。

-skipnonpubliclibraryclasses
为了加快运行和减少proguard的使用内存,当读取library jars指定跳过non-public 类。在默认情况下ProGuard读取non-publicpublic类一样,然而,如果它们没有影响输入jar里面的的程序代码,non-public 类通常是不相关的。在没有影响输出的情况下,忽略它们来加速Proguard.不幸的是,一些库,包括最近 的JSE run-time 库,包含一些非public class继承自公共的library classes,那在这种情况下你不能使用这个option。在使用这个选项ProGuard将打印出警告当找不到类的时候。

-dontskipnonpubliclibraryclasses
指定不去忽略非公共库类,在4.5以上这个是默认设置。

-dontskipnonpubliclibraryclassmembers
指定不忽略包可见的库类成员(字段和方法)。默认地,当解析库类的时候ProGuard会跳过这些类成员,项目类一般不会去引用它们。然而有的时候,程序里的类相当于库类存在于相同包。此时它们会引用他们的包可见的类成员。在这种情况下为了保持程序代码保持一致性去读取这些类的成员是有用的。

-keepdirectories [directory_filter]
指定要保存在输出jars(或wars ,ears,或directories)的目录。默认地,目录部分是会被移除的,这样可以减少jar的大小,但是当程序尝试用构造器找寻它们时会出现不愿看见的结果比如:“MyClass.class.getResource(“”)”如果这个选项没过滤器,所有的目录都会保存下来,有过滤器时只有符合过滤器的目录会保存下来。

-target version
在被处理的class文件中指定版本号。版本号可以是1.0,1.1,1.2,1.3,1.4,1.5(或者就是5),1.6(或者就是6),1.7(或者就是7),默认地,class文件的版本号是保持不变的。比如,你可能想更新class file到Java 6,通过改变他们的版本让他们预编译。

-forceprocessing
指定输入的过程,即使输出看起来最新。这个最新的测试是基于比较指定输入,输出和配置文件或目录的时间戳。

保留选项(Keep Options)

-keep [,modifier,…] class_specification
不混淆某些类。
如:-keep public class mypackage.MyMain {public static void main(java.lang.String[]);}

-keepclassmembers [,modifier,…] class_specification
不混淆类的成员,如果它们的类也被保护了它们会被保护的更好。比如你想保护可以序列化类的变量和方法。
如:-keepclassmembers class * implements java.io.Serializable {private static final java.io.ObjectStreamField[] serialPersistentFields;private void writeObject(java.io.ObjectOutputStream);private void readObject(java.io.ObjectInputStream);java.lang.Object writeReplace();java.lang.Object readResolve();}

-keepclasseswithmembers [,modifier,…] class_specification
不混淆类及其成员,但条件是所有指定的类和类成员是要存在。比如,你想保持似有的包含主方法 的application而无需显示的列出它们。
如:-keepclasseswithmembers public class * {public static void main(java.lang.String[]);}

-keepnames class_specification
是-keep,allowshrinking class_specification的缩写,保护指定的类和类的成员的名称。

keepclassmembernames class_specification
相当于-keepclassmembers,allowshrinking class_specification只保留成员名称,混淆内容。

keepclasseswithmembernames class_specification
相当于-keepclasseswithmembers,allowshrinking class_specification如果这些指定的类在压缩阶段后存在, 保护指定的类和类的成员的名称。
保持 native 方法不被混淆:-keepclasseswithmembernames class * {native ;}

-printseeds [filename]
列出匹配各种-keep选项的类和类的成员,标准输出到给定的文件。

收缩选项(Shrinking Options)

-dontshrink
产压缩输入类文件,默认是要压缩的,除了-keep列出的class和这些类文件直接或间接引用的文件间外其它类文件都会被移除,压缩步骤在优化之后执行,因为优化可能会移除更多的类文件和类成员。

-printusage [filename]
指定输入文件中的死代码输出到标准文件中,适用于使用了压缩。

-whyareyoukeeping class_specification
打印出为什么在压缩过程中保留了这些类文件和类成员的具体原因,也适用了使用了压缩的情况。

优化选项(Optimization Options)

-dontoptimize
指定不优化输入类文件。默认情况下,已启用优化,所有的方法都在字节码级优化。

-optimizations optimization_filter
指定要启用和禁用的优化,在更精细的水平。只有当优化适用。

-optimizationpasses n
指定代码的优化压缩级别

-assumenosideeffects class_specification
优化试某个类中的方法不被执行,通常用来优化移除日志

-allowaccessmodification
优化时允许访问并修改有修饰符的类和类的成员

-mergeinterfacesaggressively
指定接口可以合并,即使实现类没实现所有的方法。该选项可以通过减少类的总数减少输出文件的大小。只有开启优化时可用。

混淆选项(Obfuscation Options)

-dontobfuscate
指定不混淆类文件,默认开启。

-printmapping [filename]
输出类和类成员新旧名字之间的映射到指定文件中。只有开启混淆时可用。

-applymapping filename
重用映射,映射文件未列出的类和类成员会使用随机的名称。如果代码结构从根本上发生变化,ProGuard 可能会输出映射会引起冲突警告。你可以通过添加-useuniqueclassmembernames选项来降低风险。只能指定一个映射文件。只有开启混淆时可用。

-obfuscationdictionary filename
使用文件中的关键字作为方法及字段混淆后的名称。默认使用 ‘a’,’b’ 等短名称作为混淆后的名称。你可以指定保留关键字或不相关的标识符。文件中的空格、标点符号、重复的单词及注释会被忽略。只有开启混淆时可用。

-classobfuscationdictionary filename
使用文件中的关键字作为类混淆后的名称,类似于-obfuscationdictionary。只有开启混淆时可用。

-packageobfuscationdictionary filename
使用文件中的关键字作为包混淆后的名称,类似于-obfuscationdictionary。只有开启混淆时可用。

-overloadaggressively
开启侵入性重载混淆。多个字段及方法允许同名,只要它们的参数及返回值类型不同。该选项可使处理后的代码更小(及更难阅读)。只有开启混淆时可用。注:Dalvik 不能处理重载的静态字段。

-useuniqueclassmembernames
方法同名混淆后亦同名,方法不同名混淆后亦不同名。不使用该选项时,类成员可被映射到相同的名称。因此该选项会增加些许输出文件的大小。只有开启混淆时可用。

-dontusemixedcaseclassnames
混淆时不会产生大小写混合的类名。默认混淆后的类名可以包含大写及小写。如果 jar 被解压到非大小写敏感的系统(比如 Windows),解压工具可能会将命名类似的文件覆盖另一个文件。只有开启混淆时可用。

-keeppackagenames [package_filter]
不混淆指定的包名。过滤器是由逗号分隔的包名列表。包名可以包含 ?、*、** 通配符,并且可以在包名前加上 ! 否定符。只有开启混淆时可用。

-flattenpackagehierarchy [package_name]
重新打包所有重命名的包到给定的包中。如果没参数或字符串为空,包移动到根包下。该选项是进一步混淆包名的例子,可以使处理后的代码更小更难阅读。只有开启混淆时可用。

-repackageclasses [package_name]
重新打包所有重命名的类到给定的包中。如果没参数或字符串为空,类的包会被完全移除。该选项覆盖-flattenpackagehierarchy,是进一步混淆包名的另一个例子,可以使处理后的代码更小更难阅读。曾用名为-defaultpackage。只有开启混淆时可用。

-keepattributes [attribute_filter]
保留任何可选属性。过滤器是由逗号分隔的 JVM 及 ProGuard 支持的属性列表。属性名可以包含 ?、*、** 通配符,并且可以在属性名前加上 ! 否定符。例如:处理库文件时应该加上Exceptions,InnerClasses,Signature属性。同时保留SourceFile及LineNumberTable属性使混淆后仍能获取准确的堆栈信息。同时如果你的代码有使用注解你可能会保留annotations属性。只有开启混淆时可用。

-keepparameternames
保留已保留方法的参数的名称及类型。只有开启混淆时可用。

-renamesourcefileattribute [string]
指定一个常量字符串作为SourceFile(和SourceDir)属性的值。需要被-keepattributes选项指定保留。只有开启混淆时可用。

-adaptclassstrings [class_filter]
混淆与完整类名一致的字符串。没指定过滤器时,所有符合现有类的完整类名的字符串常量均会混淆。只有开启混淆时可用。

-adaptresourcefilenames [file_filter]
以混淆后的类文件作为样本重命名指定的源文件。没指定过滤器时,所有源文件都会重命名。只有开启混淆时可用。

-adaptresourcefilecontents [file_filter]
以混淆后的类文件作为样本混淆指定的源文件中与完整类名一致的内容。没指定过滤器时,所有源文件中与完整类名一致的内容均会混淆。只有开启混淆时可用。

预校验选项(Preverification Options)

-dontpreverify
指定不对处理后的类文件进行预校验。默认情况下如果类文件的目标平台是 JavaMicro Edition 或 Java 6 或更高时会进行预校验。目标平台是 Android时没必要开启,关闭可减少处理时间。

-microedition
指定处理后的类文件目标平台是 Java Micro Edition。

常规选项(General Options)

-verbose
指定处理期间打印更多相关信息。

-dontnote [class_filter]
指定配置中潜在错误或遗漏时不打印相关信息。类名错误或遗漏选项时这些信息可能会比较有用。class_filter 是一个可选的正则表达式。类名匹配时 ProGuard 不会输出这些类的相关信息。

-dontwarn [class_filter]
指定找不到引用或其他重要问题时不打印警告信息。class_filter 是一个可选的正则表达式。类名匹配时 ProGuard 不会输出这些类的相关信息。*注意:如果找不到引用的类或方法在处理过程中是必须的,处理后的代码将会无法正常运行。请明确该操作的影响时使用该选项。

-ignorewarnings
打印找不到引用或其他重要问题的警告信息,但继续处理代码。注意:如果找不到引用的类或方法在处理过程中是必须的,处理后的代码将会无法正常运行。请明确该操作的影响时使用该选项。

-printconfiguration [filename]
将已解析过的配置标准输出到指定的文件。该选项可用于调试配置。
-dump [filename]
标准输出类文件的内部结构到给定的文件中。例如,你可能要输出一个 jar 文件的内容而不需要进行任何处理。

通常的混淆模式都是

-optimizationpasses 5
-dontusemixedcaseclassnames
-dontskipnonpubliclibraryclasses
-dontskipnonpubliclibraryclassmembers
-dontpreverify
-verbose
-optimizations !code/simplification/arithmetic,!field/*,!class/merging/*

-dontnote com.google.vending.licensing.ILicensingService
-dontnote com.android.vending.licensing.ILicensingService

-keep public class * extends android.app.Activity
-keep public class * extends android.app.Application
-keep public class * extends android.app.Service
-keep public class * extends android.content.BroadcastReceiver
-keep public class * extends android.content.ContentProvider
-keep public class * extends android.app.backup.BackupAgentHelper
-keep public class * extends android.preference.Preference
-keep class android.support.** { *; }
-keep public class * extends android.support.**
-keep public class * extends android.app.Fragment

...
作者:byhook 发表于2016/9/13 21:15:05 原文链接
阅读:110 评论:0 查看评论

Android上拉加载更多ListView——PulmListView

$
0
0

思路

今天带大家实现一个上拉加载更多的ListView.GitHub传送门:PulmListView, 欢迎大家fork&&star.

先带大家理一下思路, 如果我们要实现一个上拉加载更多的ListView, 我们需要实现的功能包括:

  1. 一个自定义的ListView, 并且该ListView能够判断当前是否已经处于最底部.
  2. 一个自定义的FooterView, 用于在ListView加载更多的过程中进行UI展示.
  3. 关联FooterView和ListView, 包括加载时机判断、FooterView的显示和隐藏.
  4. 提供一个加载更多的接口, 便于回调用户真正加载更多的功能实现.
  5. 提供一个加载更多结束的回调方法, 用于添加用户的最新数据并更新相关状态标记和UI显示.

针对上面的5个功能, 我们挨个分析对应的实现方法.


功能1(自定义ListView)

我们可以通过继承ListView, 实现一个自定义的PulmListView.

public class PulmListView extends ListView {
    public PulmListView(Context context) {
        this(context, null);
    }

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

    public PulmListView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        // 初始化
        init();
    }
}

只是实现ListView的三个构造函数还不够, 我们需要ListView能够判断当前的ListView是否滑动到最后一个元素.

判断是否滑动到最后一个元素, 我们可以通过为ListView设置OnScrollListener来实现.代码如下:

private void init() {
    super.setOnScrollListener(new OnScrollListener() {
        @Override
        public void onScrollStateChanged(AbsListView view, int scrollState) {
            // 调用用户设置的OnScrollListener
            if (mUserOnScrollListener != null) {
                mUserOnScrollListener.onScrollStateChanged(view, scrollState);
            }
        }

        @Override
        public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
            // 调用用户设置的OnScrollListener
            if (mUserOnScrollListener != null) {
                mUserOnScrollListener.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount);
            }

            // firstVisibleItem是当前屏幕能显示的第一个元素的位置
            // visibleItemCount是当前屏幕能显示的元素的个数
            // totalItemCount是ListView包含的元素总数
            int lastVisibleItem = firstVisibleItem + visibleItemCount;
            if (!mIsLoading && !mIsPageFinished && lastVisibleItem == totalItemCount) {
                if (mOnPullUpLoadMoreListener != null) {
                    mIsLoading = true;
                    mOnPullUpLoadMoreListener.onPullUpLoadMore();
                }
            }
        }
    });
}

从代码注释可以知道, 通过(firstVisibleItem + visibleItemCount)可以获取当前屏幕已经展示的元素个数, 如果已经展示的元素个数等于ListView的元素总数, 则此时可以认为ListView已经滑动到底部.


功能2(自定义的FooterView)

这里我们可以实现一个比较简单的FooterView, 即加载更多的UI布局.例如我们可以展示一个ProgressBar和一行文字, 具体代码如下:

/**
 * 加载更多的View布局,可自定义.
 */
public class LoadMoreView extends LinearLayout {

    public LoadMoreView(Context context) {
        this(context, null);
    }

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

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

    private void init() {
        LayoutInflater.from(getContext()).inflate(R.layout.lv_load_more, this);
    }
}

布局文件:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/id_load_more_layout"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal"
    android:gravity="center"
    android:layout_margin="@dimen/loading_view_margin_layout">

    <ProgressBar
        android:id="@+id/id_loading_progressbar"
        android:layout_width="@dimen/loading_view_progress_size"
        android:layout_height="@dimen/loading_view_progress_size"
        android:indeterminate="true"
        style="?android:progressBarStyleSmall"/>

    <TextView
        android:id="@+id/id_loading_label"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/page_loading"/>
</LinearLayout>

功能3(关联ListView和FooterView)

第一,我们需要在ListView中通过一个变量保存FooterView, 并且在构造函数中将其实例化.

private View mLoadMoreView;
private void init() {
    mLoadMoreView = new LoadMoreView(getContext());
}

第二,我们需要控制FooterView的显示和隐藏.考虑一下FooterView的显示和隐藏的时机:

  • 显示的时机: ListView处于最底部并且当前还有更多的数据需要加载.
  • 隐藏的时机: ListView结束完加载更多的操作.

为了判断当前是否还有数据需要加载, 因此我们需要定义一个boolean变量mIsPageFinished, 表示数据加载是否结束.
为了保证同一时间只进行一次数据加载过程, 因此我们还需要定义一个boolean变量mIsLoading, 表示当前是否已经处于数据加载状态.

明确了FooterView的显示和隐藏时机, 也有了控制状态的变量, 代码也就比较容易实现了.

显示时机:

private void init() {
    mIsLoading = false; // 初始化时没处于加载状态
    mIsPageFinished = false; // 初始化时默认还有更多数据需要加载
    mLoadMoreView = new LoadMoreView(getContext()); // 实例化FooterView
    super.setOnScrollListener(new OnScrollListener() {
        @Override
        public void onScrollStateChanged(AbsListView view, int scrollState) {
            // 调用用户设置的OnScrollListener
            if (mUserOnScrollListener != null) {
                mUserOnScrollListener.onScrollStateChanged(view, scrollState);
            }
        }

        @Override
        public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
            // 调用用户设置的OnScrollListener
            if (mUserOnScrollListener != null) {
                mUserOnScrollListener.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount);
            }

            int lastVisibleItem = firstVisibleItem + visibleItemCount;
            // 当处于ListView尾部且有更多数据需要加载且当前没有加载程序再进行中时, 执行加载更多操作
            if (!mIsLoading && !mIsPageFinished && lastVisibleItem == totalItemCount) {
                if (mOnPullUpLoadMoreListener != null) {
                    mIsLoading = true; // 将加载更多进行时状态设置为true
                    showLoadMoreView(); // 显示加载更多布局
                    mOnPullUpLoadMoreListener.onPullUpLoadMore(); // 调用用户设置的加载更多回调接口
                }
            }
        }
    });
}

private void showLoadMoreView() {
    // 这里将加载更多的根布局id设置为id_load_more_layout, 便于用户自定制加载更多布局.
    if (findViewById(R.id.id_load_more_layout) == null) {
        addFooterView(mLoadMoreView);
    }
}

隐藏时机:

/**
 * 加载更多结束后ListView回调方法.
 *
 * @param isPageFinished 分页是否结束
 * @param newItems       分页加载的数据
 * @param isFirstLoad    是否第一次加载数据(用于配置下拉刷新框架使用, 避免出现页面闪现)
 */
public void onFinishLoading(boolean isPageFinished, List<?> newItems, boolean isFirstLoad) {
    mIsLoading = false; // 标记当前已经没有加载更多的程序在执行
    setIsPageFinished(isPageFinished); // 设置分页是否结束标志并移除FooterView
}

private void setIsPageFinished(boolean isPageFinished) {
    mIsPageFinished = isPageFinished;
    removeFooterView(mLoadMoreView);
}

功能4(上拉加载更多实现的回调接口)

这个比较简单, 我们定义一个interface, 便于回调用户真正的加载更多的实现方法.

/**
 * 上拉加载更多的回调接口
 */
public interface OnPullUpLoadMoreListener {
    void onPullUpLoadMore();
}

private OnPullUpLoadMoreListener mOnPullUpLoadMoreListener;
/**
 * 设置上拉加载更多的回调接口.
 * @param l 上拉加载更多的回调接口
 */
public void setOnPullUpLoadMoreListener(OnPullUpLoadMoreListener l) {
    this.mOnPullUpLoadMoreListener = l;
}

功能5(加载更多的结束回调)

为了在PulmListView中维护数据集合, 必须自定义一个Adapter, 在Adapter中使用List存储数据集合, 并提交增删的方法.

自定义的Adapter:

/**
 * 抽象的Adapter.
 */
public abstract class PulmBaseAdapter<T> extends BaseAdapter {
    protected List<T> items;

    public PulmBaseAdapter() {
        this.items = new ArrayList<>();
    }

    public PulmBaseAdapter(List<T> items) {
        this.items = items;
    }

    public void addMoreItems(List<T> newItems, boolean isFirstLoad) {
        if (isFirstLoad) {
            this.items.clear();
        }
        this.items.addAll(newItems);
        notifyDataSetChanged();
    }

    public void removeAllItems() {
        this.items.clear();
        notifyDataSetChanged();
    }
}

为什么在addMoreItems方法中要增加一个isFirstLoad变量呢?

是因为上拉加载更多通常要配合下拉刷新使用.而下拉刷新的过程中会牵扯到ListView的数据集合clear然后再addAll.如果没有isFirstLoad参数, 那用户下拉刷新去更新ListView的数据集合就必须分为两步:

  1. removeAllItems并进行notifyDataSetChanged.
  2. addMoreItems并进行notifyDataSetChanged.

同一时间连续两次notifyDataSetChanged会导致屏幕闪屏, 因此这里提交了一个isFirstLoad方法.当是第一次加载数据时, 会先clear掉所有的数据, 然后再addAll, 最后再notify.

有了自定义的adapter, 就可以写加载更多结束的回调函数了:

/**
 * 加载更多结束后ListView回调方法.
 *
 * @param isPageFinished 分页是否结束
 * @param newItems       分页加载的数据
 * @param isFirstLoad    是否第一次加载数据(用于配置下拉刷新框架使用, 避免出现页面闪现)
 */
public void onFinishLoading(boolean isPageFinished, List<?> newItems, boolean isFirstLoad) {
    mIsLoading = false;
    setIsPageFinished(isPageFinished);
    // 添加更新后的数据
    if (newItems != null && newItems.size() > 0) {
        PulmBaseAdapter adapter = (PulmBaseAdapter) ((HeaderViewListAdapter) getAdapter()).getWrappedAdapter();
        adapter.addMoreItems(newItems, isFirstLoad);
    }
}

这里需要注意, 当添加了FooterView或者HeaderView之后, 我们无法通过listview.getAdapter拿到我们自定义的adapter, 必须按照如下步骤:

PulmBaseAdapter adapter = (PulmBaseAdapter) ((HeaderViewListAdapter) getAdapter()).getWrappedAdapter();

参考

  1. PagingListView
作者:zinss26914 发表于2016/9/13 21:18:54 原文链接
阅读:114 评论:0 查看评论
Viewing all 5930 articles
Browse latest View live


<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>