android 常见分辨率(mdpi、hdpi 、xhdpi、xxhdpi )及屏幕适配注意事项

1 Android手机目前常见的分辨率

1.1 手机常见分辨率:

4:3
VGA     640*480 (Video Graphics Array)
QVGA  320*240 (Quarter VGA)
HVGA  480*320 (Half-size VGA)
SVGA  800*600 (Super VGA)

5:3
WVGA  800*480 (Wide VGA)

16:9
FWVGA 854*480 (Full Wide VGA)
HD        1920*1080 High Definition
QHD     960*540
720p    1280*720  标清
1080p  1920*1080 高清

手机:
iphone 4/4s    960*640 (3:2)
iphone5         1136*640
小米1             854*480(FWVGA)
小米2             1280*720

1.2 分辨率对应DPI

“HVGA    mdpi”
“WVGA   hdpi ”
“FWVGA hdpi ”
“QHD      hdpi ”
“720P     xhdpi”
“1080P   xxhdpi ”

2 屏幕适配的注意事项

2.1 基本设置

2.1.1 AndroidManifest.xml设置

在中Menifest中添加子元素

android:anyDensity=”true”时,应用程序安装在不同密度的终端上时,程序会分别加载xxhdpi、xhdpi、hdpi、mdpi、ldpi文件夹中的资源。

相反,如果设为false,即使在文件夹下拥有相同资源,应用不会自动地去相应文件夹下寻找资源:

1) 如果drawable-hdpi、drawable-mdpi、drawable-ldpi三个文件夹中有同一张图片资源的不同密度表示,那么系统会去加载drawable_mdpi文件夹中的资源;

2) 如果drawable-hpdi中有高密度图片,其它两个文件夹中没有对应图片资源,那么系统会去加载drawable-hdpi中的资源,其他同理;

3) 如果drawable-hdpi,drawable-mdpi中有图片资源,drawable-ldpi中没有,系统会加载drawable-mdpi中的资源,其他同理,使用最接近的密度级别。

2.1.2 横屏竖屏目录区分

1) drawable

a) drawable-hdpi该图片即适用于横屏,也适用于竖屏;

b) drawable-land-hdpi,当屏幕为横屏,且为高密度时,加载此文件夹的资源;

c) drawable-port-hdpi,当屏幕为竖屏,且为高密度时,加载此文件夹中的资源。其他同理。

2) layout

在res目录下建立layout-port和layout-land两个目录,里面分别放置竖屏和横屏两种布局文件,以适应对横屏竖屏自动切换。

2.2 多屏幕适配的4条黄金原则

1) 在layout文件中设置控件尺寸时应采用fill_parent、wrap_content、match_parent和dp;

具体来说,设置view的属性android:layout_width和android:layout_height的值时,wrap_content,match_parent或dp比px更好,文字大小应该使用sp来定义

2) 在程序的代码中不要出现具体的像素值,在dimens.xml中定义;

为了使代码简单,android内部使用pix为单位表示控件的尺寸,但这是基于当前屏幕基础上的。为了适应多种屏幕,android建议开发者不要使用具体的像素来表示控件尺寸。

3) 不使用AbsoluteLayout(android1.5已废弃) ,可以使用RelativeLayout替代;

4) 对不同的屏幕提供合适大小的图片。

不同大小屏幕用不同大小的图片,low:medium:high:extra-high图片大小的比例为3:4:6:8;举例来说,对于中等密度(medium)的屏幕你的图片像素大小为48×48,那么低密度(low)屏幕的图片大小应为36×36,高(high)的为72×72,extra-high为96×96。

2.3 使用9-patch PNG图片

使用图片资源时,如果出现拉伸,因为图片处理的原因,会变形,导致界面走形。9-patch PNG图片也是一种标准的PGN图片,在原生PNG图片四周空出一个像素间隔,用来标识PNG图片中哪些部分可以拉伸、哪些不可以拉伸、背景上的边框位置等。

“上、左”定义可拉伸区域

“右、下”定义显示区域,如果用到完整填充的背景图,建议不要通过android:padding来设置边距,而是通过9-patch方式来定义。

Android SDK中提供了编辑9-Patch图片的工具,在tools目录下draw9patch.bat,能够立刻看到编辑后的拉伸效果,也可以直接用其他图片编辑工具编辑,但是看不到效果。

2.4 不同的layout

Android手机屏幕大小不一,有480×320, 640×360, 800×480……

怎样才能让Application自动适应不同的屏幕呢?

其实很简单,只需要在res目录下创建不同的layout文件夹,比如:layout-640×360、layout-800×480……所有的layout文件在编译之后都会写入R.java里,而系统会根据屏幕的大小自己选择合适的layout进行使用。

video.js 源码分析(组织结构、继承关系、运行机制、插件的运行机制、类的挂载方式)

组织结构

以下是video.js的源码组织结构关系,涉及控制条、菜单、浮层、进度条、滑动块、多媒体、音轨字幕、辅助函数集合等等。

├── control-bar
│   ├── audio-track-controls
│   │   ├── audio-track-button.js
│   │   └── audio-track-menu-item.js
│   ├── playback-rate-menu
│   │   ├── playback-rate-menu-button.js
│   │   └── playback-rate-menu-item.js
│   ├── progress-control
│   │   ├── load-progress-bar.js
│   │   ├── mouse-time-display.js
│   │   ├── play-progress-bar.js
│   │   ├── progress-control.js
│   │   ├── seek-bar.js
│   │   └── tooltip-progress-bar.js
│   ├── spacer-controls
│   │   ├── custom-control-spacer.js
│   │   └── spacer.js
│   ├── text-track-controls
│   │   ├── caption-settings-menu-item.js
│   │   ├── captions-button.js
│   │   ├── chapters-button.js
│   │   ├── chapters-track-menu-item.js
│   │   ├── descriptions-button.js
│   │   ├── off-text-track-menu-item.js
│   │   ├── subtitles-button.js
│   │   ├── text-track-button.js
│   │   └── text-track-menu-item.js
│   ├── time-controls
│   │   ├── current-time-display.js
│   │   ├── duration-display.js
│   │   ├── remaining-time-display.js
│   │   └── time-divider.js
│   ├── volume-control
│   │   ├── volume-bar.js
│   │   ├── volume-control.js
│   │   └── volume-level.js
│   ├── control-bar.js
│   ├── fullscreen-toggle.js
│   ├── live-display.js
│   ├── mute-toggle.js
│   ├── play-toggle.js
│   ├── track-button.js
│   └── volume-menu-button.js
├── menu
│   ├── menu-button.js
│   ├── menu-item.js
│   └── menu.js
├── popup
│   ├── popup-button.js
│   └── popup.js
├── progress-bar
│   ├── progress-control
│   │   ├── load-progress-bar.js
│   │   ├── mouse-time-display.js
│   │   ├── play-progress-bar.js
│   │   ├── progress-control.js
│   │   ├── seek-bar.js
│   │   └── tooltip-progress-bar.js
│   └── progress-bar.js
├── slider
│   └── slider.js
├── tech
│   ├── flash-rtmp.js
│   ├── flash.js
│   ├── html5.js
│   ├── loader.js
│   └── tech.js
├── tracks
│   ├── audio-track-list.js
│   ├── audio-track.js
│   ├── html-track-element-list.js
│   ├── html-track-element.js
│   ├── text-track-cue-list.js
│   ├── text-track-display.js
│   ├── text-track-list-converter.js
│   ├── text-track-list.js
│   ├── text-track-settings.js
│   ├── text-track.js
│   ├── track-enums.js
│   ├── track-list.js
│   ├── track.js
│   ├── video-track-list.js
│   └── video-track.js
├── utils
│   ├── browser.js
│   ├── buffer.js
│   ├── dom.js
│   ├── events.js
│   ├── fn.js
│   ├── format-time.js
│   ├── guid.js
│   ├── log.js
│   ├── merge-options.js
│   ├── stylesheet.js
│   ├── time-ranges.js
│   ├── to-title-case.js
│   └── url.js
├── big-play-button.js
├── button.js
├── clickable-component.js
├── close-button.js
├── component.js
├── error-display.js
├── event-target.js
├── extend.js
├── fullscreen-api.js
├── loading-spinner.js
├── media-error.js
├── modal-dialog.js
├── player.js
├── plugins.js
├── poster-image.js
├── setup.js
└── video.js

video.js的JavaScript部分都是采用面向对象方式来实现的。基类是Component,所有其他的类都是直接或间接集成此类实现。语法部分采用的是ES6标准。

继承关系

深入源码解读需要了解类与类之间的继承关系,直接上图。

  • 所有的继承关系
  • 主要的继承关系

运行机制

首先调用videojs启动播放器,videojs方法判断当前id是否已被实例化,如果没有实例化新建一个Player对象,因Player继承Component会自动初始化Component类。如果已经实例化直接返回Player对象。

videojs方法源码如下:

function videojs(id, options, ready) {
let tag;
// id可以是选择器也可以是DOM节点
if (typeof id === 'string') {
    if (id.indexOf('#') === 0) {
        id = id.slice(1);
    }
    //检查播放器是否已被实例化
    if (videojs.getPlayers()[id]) {
        if (options) {
            log.warn(`Player "${id}" is already initialised. Options will not be applied.`);
        }
        if (ready) {
            videojs.getPlayers()[id].ready(ready);
        }
        return videojs.getPlayers()[id];
    }
    // 如果播放器没有实例化,返回DOM节点
    tag = Dom.getEl(id);
} else {
    // 如果是DOM节点直接返回
    tag = id;
}
if (!tag || !tag.nodeName) {
    throw new TypeError('The element or ID supplied is not valid. (videojs)');
}
// 返回播放器实例
return tag.player || Player.players[tag.playerId] || new Player(tag, options, ready);
}
[]()

接下来我们看下Player的构造函数,代码如下:

constructor(tag, options, ready) {
    // 注意这个tag是video原生标签
    tag.id = tag.id || `vjs_video_${Guid.newGUID()}`;
    // 选项配置的合并
    options = assign(Player.getTagSettings(tag), options);
    // 这个选项要关掉否则会在父类自动执行加载子类集合
    options.initChildren = false;
    // 调用父类的createEl方法
    options.createEl = false;
    // 在移动端关掉手势动作监听
    options.reportTouchActivity = false;
    // 检查播放器的语言配置
    if (!options.language) {
        if (typeof tag.closest === 'function') {
            const closest = tag.closest('[lang]');
            if (closest) {
                options.language = closest.getAttribute('lang');
            }
        } else {
            let element = tag;
            while (element && element.nodeType === 1) {
                if (Dom.getElAttributes(element).hasOwnProperty('lang')) {
                    options.language = element.getAttribute('lang');
                    break;
                }
                element = element.parentNode;
            }
        }
    }
    // 初始化父类
    super(null, options, ready);
    // 检查当前对象必须包含techOrder参数
    if (!this.options_ || !this.options_.techOrder || !this.options_.techOrder.length) {
        throw new Error('No techOrder specified. Did you overwrite ' +
            'videojs.options instead of just changing the ' +
            'properties you want to override?');
    }
    // 存储当前已被实例化的播放器
    this.tag = tag;
    // 存储video标签的各个属性
    this.tagAttributes = tag && Dom.getElAttributes(tag);
    // 将默认的英文切换到指定的语言
    this.language(this.options_.language);
    if (options.languages) {
        const languagesToLower = {};
        Object.getOwnPropertyNames(options.languages).forEach(function(name) {
            languagesToLower[name.toLowerCase()] = options.languages[name];
        });
        this.languages_ = languagesToLower;
    } else {
        this.languages_ = Player.prototype.options_.languages;
    }
    // 缓存各个播放器的各个属性.
    this.cache_ = {};
    // 设置播放器的贴片
    this.poster_ = options.poster || '';
    // 设置播放器的控制
    this.controls_ = !!options.controls;
    // 默认是关掉控制
    tag.controls = false;
    this.scrubbing_ = false;
    this.el_ = this.createEl();
    const playerOptionsCopy = mergeOptions(this.options_);
    // 自动加载播放器插件
    if (options.plugins) {
        const plugins = options.plugins;
        Object.getOwnPropertyNames(plugins).forEach(function(name) {
            if (typeof this[name] === 'function') {
                this[name](plugins[name]);
            } else {
                log.error('Unable to find plugin:', name);
            }
        }, this);
    }
    this.options_.playerOptions = playerOptionsCopy;
    this.initChildren();
    // 判断是不是音频
    this.isAudio(tag.nodeName.toLowerCase() === 'audio');
    if (this.controls()) {
        this.addClass('vjs-controls-enabled');
    } else {
        this.addClass('vjs-controls-disabled');
    }
    this.el_.setAttribute('role', 'region');
    if (this.isAudio()) {
        this.el_.setAttribute('aria-label', 'audio player');
    } else {
        this.el_.setAttribute('aria-label', 'video player');
    }
    if (this.isAudio()) {
        this.addClass('vjs-audio');
    }
    if (this.flexNotSupported_()) {
        this.addClass('vjs-no-flex');
    }

    if (!browser.IS_IOS) {
        this.addClass('vjs-workinghover');
    }
    Player.players[this.id_] = this;
    this.userActive(true);
    this.reportUserActivity();
    this.listenForUserActivity_();
    this.on('fullscreenchange', this.handleFullscreenChange_);
    this.on('stageclick', this.handleStageClick_);
}

在Player的构造器中有一句super(null, options, ready);实例化父类Component。我们来看下Component的构造函数:

constructor(player, options, ready) {
    // 之前说过所有的类都是继承Component,不是所有的类需要传player
    if (!player && this.play) {
        // 这里判断调用的对象是不是Player本身,是本身只需要返回自己
        this.player_ = player = this; // eslint-disable-line
    } else {
        this.player_ = player;
    }
    this.options_ = mergeOptions({}, this.options_);
    options = this.options_ = mergeOptions(this.options_, options);
    this.id_ = options.id || (options.el && options.el.id);
    if (!this.id_) {
        const id = player && player.id && player.id() || 'no_player';
        this.id_ = `${id}_component_${Guid.newGUID()}`;
    }
    this.name_ = options.name || null;
    if (options.el) {
        this.el_ = options.el;
    } else if (options.createEl !== false) {
        this.el_ = this.createEl();
    }
    this.children_ = [];
    this.childIndex_ = {};
    this.childNameIndex_ = {};
    // 知道Player的构造函数为啥要设置initChildren为false了吧
    if (options.initChildren !== false) {
        // 这个initChildren方法是将一个类的子类都实例化,一个类都对应着自己的el(DOM实例),通过这个方法父类和子类的DOM继承关系也就实现了
        this.initChildren();
    }
    this.ready(ready);
    if (options.reportTouchActivity !== false) {
        this.enableTouchActivity();
    }
}

插件的运行机制

插件的定义

import Player from './player.js';
// 将插件种植到Player的原型链
const plugin = function(name, init) {
  Player.prototype[name] = init;
};
// 暴露plugin接口
videojs.plugin = plugin;

插件的运行

// 在Player的构造函数里判断是否使用了插件,如果有遍历执行
if (options.plugins) {
    const plugins = options.plugins;
    Object.getOwnPropertyNames(plugins).forEach(function(name) {
    if (typeof this[name] === 'function') {
        this[name](plugins[name]);
    } else {
        log.error('Unable to find plugin:', name);
    }
    }, this);
}

控制条是如何运行的

Player.prototype.options_ = {
  // 此处表示默认使用html5的video标签
  techOrder: ['html5', 'flash'],
  html5: {},
  flash: {},
  // 默认的音量,官方代码该配置无效有bug,我们已修复,
  defaultVolume: 0.85,
  // 用户的交互时长,比如超过这个时间表示失去焦点
  inactivityTimeout: 2000,
  playbackRates: [],
  // 这是控制条各个组成部分,作为Player的子类
  children: [
    'mediaLoader',
    'posterImage',
    'textTrackDisplay',
    'loadingSpinner',
    'bigPlayButton',
    'progressBar',
    'controlBar',
    'errorDisplay',
    'textTrackSettings'
  ],
  language: navigator && (navigator.languages && navigator.languages[0] || navigator.userLanguage || navigator.language) || 'en',
  languages: {},
  notSupportedMessage: 'No compatible source was found for this media.'
};

Player类中有个children配置项,这里面是控制条的各个组成部分的类。各个UI类还有子类,都是通过children属性链接的。

UI与JavaScript对象的衔接

video.js里都是组件化实现的,小到一个按钮大到一个播放器都是一个继承了Component类的对象实例,每个对象包含一个el属性,这个el对应一个DOM实例,el是通过createEl生成的DOM实例,在Component基类中包含一个方法createEl方法,子类也可以重写该方法。类与类的从属关系是通过children属性连接。

那么整个播放器是怎么把播放器的UI加载到HTML中的呢?在Player的构造函数里可以看到先生成el,然后初始化父类遍历Children属性,将children中的类实例化并将对应的DOM嵌入到player的el属性中,最后在Player的构造函数中直接挂载到video标签的父级DOM上。

if (tag.parentNode) {
  tag.parentNode.insertBefore(el, tag);
}

这里的tag指的是video标签。

类的挂载方式

上文有提到过UI的从属关系是通过类的children方法连接的,但是所有的类都是关在Component类上的。这主要是基于对模块化的考虑,通过这种方式实现了模块之间的通信。

存储

static registerComponent(name, comp) {
    if (!Component.components_) {
      Component.components_ = {};
    }

    Component.components_[name] = comp;
    return comp;
}

获取

static getComponent(name) {
    if (Component.components_ && Component.components_[name]) {
      return Component.components_[name];
    }

    if (window && window.videojs && window.videojs[name]) {
      log.warn(`The ${name} component was added to the videojs object when it should be registered using videojs.registerComponent(name, component)`);
      return window.videojs[name];
    }
}

在Componet里有个静态方法是registerComponet,所有的组件类都注册到Componet的components_属性里。

例如控制条类ControlBar就是通过这个方法注册的。

Component.registerComponent('ControlBar', ControlBar);

在Player的children属性里包括了controlBar类,然后通过getComponet获取这个类。

.filter((child) => {
  const c = Component.getComponent(child.opts.componentClass ||
                                 toTitleCase(child.name));

  return c && !Tech.isTech(c);
})

android studio theme文件下载、导入,风格修改,包含护眼模式

theme文件导入方法:见文章底部!

android studio theme 1:more-copy

1


android studio theme 2:blueforest

2


android studio theme 3:solarized-light

3


android studio theme 4: solarized-dark

4


android studio theme 5:tomorrow-night-eighties

5


android studio theme 6:tomorrow-night-bright

6


android studio theme 7:tomorrow-night-blue

7


android studio theme 8:tomorrow-night

8


android studio theme 9:tomorrow

9


android studio theme 10:github

10


android studio theme 11:ubuntu

11


android studio theme 12:obsidian

12


android studio theme 13:doushalv

13


android studio theme 14:sublimemonokai

14


风格导入方法:

  1. 下载对应theme文件,将后缀从.txt修改为.icls
  2. 将.icls文件复制到以下目录中
    1. mac:%USERPROFILE%/.AndroidStudio/config/colors
    2. windows:我也不知道,你自己找一下吧
  3. 重启android studio,如果未生效,再多重启几次
  4. 设置android studio:
    1. mac:Android Studio -> Preferences -> Editor -> General -> Scheme,选择对应的风格即可。
    2. windows:我也不知道,你自己找一下吧

 

android补间动画和属性动画的区别

Android3.0之前提供的补间动画机制还算相对比较健全的,比如你的需求中只需要对View进行移动、缩放、旋转和淡入淡出的操作,那么补间动画已经足够健全了。但是,如果一旦需求超出了这四种操作,补间动画就无能为力了。
例如,我们需要改变View的宽度,这个时候就不能通过补间动画实现。此外,补间动画还有一个最大的缺陷,就是它只是改变了View的显示效果而已,并不会真正的改变View的属性。具体来说,例如屏幕左上角有一个Button,使用补间动画将其移动到右下角,此刻你去点击右下角的Button,它是绝对不会响应点击事件的,因此其作用区域依然还在左上角。只不过是补间动画将其绘制在右下角而已。

Android动画Tween Animation详解(alpha、scale、translate、rotate)

前面讲了动画中的Frame动画,今天就来详细讲解一下Tween动画的使用。

同样,在开始实例演示之前,先引用官方文档中的一段话:

Tween动画是操作某个控件让其展现出旋转、渐变、移动、缩放的这么一种转换过程,我们成为补间动画。我们可以以XML形式定义动画,也可以编码实现。

如果以XML形式定义一个动画,我们按照动画的定义语法完成XML,并放置于/res/anim目录下,文件名可以作为资源ID被引用;如果由编码实现,我们需要使用到Animation对象。

如果用定义XML方式实现动画,我们需要熟悉一下动画XML语法:

<?xml version="1.0" encoding="utf-8"?>  
<set xmlns:android="http://schemas.android.com/apk/res/android"  
    android:interpolator="@[package:]anim/interpolator_resource"  
    android:shareInterpolator=["true" | "false"] >  
    <alpha  
        android:fromAlpha="float"  
        android:toAlpha="float" />  
    <scale  
        android:fromXScale="float"  
        android:toXScale="float"  
        android:fromYScale="float"  
        android:toYScale="float"  
        android:pivotX="float"  
        android:pivotY="float" />  
    <translate  
        android:fromX="float"  
        android:toX="float"  
        android:fromY="float"  
        android:toY="float" />  
    <rotate  
        android:fromDegrees="float"  
        android:toDegrees="float"  
        android:pivotX="float"  
        android:pivotY="float" />
    <set>  
        ...  
    </set>  
</set>

XML文件中必须有一个根元素,可以是<alpha>、<scale>、<translate>、<rotate>中的任意一个,也可以是<set>来管理一个由前面几个元素组成的动画集合。

<set>是一个动画容器,管理多个动画的群组,与之相对应的Java对象是AnimationSet。它有两个属性,android:interpolator代表一个插值器资源,可以引用系统自带插值器资源,也可以用自定义插值器资源,默认值是匀速插值器;稍后我会对插值器做出详细讲解。android:shareInterpolator代表<set>里面的多个动画是否要共享插值器,默认值为true,即共享插值器,如果设置为false,那么<set>的插值器就不再起作用,我们要在每个动画中加入插值器。

<alpha>是渐变动画,可以实现fadeIn和fadeOut的效果,与之对应的Java对象是AlphaAnimation。android:fromAlpha属性代表起始alpha值,浮点值,范围在0.0和1.0之间,分别代表透明和完全不透明,android:toAlpha属性代表结尾alpha值,浮点值,范围也在0.0和1.0之间。

<scale>是缩放动画,可以实现动态调控件尺寸的效果,与之对应的Java对象是ScaleAnimation。android:fromXScale属性代表起始的X方向上相对自身的缩放比例,浮点值,比如1.0代表自身无变化,0.5代表起始时缩小一倍,2.0代表放大一倍;android:toXScale属性代表结尾的X方向上相对自身的缩放比例,浮点值;android:fromYScale属性代表起始的Y方向上相对自身的缩放比例,浮点值;android:toYScale属性代表结尾的Y方向上相对自身的缩放比例,浮点值;android:pivotX属性代表缩放的中轴点X坐标,浮点值,android:pivotY属性代表缩放的中轴点Y坐标,浮点值,对于这两个属性,如果我们想表示中轴点为图像的中心,我们可以把两个属性值定义成0.5或者50%。

<translate>是位移动画,代表一个水平、垂直的位移。与之对应的Java对象是TranslateAnimation。android:fromXDelta属性代表起始X方向的位置,android:toXDelta代表结尾X方向上的位置,android:fromYScale属性代表起始Y方向上的位置,android:toYDelta属性代表结尾Y方向上的位置,以上四个属性都支持三种表示方式:浮点数、num%、num%p;如果以浮点数字表示,代表相对自身原始位置的像素值;如果以num%表示,代表相对于自己的百分比,比如toXDelta定义为100%就表示在X方向上移动自己的1倍距离;如果以num%p表示,代表相对于父类组件的百分比。

<rotate>是旋转动画,与之对应的Java对象是RotateAnimation。android:fromDegrees属性代表起始角度,浮点值,单位:度;android:toDegrees属性代表结尾角度,浮点值,单位:度;android:pivotX属性代表旋转中心的X坐标值,android:pivotY属性代表旋转中心的Y坐标值,这两个属性也有三种表示方式,数字方式代表相对于自身左边缘的像素值,num%方式代表相对于自身左边缘或顶边缘的百分比,num%p方式代表相对于父容器的左边缘或顶边缘的百分比。

另外,在动画中,如果我们添加了android:fillAfter=”true”后,这个动画执行完之后保持最后的状态;android:duration=”integer”代表动画持续的时间,单位为毫米。

如果要把定义在XML中的动画应用在一个ImageView上,代码是这样的:

ImageView image = (ImageView) findViewById(R.id.image);  
Animation testAnim = AnimationUtils.loadAnimation(this, R.anim.test);  
image.startAnimation(testAnim);

下面重点介绍一下插值器的概念:

首先要了解为什么需要插值器,因为在补间动画中,我们一般只定义关键帧(首帧或尾帧),然后由系统自动生成中间帧,生成中间帧的这个过程可以成为“插值”。插值器定义了动画变化的速率,提供不同的函数定义变化值相对于时间的变化规则,可以定义各种各样的非线性变化函数,比如加速、减速等。下面是几种常见的插值器:

Interpolator对象 资源ID 功能作用
AccelerateDecelerateInterpolator @android:anim/accelerate_decelerate_interpolator 先加速再减速
AccelerateInterpolator @android:anim/accelerate_interpolator 加速
AnticipateInterpolator @android:anim/anticipate_interpolator 先回退一小步然后加速前进
AnticipateOvershootInterpolator @android:anim/anticipate_overshoot_interpolator 在上一个基础上超出终点一小步再回到终点
BounceInterpolator @android:anim/bounce_interpolator 最后阶段弹球效果
CycleInterpolator @android:anim/cycle_interpolator 周期运动
DecelerateInterpolator @android:anim/decelerate_interpolator 减速
LinearInterpolator @android:anim/linear_interpolator 匀速
OvershootInterpolator @android:anim/overshoot_interpolator 快速到达终点并超出一小步最后回到终点

然后我们可以这样使用一个插值器:

<set android:interpolator="@android:anim/accelerate_interpolator">  

...  

</set>
<alpha android:interpolator="@android:anim/accelerate_interpolator"  

    .../>

如果只简单地引用这些插值器还不能满足需要的话,我们要考虑一下个性化插值器。我们可以创建一个插值器资源修改插值器的属性,比如修改AnticipateInterpolator的加速速率,调整CycleInterpolator的循环次数等。为了完成这种需求,我们需要创建XML资源文件,然后将其放于/res/anim下,然后再动画元素中引用即可。我们先来看一下几种常见的插值器可调整的属性:

<accelerateDecelerateInterpolator> 无

<accelerateInterpolator> android:factor 浮点值,加速速率,默认为1

<anticipateInterploator> android:tension 浮点值,起始点后退的张力、拉力数,默认为2

<anticipateOvershootInterpolator> android:tension 同上 android:extraTension 浮点值,拉力的倍数,默认为1.5(2  * 1.5)

<bounceInterpolator> 无

<cycleInterplolator> android:cycles 整数值,循环的个数,默认为1

<decelerateInterpolator> android:factor 浮点值,减速的速率,默认为1

<linearInterpolator> 无

<overshootInterpolator> 浮点值,超出终点后的张力、拉力,默认为2

下面我们就拿最后一个插值器来举例:

<?xml version="1.0" encoding="utf-8"?>  
<overshootInterpolator xmlns:android="http://schemas.android.com/apk/res/android"  
    android:tension="7.0"/>

上面的代码中,我们把张力改为7.0,然后将此文件命名为my_overshoot_interpolator.xml,放置于/res/anim下,我们就可以引用到自定义的插值器了:

<scale xmlns:android="http://schemas.android.com/apk/res/android"  
    android:interpolator="@anim/my_overshoot_interpolator"  
    .../>

如果以上这些简单的定义还不能满足我们的需求,那么我们就需要考虑一下自己定义插值器类了。

我们可以实现Interpolator接口,因为上面所有的Interpolator都实现了Interpolator接口,这个接口定义了一个方法:float getInterpolation(float input);

此方法由系统调用,input代表动画的时间,在0和1之间,也就是开始和结束之间。

线性(匀速)插值器定义如下:

public float getInterpolation(float input) {  
    return input;  
}

加速减速插值器定义如下:

public float getInterpolation(float input) {  
    return (float)(Math.cos((input + 1) * Math.PI) / 2.0f) + 0.5f;  
}

有兴趣的话,大家可以尝试一下自定义一个插值器。

讲了这么久的概念,下面我们就结合实例来演示一下几种Tween动画的应用。

先来介绍一下旋转动画的使用,布局文件/res/layout/rotate.xml如下:

<?xml version="1.0" encoding="utf-8"?>  
<LinearLayout  
  xmlns:android="http://schemas.android.com/apk/res/android"  
  android:orientation="vertical"  
  android:layout_width="fill_parent"  
  android:layout_height="fill_parent"  
  android:background="#FFFFFF">  
  <ImageView  
        android:id="@+id/piechart"  
        android:layout_width="wrap_content"  
        android:layout_height="wrap_content"  
        android:layout_gravity="center_horizontal"  
        android:src="@drawable/piechart"/>  
  <Button  
        android:id="@+id/positive"  
        android:layout_width="fill_parent"  
        android:layout_height="wrap_content"  
        android:text="顺时针"  
        android:onClick="positive"/>  
  <Button  
        android:id="@+id/negative"  
        android:layout_width="fill_parent"  
        android:layout_height="wrap_content"  
        android:text="逆时针"  
        android:onClick="negative"/>       
</LinearLayout>

我们定义了一个ImageView,用于显示一个饼状图,演示旋转动画,然后定义了两个按钮,用以运行编码实现的动画。动画定义文件/res/anim/rotate.xml如下:

<?xml version="1.0" encoding="utf-8"?>  
<set xmlns:android="http://schemas.android.com/apk/res/android"  
    android:interpolator="@android:anim/accelerate_decelerate_interpolator">  
    <rotate  
        android:fromDegrees="0"  
        android:toDegrees="+360"  
        android:pivotX="50%"  
        android:pivotY="50%"  
        android:duration="5000"/>  
</set>

最后再来看一下RotateActivity.java代码:

package com.scott.anim;  

import android.app.Activity;  
import android.os.Bundle;  
import android.view.View;  
import android.view.animation.Animation;  
import android.view.animation.AnimationUtils;  
import android.view.animation.LinearInterpolator;  
import android.view.animation.RotateAnimation;  

public class RotateActivity extends Activity {  
    private int currAngle;  
    private View piechart;  
  
    @Override  
    public void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.rotate);  

        piechart = findViewById(R.id.piechart);  
        Animation animation = AnimationUtils.loadAnimation(this, R.anim.rotate);  
        piechart.startAnimation(animation);  
    }  

    public void positive(View v) {  
        Animation anim = new RotateAnimation(currAngle, currAngle + 180, Animation.RELATIVE_TO_SELF, 0.5f,  
                Animation.RELATIVE_TO_SELF, 0.5f);  
        /** 匀速插值器 */  
        LinearInterpolator lir = new LinearInterpolator();  
        anim.setInterpolator(lir);  
        anim.setDuration(1000);
  
        /** 动画完成后不恢复原状 */  
        anim.setFillAfter(true);  
        currAngle += 180;  
        if (currAngle > 360) {  
            currAngle = currAngle - 360;  
        }  
        piechart.startAnimation(anim);  
    }  

    public void negative(View v) {  
        Animation anim = new RotateAnimation(currAngle, currAngle - 180, Animation.RELATIVE_TO_SELF, 0.5f,  
                Animation.RELATIVE_TO_SELF, 0.5f);  
        /** 匀速插值器 */  
        LinearInterpolator lir = new LinearInterpolator();  
        anim.setInterpolator(lir);  
        anim.setDuration(1000);  

        /** 动画完成后不恢复原状 */  
        anim.setFillAfter(true);  
        currAngle -= 180;  
        if (currAngle < -360) {  
            currAngle = currAngle + 360;  
        }  
        piechart.startAnimation(anim);  
    }  
}

然后,看一下渐变动画,布局文件/res/layout/alpha.xml如下:

<?xml version="1.0" encoding="utf-8"?>  
<FrameLayout  
  xmlns:android="http://schemas.android.com/apk/res/android"  
  android:layout_width="fill_parent"  
  android:layout_height="fill_parent"  
  android:background="#FFFFFF">  
  <ImageView  
      android:id="@+id/splash"  
      android:layout_width="fill_parent"  
      android:layout_height="fill_parent"  
      android:layout_gravity="center"  
      android:src="@drawable/splash"/>  
  <Button  
      android:layout_width="fill_parent"  
      android:layout_height="wrap_content"  
      android:layout_gravity="bottom"  
      android:text="alpha"  
      android:onClick="alpha"/>    
</FrameLayout>

动画定义文件/res/anim/alpha.xml如下:

<?xml version="1.0" encoding="utf-8"?>  
<set xmlns:android="http://schemas.android.com/apk/res/android">  
    <alpha  
        android:fromAlpha="0.0"  
        android:toAlpha="1.0"  
        android:duration="3000"/>  
</set>

AlphaActivity.java代码如下:

package com.scott.anim;  

import android.app.Activity;  
import android.os.Bundle;  
import android.util.Log;  
import android.view.View;  
import android.view.animation.AlphaAnimation;  
import android.view.animation.Animation;  
import android.view.animation.Animation.AnimationListener;  
import android.view.animation.AnimationUtils;  
import android.widget.ImageView;  

public class AlphaActivity extends Activity implements AnimationListener {  
    private ImageView splash;  
    @Override  
    protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.alpha);  
     
        splash = (ImageView) findViewById(R.id.splash);  
        Animation anim = AnimationUtils.loadAnimation(this, R.anim.alpha);  
        anim.setAnimationListener(this);  
        splash.startAnimation(anim);  
    }  

    public void alpha(View view) {  
        Animation anim = new AlphaAnimation(1.0f, 0.0f);  
        anim.setDuration(3000);  
        anim.setFillAfter(true);  
        splash.startAnimation(anim);  
    }  
  
    @Override  
    public void onAnimationStart(Animation animation) {  
        Log.i("alpha""onAnimationStart called.");  
    }  

    @Override  
    public void onAnimationEnd(Animation animation) {  
        Log.i("alpha""onAnimationEnd called");  
    }  
 
    @Override  
    public void onAnimationRepeat(Animation animation) {  
        Log.i("alpha""onAnimationRepeat called");  
    }  
}

接着看一下位移动画,布局文件/res/layout/translate.xml如下:

<?xml version="1.0" encoding="utf-8"?>  
<FrameLayout  
  xmlns:android="http://schemas.android.com/apk/res/android"  
  android:orientation="vertical"  
  android:layout_width="fill_parent"  
  android:layout_height="fill_parent">  
  <ImageView  
    android:id="@+id/trans_image"  
    android:layout_width="wrap_content"  
    android:layout_height="wrap_content"  
    android:layout_weight="1"  
    android:src="@drawable/person"/>  
  <Button  
    android:id="@+id/trans_button"  
    android:layout_width="fill_parent"  
    android:layout_height="wrap_content"  
    android:layout_gravity="bottom"  
    android:text="translate"  
    android:onClick="translate"/>  
</FrameLayout>

动画定义文件/res/anim/translate.xml如下:

<?xml version="1.0" encoding="utf-8"?>  
<set xmlns:android="http://schemas.android.com/apk/res/android"  
    android:interpolator="@android:anim/bounce_interpolator">  
    <translate  
        android:fromXDelta="0"  
        android:fromYDelta="0"  
        android:toXDelta="200"  
        android:toYDelta="300"  
        android:duration="2000"/>  
</set>

然后TranslateActivity.java代码如下:

package com.scott.anim;  
  
import android.app.Activity;  
import android.os.Bundle;  
import android.view.View;  
import android.view.animation.Animation;  
import android.view.animation.AnimationUtils;  
import android.view.animation.OvershootInterpolator;  
import android.view.animation.TranslateAnimation;  
import android.widget.ImageView;  
  
public class TranslateActivity extends Activity {  
     
    private ImageView trans_iamge; 
      
    @Override  
    protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.tanslate);  
        trans_iamge = (ImageView) findViewById(R.id.trans_image);  
        Animation anim = AnimationUtils.loadAnimation(this, R.anim.translate);  
        anim.setFillAfter(true);  
        trans_iamge.startAnimation(anim);  
    }  
      
    public void translate(View view) {  
        Animation anim = new TranslateAnimation(20003000);  
        anim.setDuration(2000);  
        anim.setFillAfter(true);  
        OvershootInterpolator overshoot = new OvershootInterpolator();  
        anim.setInterpolator(overshoot);  
        trans_iamge.startAnimation(anim);  
    }  
}

最后,我们再来看以下缩放动画,布局文件/res/layout/scale.xml如下:

<?xml version="1.0" encoding="utf-8"?>  
<LinearLayout  
  xmlns:android="http://schemas.android.com/apk/res/android"  
  android:orientation="vertical"  
  android:layout_width="fill_parent"  
  android:layout_height="fill_parent">  
  <ImageView  
    android:id="@+id/scale_image"  
    android:layout_width="wrap_content"  
    android:layout_height="wrap_content"  
    android:layout_gravity="center"  
    android:layout_weight="1"  
    android:src="@drawable/person"/>  
  <Button  
    android:id="@+id/scale_button"  
    android:layout_width="fill_parent"  
    android:layout_height="wrap_content"  
    android:layout_gravity="bottom"  
    android:text="scale"  
    android:onClick="sclae"/>  
</LinearLayout>

动画定义文件/res/anim/scale.xml如下:

<?xml version="1.0" encoding="utf-8"?>  
<set xmlns:android="http://schemas.android.com/apk/res/android"  
    android:interpolator="@android:anim/bounce_interpolator">  
    <scale  
        android:fromXScale="1.0"  
        android:toXScale="2.0"  
        android:fromYScale="1.0"  
        android:toYScale="2.0"  
        android:pivotX="0.5"  
        android:pivotY="50%"  
        android:duration="2000"/>  
    <alpha  
        android:fromAlpha="0.0"  
        android:toAlpha="1.0"  
        android:duration="3000"/>  
</set>

然后ScaleActivity.java代码如下:

package com.scott.anim;  
  
import android.app.Activity;  
import android.os.Bundle;  
import android.view.View;  
import android.view.animation.Animation;  
import android.view.animation.AnimationUtils;  
import android.view.animation.BounceInterpolator;  
import android.view.animation.ScaleAnimation;  
import android.widget.ImageView;  
  
public class ScaleActivity extends Activity {  
      
    private ImageView scale_iamge;  
      
    @Override  
    protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.scale);  
        scale_iamge = (ImageView) findViewById(R.id.scale_image);  
        Animation anim = AnimationUtils.loadAnimation(this, R.anim.scale);  
        anim.setFillAfter(true);  
        scale_iamge.startAnimation(anim);  
    }  
      
    public void sclae(View view) {  
        Animation anim = new ScaleAnimation(2.0f, 1.0f, 2.0f, 1.0f,   
                Animation.RELATIVE_TO_SELF, 0.5f,   
                Animation.RELATIVE_TO_SELF, 0.5f);  
        anim.setDuration(2000);  
        anim.setFillAfter(true);  
        BounceInterpolator bounce = new BounceInterpolator();  
        anim.setInterpolator(bounce);  
        scale_iamge.startAnimation(anim);  
    }  
}

 

Android面试题整理

Java部分

1.GC是什么? 为什么要有GC?

GC是垃圾收集的意思(Gabage Collection),内存处理是编程人员容易出现问题的地方,忘记或者错误的内存回收会导致程序或系统的不稳定甚至崩溃,Java 提供的GC功能可以自动监测对象是否超过作用域从而达到自动回收内存的目的,Java语言没有提供释放已分配内存的显示操作方法。

2.XML包括哪些解释技术,区别是什么?

DOM和SAX
DOM将文档解析成一颗文档树,可在节点上进行遍历、增加、修改和删除。一次性读入内存,对内存消耗大。
SAX至上而下解析文档,以事件进行驱动。不会一次性读入内存,对内存消耗小,不能任意读取节点,并且不能对节点进行增加、修改和删除。

3.switch语句能否作用在byte上,能否作用在long上,能否作用在String上?

switch能作用在byte、char、short和int上,JDK1.7后可以作用在String上。

4.”==”和equals方法究竟有什么区别?

==和equals都可以比较地址。==是运算符,equals是方法,方法可以通过重写改变其行为,如String的equals就是比较字符串内容。

5.构造方法能否被重写和重载?

构造方法不能被重写但是能被重载。

6.面向对象的特征有哪些?

封装、继承、多态和抽象。

7.抽象类和接口的区别?

1).抽象类是abstract class修饰,接口是interface修饰。
2).抽象类可以有任意类型的属性,接口只能有静态常量修饰的属性。
3).抽象类可以有普通方法和抽象法方法,接口的方法都是抽象方法。
4).抽象类和接口都不能实例化,但是抽象类有构造方法,接口没有构造方法。
5).抽象类只能单根继承,接口可以多重实现。

8.内部类可以引用它的包含类的成员吗?有没有什么限制?

可以引用。如果需要指定当前类时要用外部类.this来引用。如果引用局部变量,需要将局部变量指定为final。

9.String s = new String(“xyz”);创建了几个String Object? 二者之间有什么区别?

2个对象。”xyz”创建在字符串常量池中,new String()创建在堆中。

10.try {}里有一个return语句,那么紧跟在这个try后的finally {}里的code会不会被执行,什么时候被执行,在return前还是后?

会在return前执行。

11.Integer与int的区别

Integer为包装类,int是基本数据类型。包装类拥有方法和属性,基本数据类型不具备。包装类可以通过intValue来转换成基本数据类型,也可以通过new Integer()将基本数据类型转换为包装类。在JDK1.5后,包装类和基本数据类型可以实现自动转换。

12.sleep()和wait()有什么区别?

sleep是Thread类的方法,wait是Object类的方法。
sleep是自动唤醒,wait需要其他线程来唤醒。
sleep不会释放同步锁,wait会释放同步锁。
sleep可以用在任意方法中,wait只能用在同步方法或同步块中。
Sleep()不会释放对象锁到时自动恢复, wait()会释放对象锁 进入等待此对象的等待锁定池 发出notify()方法后 才进入等待锁定池准备对象锁的获 取进入运行状态

13.同步和异步有何异同,在什么情况下分别使用他们?

同步指同一时间只能一个线程执行该方法,其他线程需要等待。异步指多个线程可以同时执行某个方法,并共享同一资源。
同步可以让访问的资源具有安全性,因为同一时间只能一个线程对其进行访问。但是效率不高。
异步对访问的资源会造成不稳定性,比如多个线程同时访问一个资源,一个在修改、一个在删除、一个在读取,这样可能会造成资源的混乱。但是由于同时运行, 执行效率得到提高。

14.启动一个线程是用run()还是start()?

start()方法启动线程,run方法是线程执行的主方法。

15.java中有几种类型的流?JDK为每种类型的流提供了一些抽象类以供继承,请说出他们分别是哪些类?

java中有三种流,分别是字节流(InputStream、OutputStream)、字符流(Reader、Writer)、对象流(ObjectInputStream、 ObjectOutputStream)。

16.字节流与字符流的区别?

字节流用于读取或写出二进制数据,比如图片、影像等数据。
字符流用于读取或写出字符数据,比如传输字符串。
所有的数据都可以通过字节流来进行处理,不过如果是字符数据,用字节流还需要进行转换后传输,如果使用字符流可以方便数据的转换。

17.error和exception有什么区别?

error是系统错误,代码不能处理的错误,比如内存溢出、堆栈溢出等。
exception是程序异常,可以通过代码try-catch进行处理,比如空指针异常,数组越界等。

18.谈谈final,finally,finalize的区别?

final是修饰符,可以修饰类(不能被继承)、属性(常量)、和方法(不能被重写)。
finally是异常处理块中的代码块,表示无论如何都会执行的代码块。
finalize是Object类的方法,该方法在对象被垃圾回收之前执行的方法。

19.当一个线程进入一个对象的一个synchronized方法后,其它线程是否可进入此对象的其它方法?

如果其他方法没有加synchronized的话是可以进入的。

20.当一个对象被当作参数传递到一个方法后,此方法可改变这个对象的属性,并可返回变化后的结果,那么这里到底是值传递还是引用传递?

java中只有值传递,如果传递的对象,实际也是传递该对象的地址。

21.作用域public,private,protected,以及不写时的区别

public公共修饰符,表示任意类都可以访问。
protected为受保护的修饰符,表示同类、同包以及不同包但是父子关系的是可以访问。
不写表示默认修饰符,或者称为package修饰符,该修饰符表示只有同类或同包下的类可以访问,出了这个包就不能访问了。
private为私有修饰符,表示只有同类中可以访问,出了这个类就不能访问了。

22.用最有效率的方法算出2乘以8等於几 2《 3

将2的二进制向左移3位。java中用<<来移位。

23.heap和stack有什么区别。

heap表示堆,stack表示栈。堆中放对象,栈中放引用变量。
堆空间是一个无序的空间,栈是先进后出的结构。

24.运行时异常与一般异常有何异

运行时异常是指继承于RuntimeException的异常,这些异常在编译时可以不进行处理,当运行时如果出现问题才会抛出。如NullPointException、 ArrayIndexOutOfBoundsException
一般异常也称为编译时异常,这些异常是继承Exception但又不属于RuntimeException的子类,如果程序中出现这些异常,在编译时必须进行捕获或抛出,否 则编译无法通过。如IOException、FileNotFoundException

25.垃圾回收的优点和原理。并考虑2种回收机制

Java语言中一个显著的特点就是引入了垃圾回收机制,使c++程序员最头疼的内存管理的问题迎刃而解,它使得Java程序员在编写程序的时候不再需要考虑内存 管理。由于有个垃圾回收机制,Java中的对象不再有”作用域”的概念,只有对象的引用才有”作用域”。垃圾回收可以有效的防止内存泄露,有效的使用可以使用 的内存。垃圾回收器通常是作为一个单独的低级别的线程运行,不可预知的情况下对内存堆中已经死亡的或者长时间没有使用的对象进行清楚和回收,程序员不能 实时的调用垃圾回收器对某个对象或所有对象进行垃圾回收。回收机制有分代复制垃圾回收和标记垃圾回收,增量垃圾回收。

26.描述一下JVM加载class文件的原理机制?

JVM中类的装载是由ClassLoader和它的子类来实现的,Java ClassLoader 是一个重要的Java运行时系统组件。它负责在运行时查找和装入类文件的类。

27.是否可以从一个static方法内部发出对非static方法的调用?

不能,除非先创建非static方法所在类的对象。

28.什么是java序列化,如何实现java序列化?

序列化就是一种用来处理对象流的机制,所谓对象流也就是将对象的内容进行流化。可以对流化后的对象进行读写操作,也可将流化后的对象传输于网络之间。序 列化是为了解决在对对象流进行读写操作时所引发的问题。
序列化的实现:将需要被序列化的类实现Serializable接口,该接口没有需要实现的方法,implements Serializable只是为了标注该对象是可被序列化 的,然后使用一个输出流(如:FileOutputStream)来构造一个ObjectOutputStream(对象流)对象,接着,使用ObjectOutputStream对象的 writeObject(Object obj)方法就可以将参数为obj的对象写出(即保存其状态),要恢复的话则用输入流。

29.Anonymous Inner Class(匿名内部类)是否可以extends(继承)其它类,是否可以implements(实现)interface(接口)?

匿名内部类可以继承类或实现接口,但不是显示的使用extends或implements来继承或实现。

30.ArrayList和Vector的区别,HashMap和Hashtable的区别?

ArrayList是JDK1.2的集合类并且线程不安全,Vector是1.0的集合类并且线程安全,二者用法类似。
HashMap线程不安全且能放空键或空值,Hashtable线程安全且不能放空键或空值。

31.String 和StringBuffer有什么差别?在什么情况下使用它们?

String字符串的基本类,该字符串是不可变的。StringBuffer是利用堆来存储字符串,并且可以对字符串的内容进行改变。

32.new一个类对象和使用类名创建一个对象有什么区别?二者使用时应该注意什么?

new对象是最常见的创建对象的方式,利用类模板是通过反射来创建对象。虽然new对象时在底层也会通过类模板来创建对象,但是new对象的效率要比直接通过类 模板创建对象的方式要高。
但是使用类模板的方式可以让程序的灵活性提高。

33.LinkedList和ArrayList的区别?

1)LinkedList是链表结构的集合,ArrayList数组结构的集合。
2)LinkedList在中间或前面增加或删除数据时效率比ArrayList高。
3)LinkedList在最后添加或删除数据时效率比ArrayList低。
4)遍历数据时ArrayList效率高于LinkedList。

34.介绍JAVA开发中常用的Collection FrameWork(集合框架)?

Java中集合框架分为Collection和Map接口,Collection接口下的集合每个元素都由一个值组成,Map接口下的集合类每个元素都是由键值对组成。
Collection接口下面有List和Set接口,List接口下常见的类有ArrayList、LinkedList、Vector。它们中的元素可以重复,并且是有序的。Set接口下常 见的类有HashSet、TreeSet。它们中的元素不能重复,并且是无序的。

35.在异常当中 throw和throws 有什么区别和联系?

throw是在代码中抛出一个异常,后面跟的是异常对象,虚拟机运行到这里时会立即引发一个异常。
throws是写在方法声明上的,表示声明该方法可能会抛出异常,后面跟的是异常类型。调用该方法的时候可以选择处理它或继续往外抛。

36.重载和重写的区别

重载是指在一个类中,两个或两个以上的方法具有相同方法名和不同参数列表,则表示这些方法为重载方法。
重写是指在父类和子类中,子类的方法和父类的方法具有相同方法名、相同参数列表、相同返回类型、子类的访问修饰符范围不小于父类的访问修饰符范围,异常 的类型和个数不大于或多于父类的异常类型和个数,则表示该方法为重写方法。换句话说重载方法是区分同一个类中相同方法名的方法,重写方法是找到父类相同 方法名的方法并重新改变方法的行为。

37.Java中try catch finally的执行顺序

先执行try中代码发生异常执行catch中代码,最后一定会执行finally中代码

38.内存泄露的原因:

  • 资源对象没关闭。
    如Cursor、File等资源。他们会在finalize中关闭,但这样效率太低。容易造成内存泄露。
    SQLiteCursor,当数据量大的时候容易泄露
  • 使用Adapter时,没有使用系统缓存的converView。
  • 即时调用recycle()释放不再使用的Bitmap。
    适当降低Bitmap的采样率,如:
    BitmapFactory.Options options = newBitmapFactory.Options();
    options.inSampleSize = 2;//图片宽高都为原来的二分之一,即图片为原来的四分之一
    Bitmap bitmap =BitmapFactory.decodeStream(cr.openInputStream(uri), null, options); preview.setImageBitmap(bitmap);
  • 使用application的context来替代activity相关的context。
    尽量避免activity的context在自己的范围外被使用,这样会导致activity无法释放。
  • 注册没取消造成内存泄露
    如:广播
    集合中的对象没清理造成的内存泄露我们通常把一些对象的引用加入到了集合中,当我们不需要该对象时,并没有把它的引用从集合中清理掉,这样这个集合就会 越来越大。如果这个集合是static的话,那情况就更严重了。
  • Handler应该申明为静态对象, 并在其内部类中保存一个对外部类的弱引用。如下:
    static class MyHandler extends Handler
    {

     WeakReference<Activity > mActivityReference;
     MyHandler(Activity activity)
    { 
          mActivityReference= new WeakReference<Activity>(activity);
    }
    

    @Override
    public void handleMessage(Message msg)
    {

     final Activity activity = mActivityReference.get();
     if (activity != null)
    {
           mImageView.setImageBitmap(mBitmap);
    }    
    

    }
    }

39.Iterator和Enumeration的不同

  • 函数接口不同
    Enumeration只有2个函数接口。通过Enumeration,我们只能读取集合的数据,而不能对数据进行修改。 Iterator只有3个函数接口。Iterator除了能读 取集合的数据之外,也能数据进行删除操作。
  • Iterator支持fail-fast机制,而Enumeration不支持。 Enumeration 是JDK 1.0添加的接口。使用到它的函数包括Vector、Hashtable等类,这些类 都是JDK 1.0中加入的,Enumeration存在的目的就是为它们提供遍历接口。Enumeration本身并没有支持同步,而在Vector、Hashtable实现 Enumeration时,添加了同步。而Iterator 是JDK 1.2才添加的接口,它也是为了HashMap、ArrayList等集合提供遍历接口。
    Iterator是支持fail-fast 机制的:当多个线程对同一个集合的内容进行操作时,就可能会产生fail-fast事件。
    ail-fast 机制是java集合(Collection)中的一种错误机制。当多个线程对同一个集合的内容进行操作时,就可能会产生fail-fast事件。例如:当某一个线 程A通过iterator去遍历某集合的过程中,若该集合的内容被其他线程所改变了;那么线程A访问集合时,就会抛出ConcurrentModificationException异 常,产生fail-fast事件。

40.接口的注意点

  • 接口中的字段全部默认为 public static类型。
  • 接口中的方法全部默认为 public类型。
  • 接口中可以申明内部类,而默认为public static,正因为是static,只是命名空间属于接口,代码逻辑不属于接口。所以不违法接口定义。
  • 接口本身可以申明为public或者缺省。
  • 抽象类继承自某接口。如果在抽象类中实现了父类(接口)中的方法,在其子类可以不用实现,否则在子类必须实现。

41.final方法

将方法声明为final那有两个原因,第一就是说明你已经知道这个方法提供的功能已经满足你要求,不需要进行扩展,并且也不允许任何从此类继承的类来覆写这个方法,但是继承仍然可以继承这个方法,也就是说可以直接使用。第二就是允许编译器将所有对此方法的调用转化为inline调用的机制,它会使你在调用final方法时,直接将方法主体插入到调用处,而不是进行例行的方法调用,例如保存断点,压栈等,这样可能会使你的程序效率有所提高,然而当你的方法主体非常庞大时,或你在多处调用此方法,那么你的调用主体代码便会迅速膨胀,可能反而会影响效率,所以你要慎用final进行方法定义。

ANDROID部分

1.activity、Service、BroadcastReceiver的作用(android)

Activity:Activity是Android程序与用户交互的窗口,是Android构造块中最基本的一种,它需要为保持各界面的状态,做很多持久化的事情,妥善管理生命周期以及一些跳转逻辑
service:后台服务于Activity,封装有一个完整的功能逻辑实现,接受上层指令,完成相关的指令,定义好需要接受的Intent提供同步和异步的接口
BroadCast Receiver:接受一种或者多种Intent作触发事件,接受相关消息,做一些简单处理,转换成一条Notification,统一了Android的事件广播模型

2.描述一个完整的Android activity lifecycle

activity的生命周期方法有:onCreate()、onStart()、onReStart()、onResume()、onPause()、onStop()、onDestory();

3.显式intent和隐式intent的区别是什么(android)

Intent定义:Intent是一种在不同组件之间传递的请求消息,是应用程序发出的请求和意图。作为一个完整的消息传递机制,Intent不仅需要发送端,还需要接收端。
显式Intent定义:对于明确指出了目标组件名称的Intent,我们称之为显式Intent。
隐式Intent定义:对于没有明确指出目标组件名称的Intent,则称之为隐式Intent。
说明:Android系统使用IntentFilter 来寻找与隐式Intent相关的对象。

4.Android中线程同步的方法

线程同步的方法可以采用同步方法和同步块。

5.怎么将一个Activity封装成对话框的样子? 怎样将Activity封装成长按Menu菜单的样子?

简单你只需要设置 一下Activity的主题就可以了在AndroidManifest.xml 中定义 Activity的地方一句话:
Xml代码
android :theme=”@android:style/Theme.Dialog”
android:theme=”@android:style/Theme.Dialog”
这就使你的应用程序变成对话框的形式弹出来了,或者
Xml代码
android:theme=”@android:style/Theme.Translucent”
android:theme=”@android:style/Theme.Translucent”
就变成半透明的。
重写OnCreateOptionMenu方法来处理按下menu后的行为,然后再该方法中弹出对话框形式的Activity。
也可以利用事件监听来监听menu按键,并在该按钮按下后弹出对话框形式的Activity。

6.介绍一下Android系统的体系结构

应用层:android的应用程序通常涉及用户界面和交互。
应用框架层:UI组件、各种管理器等。
函数库层:系统C库、媒体库、webkit、SQLite等。
linux核心库:linux系统运行的组件。

7.描述下横竖屏切换时候 activity 的生命周期

不设置Activity的android:configChanges时,切屏会重新调用各个生命周期,切横屏时会执行一次,切竖屏时会执行两次.
设置Activity的android:configChanges=”orientation”时,切屏还是会重新调用各个生命周期,切横、竖屏时只会执行一次.
设置Activity的android:configChanges=”orientation|keyboardHidden”时,切屏不会重新调用各个生命周期,只会执行onConfigurationChanged方法.

8.android 中的动画有哪几种,它们的特点和区别是什么 ?

两种,一种是补间动画(Tween)动画、还有一种是帧动画(Frame)动画。Tween动画,这种实现方式可以使视图组件移动、放大、缩小以及产生透明度的变化;另一种Frame动画,传统的动画方法,通过顺序的播放排列好的图片来实现,类似电影。

9.一条最长的短信息约占多少 byte?

140byte,70个汉字。

10.描述handler 机制的原理

andriod提供了 Handler 和 Looper 来满足线程间的通信。
Handler 先进先出原则。
Looper类用来管理特定线程内对象之间的消息交换(Message Exchange)。
1)Looper: 一个线程可以产生一个Looper对象,由它来管理此线程里的Message Queue(消息队列)。
2)Handler: 你可以构造Handler对象来与Looper沟通,以便push新消息到Message Queue里;或者接收Looper从Message Queue取出)所送来的消息。
3) Message Queue(消息队列):用来存放线程放入的消息。
4)线程:UI thread 通常就是main thread,而Android启动程序时会替它建立一个Message Queue。

11.如何将 SQLite 数据库 (dictionary.db 文件 ) 与 apk 文件一起发布 ?

可以将dictionary.db文件复制到Eclipse Android工程中的res\raw目录中。所有在res\raw目录中的文件不会被压缩,这样可以直接提取该目录中的文件。
使用openDatabase方法来打开数据库文件,如果该文件不存在,系统会自动创建/sdcard/dictionary目录,并将res\raw目录中的 dictionary.db文件复制到/sdcard/dictionary目录中

12.说说 android 中 mvc 的具体体现

mvc是model,view,controller的缩写,mvc包含三个部分:
模型(model)对象:是应用程序的主体部分,所有的业务逻辑都应该写在该层。
视图(view)对象:是应用程序中负责生成用户界面的部分。也是在整个mvc架构中用户唯一可以看到的一层,接收用户的输入,显示处理结果。
控制器(control)对象:是根据用户的输入,控制用户界面数据显示及更新model对象状态的部分,控制器更重要的一种导航功能,响应用户出发的相关事件,交给m层处理。
android鼓励弱耦合和组件的重用,在android中mvc的具体体现如下:
1)视图(view):一般采用xml文件进行界面的描述,使用的时候可以非常方便的引入。
2)控制层(controller):android的控制层的重任通常落在了众多的acitvity的肩上,这句话也就暗含了不要在acitivity中写过多的代码,要通过activity交割model业务逻辑层处理,这样做的另外一个原因是android中的acitivity的响应时间是5s,如果耗时的操作放在这里,程序就很容易被回收掉。
3)模型层(model):对数据库的操作、对网络等的操作都应该在model里面处理,当然对业务计算等操作也是必须放在的该层的。

13.请介绍下 Android 中常用的五种布局

帧布局(FrameLayout)
线性布局(LinearLayout)
表格布局(TableLayout)
相对布局(RelativeLayout)
绝对布局(AbsoluteLayout)

14.如何启用 Service ,如何停用 Service

1)startService用于启动Service、stopService停止Service。
2)bindService绑定Service,unbindService解除Service的绑定。

15.如何优化ListView

1、如果自定义适配器,那么在getView方法中要考虑方法传进来的参数contentView是否为null,如果为null就创建contentView并返回,如果不为null则直接使用。在这个方法中尽可能少创建view。
2、给contentView设置tag(setTag()),传入一个viewHolder对象,用于缓存要显示的数据,可以达到图像数据异步加载的效果。
3、如果listview需要显示的item很多,就要考虑分页加载。比如一共要显示100条或者更多的时候,我们可以考虑先加载20条,等用户拉到列表底部的时候再去加载接下来的20条。

16.描述4 种 activity 的启动模式

1)standard :系统的默认模式,一次跳转即会生成一个新的实例。假设有一个activity命名为MainActivity,执行语句:
startActivity(new Intent(MainActivity.this, MainActivity.class))后,MainActivity将跳转到另外一个MainActivity,也就是现在的Task栈里面有MainActivity的两个实例。按返回键后你会发现仍然是在MainActivity(第一个)里面。
2)singleTop:singleTop 跟standard 模式比较类似。如果已经有一个实例位于Activity栈的顶部时,就不产生新的实例,而只是调用Activity中的newInstance()方法。如果不位于栈顶,会产生一个新的实例。例:当MainActivity为 singleTop 模式时,执行跳转后栈里面依旧只有一个实例,如果现在按返回键程序将直接退出。
3)singleTask: singleTask模式和后面的singleInstance模式都是只创建一个实例的。在这种模式下,无论跳转的对象是不是位于栈顶的activity,程序都不会生成一个新的实例(当然前提是栈里面已经有这个实例)。这种模式相当有用,在以后的多activity开发中,经常会因为跳转的关系导致同个页面生成多个实例,这个在用户体验上始终有点不好,而如果你将对应的activity声明为 singleTask 模式,这种问题将不复存在。
4)singleInstance: 设置为 singleInstance 模式的 activity 将独占一个task(感觉task可以理解为进程),独占一个task的activity与其说是activity,倒不如说是一个应用,这个应用与其他activity是独立的,它有自己的上下文activity。

17.什么是Intent,如何使用?

Android基本的设计理念是鼓励减少组件间的耦合,因此Android提供了Intent (意图) ,Intent提供了一种通用的消息系统,它允许在你的应用程序与其它的应用程序间传递Intent来执行动作和产生事件。使用Intent可以激活Android应用的三个核心组件:活动、服务和广播接收器。
通过startActivity() orstartActivityForResult()启动一个Activity;
过 startService() 启动一个服务,或者通过bindService() 和后台服务交互;
通过广播方法(比如 sendBroadcast(),sendOrderedBroadcast(),sendStickyBroadcast())发给broadcast receivers

18.Android用的数据库是什么样的?它和sql有什么区别?为什么要用ContentProvide?它和sql的实现上有什么差别?

Adnroid用的是SQLite数据库。它和其他网络数据库类似,也是通过SQL对数据进行管理。SQLite的操作非常简单,包括数据类型在建表时也可以不指定。
使用ContentProvider 可以将数据共享给其他应用,让除本应用之外的应用也可以访问本应用的数据。它的底层是用SQLite 数据库实现的,所以其对数据做的各种操作都是以Sql实现,只是在上层提供的是Uri。

19.通过Intent传递一些二进制数据的方法有哪些?

1)使用Serializable接口实现序列化,这是Java常用的方法。
2)实现Parcelable接口,这里Android的部分类比如Bitmap类就已经实现了,同时Parcelable在Android AIDL中交换数据也很常见的。

20.对一些资源以及状态的操作保存,最好是保存在生命周期的哪个函数中进行?

onResume()恢复数据、onPause()保存数据。

21.如何一次性退出所有打开的Activity

编写一个Activity作为入口,当需要关闭程序时,可以利用Activity的SingleTop模式跳转该Activity,它上面的所有Activity都会被销毁掉。然后再将该Activity关闭。
或者再跳转时,设置intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);这样也能将上面的Activity销毁掉。

22.说说Service的生命周期?

启动Service的方式有两种,各自的生命周期也有所不同。
一、通过startService启动Service:onCreate、onStartCommand、onDestory。
二、通过bindService绑定Service:onCreate、onBind、onUnbind、onDestory。

23.什么是AIDL?AIDL是如何工作的?

AIDL(Android接口描述语言)是一种接口描述语言; 编译器可以通过aidl文件生成一段代码,通过预先定义的接口达到两个进程内部通信进程的目的. 如果需要在一个Activity中, 访问另一个Service中的某个对象, 需要先将对象转化成AIDL可识别的参数(可能是多个参数), 然后使用AIDL来传递这些参数, 在消息的接收端, 使用这些参数组装成自己需要的对象。AIDL是基于接口的,但它是轻量级的。它使用代理类在客户端和实现层间传递值.。

24.Android如何把文件存放在SDCard上?

在AndroidManifest.xml中加入访问SDCard的权限如下:
<!– 在SDCard中创建与删除文件权限 –>
<!– 往SDCard写入数据权限 –>
要往SDCard存放文件,程序必须先判断手机是否装有SDCard,并且可以进行读写。
注意:访问SDCard必须在AndroidManifest.xml中加入访问SDCard的权限。
Environment.getExternalStorageState()方法用于获取SDCard的状态,如果手机装有SDCard,并且可以进行读写,那么方法返回的状态等于Environment.MEDIA_MOUNTED。
Environment.getExternalStorageDirectory()方法用于获取SDCard的目录。

25.注册广播有几种方式,这些方式有何优缺点?

两种。一种是通过代码注册,这种方式注册的广播会跟随程序的生命周期。二种是在AndroidManifest.xml中配置广播,这种常驻型广播当应用程序关闭后,如果有信息广播来,程序也会被系统调用自动运行。

26.什么是ANR 如何避免它?

在Android上,如果你的应用程序有一段时间响应不够灵敏,系统会向用户显示一个对话框,这个对话框称作应用程序无响应(ANR:Application Not Responding)对话框。用户可以选择让程序继续运行,但是,他们在使用你的应用程序时,并不希望每次都要处理这个对话框。因此,在程序里对响应性能的设计很重要,这样,系统不会显示ANR给用户。要避免它,应该尽量少在主线程做耗时太长的操作,应该将这些操作放在线程当中去做。

27.Android本身的api并未声明会抛出异常,则其在运行时有无可能抛出runtime异常,你遇到过吗?诺有的话会导致什么问题?如何解决?

有可能,比如空指针异常、数组下表越界等异常,这些异常抛出后可能会导致程序FC。在编写代码时应该做好检测,多考虑可能会发生错误的情况,从代码层次解决这些问题。

28.为什么要用 ContentProvider?它和 sql 的实现上有什么差别?

使用ContentProvider 可以将数据共享给其他应用,让除本应用之外的应用也可以访问本应用的数据。它的底层是用SQLite 数据库实现的,所以其对数据做的各种操作都是以Sql实现,只是在上层提供的是Uri。

29.谈谈 UI 中, Padding 和 Margin 有什么区别?

padding指内边距,表示组件内部元素距离组件边框的距离。
marin指外边距,表示组件与组件之间的距离。

30.请介绍下 Android 的数据存储方式。

Android 提供了5种方式存储数据:
1)使用SharedPreferences存储数据;
2)文件存储数据;
3)SQLite数据库存储数据;
4)使用ContentProvider存储数据;
5)网络存储数据;

2016年Android面试题大全,最新整理

1. Intent的几种有关Activity启动的方式有哪些,你了解每个含义吗?

Intent的一些标记有
FLAG_ACTIVITY_BROUGHT_TO_FRONT 、FLAG_ACTIVITY_CLEAR_TOP、
FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET、 FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS、
FLAG_ACTIVITY_MULTIPLE_TASK和FLAG_ACTIVITY_NEW_TASK等。
每种含义大家看SDK文档和具体跑下这样你的记忆会更深刻些。

2. Activity和Task的启动模式有哪些?每种含义是什么?

有关在AndroidManifest.xml中的android:launchMode定义,主要有standard、singleTop、 singleTask和singleInstance,同时对于android:taskAffinity这些问题大家也要了解。

3. 通过Intent传递一些二进制数据的方法有哪些?

1).使用Serializable接口实现序列化,这是Java常用的方法。
2).实现Parcelable接口,这里Android的部分类比如Bitmap类就已经实现了,同时Parcelable在Android AIDL中交换数据也很常见的。

4. 能说下Android应用的入口点吗?

真正的Android入口点是application的main,你可以看下androidmanifest.xml的包含关系就清楚了。可以没有Activity但是必须有Application

5. Android都有哪些XML解析器,都熟练掌握吗?

这里XmlPull、SAX和DOM相信做过Web开发的都已经滚瓜烂熟了。

6. SQLite支持事务吗?添加删除如何提高性能?

SQLite作为轻量级的数据库,比MySQL还小,但支持SQL语句查询,提高性能可以考虑通过原始经过优化的SQL查询语句方式处理。

7. Android Service和Binder、AIDL你都熟练吗?

作为Android重要的后台服务,这些每个Android开发者都应该掌握,这也算是和JavaSE最大的不同了,具体的实例大家可以查看Android音乐播放器的源代码Music.git中的,这里不再赘述。

8.请描述下Activity的生命周期。

创建oncreate – 启动onstart – 恢复onResume – 暂停onPause – 结束onEnd–销毁onDestroy

9.如果后台的Activity由于某原因被系统回收了,如何在被系统回收之前保存当前状态?

在”暂停onPause”状态将数据保存。

10.如何将一个Activity设置成窗口的样式。

设置Theme。

11.请介绍下Android中常用的五种布局。

线性布局LinearLayout,相对布局RelativeLayout
表单布局TableLayout,
绝对布局AbsLayout(已淘汰)帧布局FrameLayout

12.请介绍下Android的数据存储方式。

Preference,文件,数据库SQlite,网络存储

13.如何启用Service,如何停用Service。

启动:
Context.startService()
and Context.bindService().
关闭:
Context.stopService()
Service.stopSelf()orService.stopSelfResult()

14.你如何评价Android系统?优缺点。

优势:一、开放性三、丰富的硬件选择五、无缝结合的Google应用支持
缺点:一、安全和隐私二,开发商自定义,不够统一四、同类机型用户减少

15. android:paddingLeft与android:layout_marginLeft的区别

padding是内部间距,margin是外部间距。

16.Android动画有哪几种?描述一下

Android包含三种动画:View Animation(Tween Animation)、 Drawable Animation、Property Animation(属性动画,Android3.0新引入)

 

文/小程序(简书作者)
原文链接:http://www.jianshu.com/p/d5f650ac5f03
著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。

今日头条架构设计——高压下的架构演进之路

导读:高可用架构在 6 月 25 日举办了『高压下的架构演进』专题沙龙,进行了闭门私董会研讨及对外开放的四个专题的演讲,期望能促进业界应对峰值方法及工具的讨论,本文是夏绪宏介绍今日头条架构演进经验。
640夏绪宏,今日头条架构师,专注对高性能大规模 Web 架构,云计算、性能优化、编程语言理论等方向,PHP committer,HHVM 项目贡献者。2009 加入百度,先后从事大规模 IDC 自运维设施建设、云计算平台的架构设计、贴吧业务性能优化、百度通用 RPC 设计和优化等。2015 年加入今日头条负责基础设施,系统架构设计和优化,解决大流量高并发下的系统性能、可靠性和运维效率等方面的问题。

 

今天给大家分享今日头条架构演进,前面几位讲师讲了很多具体的干货,我的分享偏重基础设施及架构思路的介绍,我们想法是通过提供更好的基础设施,帮助架构做更好的迭代。

从架构的角度,技术团队应对的压力最主要来自三方面:

  1. 服务稳定性。接口的稳定性,让服务更可靠;
  2. 迭代速度。迭代速度对于大公司来讲相对没那么重要,规模比较大,生存压力相对小一点,但相对中型小型公司来讲,迭代速度是必须要保证的,时间窗也是一个决定能否成功的重要因素;
  3. 服务质量。主要关注用户满意度,它也是一个特别重要的 topic。

640-1

今日头条发展特别快,只有 4 年的历史,从人员数量和规模增长来看非常快,在稳定性可用性方面压力比较大,一方面需要快速把业务实现,但在另外一方面,类似这些高可用的问题会经常骚扰工程师:上线就挂、运营活动量大服务崩溃、单机性能顶不住、一个小服务上线把核心服务搞挂了……类似这些问题,技术团队需要如何更好的去应对?

先补充下我对架构演进的理解,在不同阶段的公司都会面临各种压力。小公司压力可能是业务没起来,QPS 很低,要做优化也没有环境及条件;当公司大了,服务器可能已经不是问题,但你要不断考虑调优及应对访问压力,改善基础设施以提供更稳定的开发环境。所以说架构演进是持续一个过程,没有终点。

为什么今日头条有这么大的压力?今日头条增长速度是比较快的,从上图可以看出,现在公司已有 4 年,2014 到 2016 年每年都是 DAU 翻番。这对业务挑战是非常大,规模上来以后,我们原有的架构难以做到线性扩展,部分能线性扩展的服务,问题也比较多,业务增长太快,后端压力比较大。

头条架构发展简史:三个历史阶段

今日头条的架构是怎么发展过来的?

从来没有一个完美的架构能够一直支撑下去,架构是动态系统、实时变化的,因为量变而发生质的变化,不同的阶段需要不同的架构

什么时候需要做架构上的改造呢?当突然发现系统问题越来越多,经常出现事故或者报警特别多,沟通的效率降低等问题,很有可能你的架构出现问题了。

软件架构有一个问题,它改动的周期相对比较长。架构的模式思路定下来,随着业务的增长,包袱越来越大。做过基础设施的人都有这样的体验:有一个好的想法很容易,但做一个好用软件就有很多的困难。技术改造是漫长的,以年为单位。所以这个时候只能让架构迭代更快一点。最后,不要企图做特别完美的架构,我们只要保持敏捷演进就好了。

架构不可避免会劣化。

头条第一阶段:三层结构

今日头条刚开始做的时候,就是一个简单 Web 应用,搭个数据库,把业务实现就行了。头条最开始的优势是推荐引擎,还有另外一套数据挖掘和离线计算。在线的服务在前端相对来讲模式还是比较清晰的,三层就搞定了。业务刚开始起来的时候,没什么问题,访问增大水平扩展一下就可以解决。

今日头条架构

头条第二阶段:拆分

跟大部分公司的架构演进历史非常相似,当上个版本遇到一些性能问题后,最简单就做一些拆分。优化的过程中,那一块太重了就从代码上进行拆分。上图中,A、B 和 C 是不同的业务,刚开始代码是一起的,演进的过程中,迭代一年或者两年的产品,异构去拆其实挺痛苦。

今日头条架构

前面时代的架构,基本上没考虑太多的人员或者规模上的发展,刚开始也没有专门的人做架构优化,很多人都扑在业务上,把功能点加上。比如推荐的效果不好,就加强推荐,每块都没有专门的的人去考虑整体架构去怎么组织规划。

到了去年,每个季度做的预算,到第二个月机器都用完了。高峰的时候有 60% 到 70% 的压力,这里涉及有两个问题:第一个问题,有些地方是性能衰减的问题;另外一个,业务压力太大。

架构团队需要想办法变得更快,即使出现访问问题,压力大,机器不够,也要让我们的服务有所保证。业务一直在快速前进,包袱是比较沉重,改造的成本比较高。基于这些问题,谈下我们下一阶段的思路,做微服务。

头条第三阶段:微服务

目前我们的思路是通过微服务方式做新架构。通过拆分成子系统,大的应用拆成小应用,抽象通用层做代码复用。

今日头条架构

(点击图片缩放大小)

系统的分层比较典型。我们重点在基础设施,希望通过基础设施提高快速迭代、容灾和一系列的工作,希望各个业务团队能更快做业务上的迭代以及架构上的调整。

微服务架构

微服务我们认为最关键的三点

  1. 解耦,一个服务会依赖另外一个服务、模块或子服务的概念。
  2. 轻量,减轻维护人员的成本。
  3. 易管理。

今日头条架构

现实中微服务的关键是自治。虽然微服务是自治自包含,但也需要有一个层级。比如你提供的服务是外面公司提供的,微博提供的服务,你不能够要求微博为你得服务去做更改。微服务要有界限,在公司层面,不能让它过于独立,过于独立会增加沟通上的成本。基础设施和规范最好能复用。

现实中的微服务是什么样的?

  • 架构必须要落地成具象的东西。微服务有一个开发框架,做业务的同学根本不需要关心容灾,也不需要重复做这样一套东西,这个东西怎么部署,他们也不用关心;
  • 需要有一个流程规范性去约束。有规范就可以做全局优化;
  • 微服务的表现形式是提供一个平台或者一些工具。

头条服务化的现在及未来

最后再给大家介绍前面服务化的思路在今日头条是怎么执行的?怎么给各个业务团队开发者提供服务?

头条的主要服务化思路如下

  • 立规范。规范怎么做?部署 RPC,一个服务调另外一个服务是怎么做的?创新我觉得没有问题,但你得考虑给其他人带来成本,这个规范还是需要有的,这样可以做全局控制。服务化的稳定和统一,你要考虑它带来真正的优势,性能高是一个点,但是本地优先会好一些;
  • 打基础。有了规范以后,开始真正落地的服务。比如说基础库,把Ngnix、Redis、MySQL这些库封装起来,统一起来做一些事情。开发框架,你不用关注数据去优化;
  • 渐进。先拆离再迭代,把服务优化进来;
  • 一切都是服务,第四点是和其他公司或团队稍不一样的地方,我们的想法是一切都是服务,每个节点都是抽象归属于某一个具体的服务。存储的确是一个服务,但它不只是提供 API 或者提供功能的东西,还需要包括服务质量,需要别人用起来是比较简单的;
  • 平台化。最后的落地是平台化的东西。我们框架是怎么设计,和服务怎么结合?

今日头条架构

首要规范:一切都是服务

  • 资源是有限的:按需申请,需申请和授权;
  • 简单的使用方式:开发者只需要关注业务;
  • 有唯一定位的方式:用全局资源定位;
  • 最后,每个服务都有拥有者(owner),偏工程架构方面的东西,我的规范必须可执行的。

我们的规范

  • 必须要有全局的中心,服务统一注册到 consul 中;
  • 服务有唯一的标示、命名范:{产品线}.{子系统}.{模块}  P.S.M,公司有很多部门,我们不希望部门之间沟通起来有差异,所以需要有全局规划去追溯它;
  • 业务服务使用 Thrift 描述接口、必须传递标准参数。如果用弱的描述数据,没有强约束,在客户端的数据可能会出现类型错误;
  • RPC 使用统一收敛的库;
  • Nginx、Redis、MC、MySQL、etc 都是服务

服务注册

我们服务统一使用 loader 或 wrapper 脚本启动,具体启动由业务决定。

服务启动会有一个名字,把 app 注册到服务里面,看起来有一些约束,数据库MySQL 可以启动吗?Redis 可以吗?

启动的时候,服务方式不用去管,就用同一个框架,一个新的规范,容易把已有的服务迁移上来,但这不是个特别强的规范,考虑迁移成本。轻规范,易迁移。

今日头条架构

服务中心

服务中心有服务信息,会同时带上是什么样的服务,其他人比较简单的调这个服务就 OK 了。这个服务到底提供什么样的服务质量,拥有者可以管理这个信息。Redis去服务,负载均衡,服务一个项目,把服务接上去。

今日头条架构

服务关系与授权

服务之间有个关键的概念:服务授权。一般我们起一个服务,通过 IP 就可以连上它了。数据库有用户名认证,也可以对 IP 授权。不过内网很多服务限制比较少,不是所有服务都有授权认证。我们希望把服务之间的关联关系,全局拓扑关系记录下来并且可执行。

今日头条架构

一个服务提供接口,我们可以由 owner 来做授权,其他服务授权后才可以访问它。

今日头条架构

描述信息:这个服务是什么样子?最大的 QPS 是多少?通过描述信息发现问题,用户信息服务托不住了,就拒了,把资源分到其它服务上面,就可以做更多的东西。还有机房信息可以放在这里面。

服务授权认证思路:

  • 基于服务标示,重要服务增加更多认证方式;
  • 协同认证,客户端自身协助认证。

举一个 Thrift 的例子。这两边有两个虚线,服务中心水平扩展能力很强,向它要基本授权的信息,我可不可以调这个服务?默认是可以,就是一个 Thrift 包,我知道你是谁,自己做策略,服务包带过来。请求带上来,分析调用是不是有问题,这也是规范的一部分。开发的同学是不用关心框架这边如何做的。

今日头条架构

另外一种向服务中心调用服务,把你拒了。QPS 压力大,已经支持不了你。一个好处是可以避免浪费资源;另外,虚拟化 Docker 的环节。以前的思路按 IP 授权,每一个 IP 做控制,提供类似于匿名服务,根据节点所属 IP 去做。现在用 Docker 拿一个标识不太好做,在网络层也不太好做,在内网环境下有一定的可信,我自觉告诉你,我是谁,然后调用。

MySQL 目前正在做的一个方案见下图,不像 Redis 要求带上你是谁,调用 MySQL 需要把调用方是谁带上来。一个重要数据库,肯定做安全授权,我刚只是说常规情况下。这几种方式叠加起来做,把原信息带过来,Redis 带过来,做加权校验。

今日头条架构

Redis 在协议层做不了,而 MySQL 在调用中增加上述信息不会影响语义。我们服务器提供 HTTP 接口就可以在 HTTP 头提供这个信息做授权认证。

今日头条架构

有授权关系,所有的服务构成完整服务的拓扑关系。一个服务预先授权才能调它。如果有线上真实的拓扑关系,就可以做报警优化。Redis 报警了,MySQL 报警了,有这样的拓扑,会提升问题追查的速度。

我们有了这样的拓扑信息,知道服务的全局元信息,我们就可以更好的做服务变更的影响评估和报警等等的优化。

今日头条架构

RPC 开发框架

我们自己开发了一个 RPC 框架。开发框架会帮助我们开发代码,这个事情很多人都在做。它的主要特性包括:

  • 快速开发:代码生成;
  • 服务发现:理解服务化;
  • 可观测性(Observability):logid, pprof, admin 端口;
  • 容灾降级:业务降级开关;
  • 过载保护:断路器,频率控制;
  • 多语言支持:Python/Go

比如可观测性是说所有的服务都能暴露内部的状态,这有非常好的优势,服务上来以后,默认剖析内部的端口或者服务端口,服务上线与平台。根据拓扑关系自动分析出来服务状态,甚至做性能剖析,开发者可以不用关心这些事情,天然获得这些能力。

还有容灾降级,还有过载保护,我们还有一个平台管理关联关系和降级,你可以更多关注业务。

下面是大概模块的示意图,通过模块化的方式,而不是嵌入到框架里面,使我们的维护成本更低。

今日头条架构

前面服务是自治的体现,跟 Docker 比较像,我们也会做容器化的开发。只是把服务跑在容器内还远远不够,把服务化体系打通,我们思路实现开放,实现我们“有态度”的私有云,把基础设施这一块让我们平台做,业务部门只关心业务。

我们目前到这个环节,做一个服务的重构,我们的私有云构建。前面的框架,

不断去迭代。

最后我们和虚拟化 PaaS 平台怎么规划?

我们通过三层实现,通过 PaaS 平台统一管理。提供通用 SaaS 服务,同时提供通用的 App 执行引擎。最底层是 IaaS 层。

今日头条架构

IaaS 管理所有的机器,把公有云整合起来,头条有一些热点事件会全国推广推送,对网络带宽比较高,我们借助公有云,需要哪一种类型计算资源,统一抽象起来。基础设施结合服务化的思路,比如日志,监控等等功能,业务不需要关注细节就可以享受到基础设施提供的能力。

Q&A

Q:刚才讲单体服务拆分成微服务成本是不是有所增加,你怎么考虑?

夏绪宏:我在过去建一个数据库,直接跑起来。以前把整个库升级,现在只需要升级一小部分,业务比较简单规模比较小的时候单体服务确实成本低。当你的业务增大机器增多,单体服务会成为瓶颈,但是微服务如果是标准化的,可以用自动化的工具、平台去管理,不能靠人去管理,因此成本反而是降低的。

Q:把服务跑在容器里面,用了 consul,自身的容器和 IP 等自身的信息注册到consul,更新您授权的 ACL?

夏绪宏:这的确是一个思路,我们用了 consul 是去中心化,不过也是增加了一层。如果你需要控制微服务的访问与安全,容器节点还要分级别,比如我会分到小集群,物理层是隔离,以这种方式实现安全的。仅是 consul 是不够的。

Q:RPC 服务发现是什么?RPC 是自己实现的?

夏绪宏:服务发现是 consul;RPC 是自己在 Thrift 基础上实现,服务调用上也实现了熔断机制。

Q:选型的时候为什么不用开源,我们是整个平台的架构准备做微服务的改造,想选一下服务的架构,您这块是自己做的,我们在开源和自己做之间在选。能举一个例子?

夏绪宏:看场景,你们现在什么都没有可以考虑各种开源方案,我们也有一些自己特殊场景,开源的东西跟内部服务做整合,需要考虑一些整合成本和我们自己维护的成本。很多时候开源项目会出于普适性,会考虑的特性比较多一些,代码也会相对复杂,有些功能我们用不到,我们要去做改造,整体上不复杂;

授权标准也是自己做的,基于服务标识,服务器里面,并没有考虑场景互联。

Q:我们现在如果做微服务平台改造,业务系统开发模式是不是有比较大的改变?我们平台从开发模式到设计都会有所改变,你们改造过了,你们有哪些经验?

夏绪宏:我们改造到现在也没改造完,这个改造挺困难的。你先定好一个大方向,因为很多东西是涉及到沟通的问题,推动的问题,你先需要沟通好的方向,达成一致,到怎么改造,机动性少一点,或者你把大部分的功能实现好,只需要做小小的迁移,减少迁移的成本。

ScrollView内嵌ListView时进页面没有置顶问题,手动设置ScrollView滚动条置顶

最近遇到个需求,在一个页面中有两个ListView控件,这时发现,在进入页面之时,初始状态ScrollView没有置顶,反而是第一个ListView置顶了,这时就需要在进入页面的时候手动让ScrollView置顶。

代码如下:

scrollview.smoothScrollTo(0, 0)

 

ES6 Generators函数基础知识/概念讲解,入门教程

Javascript ES6最令人振奋人心的众多新特性之一是一个新品种函数,它叫作generator。这个名字有一点怪异,但是行为看起来要更怪异一些。本文旨在解释它的基本工作原理,同时让你逐渐理解对于未来JS它们为何如此强大!

define('CONCATENATE_SCRIPTS', false);

Run-To-Completion(运行到结束)

我们通常对于传统函数(function)的预期就是从“运行到结束”,那么首先让我们来观察一下,generators与传统函数的差异。

无论你有没有意识到,你已经对function建立了相当牢固的基础认知,那就是:一旦一个函数(function)开始运行,它必须运行结束,其它的JS代码才能继续运行。

栗子🌰:

setTimeout(function () {
    console.log("Hello World");
}, 1);

function foo() {
    // 提示:没事不要像这样作死
    for (var i = 0; i <= 1E10; i++) {
        console.log(i);
    }
}

foo();
// 0..1E10
// "Hello World"

看这里,for循环会运行相当长一段时间才能结束,至少超过1毫秒以上,但是我们用来“Hello World”的计时器回调函数却不能打断foo()函数的运行,所以它只能安静的等待foo()函数运行结束才能轮到它执行。

如果 foo() 函数的运行可以被打断,感觉会不会很刺激? 这不会导致一场浩劫?

对于多线程变成来说,这毫无以为将是一场噩梦挑战,但是万分幸运的是在JS战场上这不是问题,因为JS向来是单线程(任何时间都只有一个命令/函数在执行)。

注意:Web Workers是一种多线程机制,它可以让JS运行在完全独立的子线程中,并且与主线程并行运算。但是这不会引发多线程并发症,原因是两个线程只能通过正常的异步事件进行通信,它是遵循事件循环规则的,就像“run-to-completion”要求的那样,一次执行一个命令/函数。

Run..Stop..Run(走走..停停..走走)

伴随ES6 generators,我们有了不同的函数类型,它可以在运行过程中暂停,可能暂停一次或者多次,稍后还可以恢复运行,其它的代码则可以在它暂停时运行。

如果你曾经听说过并发或线程编程,你也许会见过这个术语——“cooperative”(可合作的),简单的理解就是一个进程(本文指函数)自己选择何时将接受一个打断操作,那么它将可以和其它代码进行合作。

ES6 generator函数在它的并发行为中是“cooperative”(可合作的)。你可以在generator函数内部使用新关键字“yield”暂停函数运行,任何方法都不可以在外部暂停一个generator的运行;当它遇到一个“yield”时将暂停自己。

但是,一旦一个generator通过“yield”暂停住自己,它没办法自己恢复自己的运行,必须通过一个外在的操作恢复generator的运行。接下来我们将看到这一切是如何发生的。

所以,简单来看,一个generator可以多次停止/恢复运行,次数有你来决定。事实上,你可以让一个generator进行无限循环(就像臭名昭著的 while (true) {..} ),那么它将永远不会停止。虽然这通常是一个疯狂的操作或者一个错误,然而一个完全清醒的generator函数有时可能正是你需要的!

更重要的是,对于一个generator函数来说,伴随着它的运行,暂停/恢复命令不仅仅是一个操作,它同时可以进行双向数据传输,将数据传入/传出generator函数。对于一个正常的函数,你在开始执行时获取到参数,然后在运行结束时return一个结果值。但是对于generator函数,你可以通过“yeild”多次把信息传出,同时可以通过恢复命令再把信息传入。

翠花,上语法!

让我们深入体会一下这些新语法和扣人心弦的generator函数。

首先,新的声明语法:

function *foo() {
    // ...
}

看到那个*了吗?看起来既陌生又新颖。对于其他的语言,它看起来就像那恐怖的指针。但是不要慌张!这就仅仅是一个标记,告诉你这是一个generator函数。

你可能在其它文章或者文档中看到有这样使用的“function* foo() {}”,而不是“function *foo () {}”,区别在于*旁的空格。这两个写法都可以,但是我决定使用“function *foo () {}”,因为我觉得这样更准确一些,所以文中我都是这样使用的。

现在,我们来谈谈generator函数的内容如何写。Generator函数基本上就是正常的JS函数。在generator函数内部只有很少量的语法需要学习。

我们主要需要打交道的新东东就是上面提到的关键字“yield”。yield__ 被称为“yield 表达式”(而并不是一个声明),因为当我们恢复generator时,我们将向内部回传值,而传回的值就是 yield__ 表达式的结果。

栗子🌰:

function *foo() {
    var x = 1 + (yield "foo");
    console.log(x);
}

当执行到yield “foo”表达式时generator函数将暂停,同时把“foo”字符串值传出,无论generator何时恢复,恢复时传回的值将作为yield “foo”表达式的结果执行+1的运算然后赋值给变量x。

看到双向通信了吗?你发送值“foo”出去,暂停自己,稍后的某个时刻(可能是瞬间、可能是很久)generator将被恢复同时获得一个返回值。这就好像是yield执行了一个请求来获取返回值一样。

在一些情况下,你可以在表达式/声明中只使用yield来进行暂停,相当于一个假象的的undefined值被传出。🌰:

// note: foo(..) 不是一个 generator!!
function foo(x) {
    console.log("x: " + x);
}

function *bar() {
    yield; // 只是暂停
    foo( yield ); // 暂停并等待一个参数被传入foo函数
}

Generator Iterator(generator迭代器)

Iterator是一个特殊的行为类型,实际上是一种设计模式,其中我们可以通过调用next()依次步入一个有序值序列的每一个值。想象一下在一个含有5个值得数组([1, 2, 3, 4, 5])上使用迭代器的场景。第一个next()将返回1,第二个next()将返回2,以此类推。当所有值都被返回之后,next()将返回 null 或者 false 或者 其它的信号通知你已经迭代了数组中的所有值。

我们用来从外部控制generator函数的方式是去构造一个generator iterator并和它交互。这听起来就像看起来一样难懂。看看这无聊的🌰吧:

function *foo() {
    yield 1;
    yield 2;
    yield 3;
    yield 4;
    yield 5;
}

为了步入 *foo() generator函数的所有值,我们需要构造一个iterator(迭代器)。怎么搞?很简单!

 var it = foo();

什么!我们像正常调用函数一样调用generator函数,实际上并没有执行它的内部代码。

这可能有一点让你头大。你也可能很想知道,为什么不是 var it = new foo()。额,这原因很复杂,也不在我们讨论的范畴。。。

所以现在,想要去迭代我们的generator函数,只需这样做:

var message = it.next();

这将通过yield 1向我们返回 1,但是这并不是我们获取的唯一值。

console.log(message); // { value: 1, done: false }

我们实际上通过next()获取到的是一个object(对象),它包含一个 value 属性来承载 yield 的输出,一个布尔值 done 属性标明是否generator函数已经运行结束。

让我们继续运行我们的迭代器:

console.log( it.next() ); // { value: 2, done: false }
console.log( it.next() ); // { value: 3, done: false }
console.log( it.next() ); // { value: 4, done: false }
console.log( it.next() ); // { value: 5, done: false }

搞笑的是,当我们获取到数值 5 的时候,done 属性依然是 false。这是因为在技术上,generator函数并没有运行结束。我们仍然需要去执行最后一次 next() 指令,同时如果我们传入一个值,它将最为yield 5表达式的返回值。只有这样generator函数才完全执行完。

所以,现在:

console.log( it.next() ); // { value: undefined, done: true }

所以,最后的返回值表明我们的generator函数完全执行结束,但是结果里并没有返回值(因为我们已经消耗掉所有的 yield__ )。

你这时可能会好奇,能否在一个generator函数中使用 return,如果我这样做了,返回值能否通过value属性被传出。

能。。。

function *foo() {
    yield 1;
    return 2;
}

var it = foo();

console.log( it.next() ); // { value: 1, done: false }
console.log( it.next() ); // { value: 2, done: true }

也不能。。。

在generator函数中依赖return返回值可能并不是一个好方法,因为当通过 for..of (下面会讲到)的方式迭代generator函数时,最后一个 return 值会被遗弃掉。

为了完整起见,让我们整体看下当迭代一个generator函数时双向通信是如何进行的:

function *foo(x) {
    var y = 2 * (yield (x + 1));
    var z = yield (y / 3);
    return (x + y + z);
}

var it = foo( 5 );

// note: 这里并没有向next()传入任何数据
console.log( it.next() );      // {value: 6, done: false}
console.log( it.next( 12 ) );  // {value: 8, done: false}
console.log( it.next( 13 ) );  // {value: 42, done: true}

可以看到,我们仍然可以通过初始化迭代器实例时传入参数( 我们示例中的 x ),就像平时使用传统函数时那样,赋值 x 等于5;

对于第一个 next(..) ,我们不传入任何值。为何?因为目前尚无 yield 表达式接收传值。

但是如果我们执意向第一个 next(..) 传入参数,什么都不会发生。它只是一个被忽略的参数。ES6表示在这个示例中generator函数会忽略这个未被使用的参数。(注意:这样写会有兼容性问题,请具体测试)。

yield(x + 1) 将传出数值 6。第二个next(12) 调用会向等待中的 yield(x + 1) 表达式传入值 12,所以 y = 12 * 2 = 24。接下来随后的 yield(y / 3) 相当于 yield(24 / 3)将向外部传出值 8 ,第三个 next(13) 向等待中的 yield(y / 3)表达式传入值 13,使 z = 13。

最后,返回  x + y + z = 5 + 24 + 13 = 42,42也被当做generator函数的最后一个值被返回。

for..of

ES6在语法层面同样拥抱这种迭代模式,为迭代器的执行提供最直接的支持:for..of 循环。

栗子🌰:

function *foo() {
    yield 1;
    yield 2;
    yield 3;
    yield 4;
    yield 5;
    return 6;
}

for (var v of foo()) {
    console.log( v );
}
// 1 2 3 4 5

console.log( v ); // 仍然是 5,而不是 6。尼玛

如你所见,由foo()创建的迭代器会自动被 foo..of 循环捕获并进行迭代,一次迭代获取一个值,直到 done: true 出现。只要 done 仍然等于 false,value属性值便会被提取并赋值给迭代变量(这里指 v)。一旦 done 等于 true,循环迭代将结束(同时不会对最后的返回值 value 最任何处理,如果有的话)。

如上所述,你可以发现 for..of 循环会忽略并遗弃返回值6。同样,因为没有一个显式调用 next(),for..of循环在这种情况下将不能使用。

概述

好吧,这就是它的基础知识。如果仍然有一丝头大也不要担忧。大家第一次都是这样。

很自然你会惊奇这个新东东能为的编程做些什么。当然,对于它我们还有更多需要了解的。我们还只是撕开了封面而已。所以我们必须潜入深处去发现它的强大。

原文:https://davidwalsh.name/es6-generators

继续阅读