Open Tech Pub

那些年关注的技术

Weex语法

  • 数据绑定
  • 样式
  • 事件处理
  • 展示逻辑控制
  • 渲染逻辑控制
  • 组件封装
  • 找节点
  • 组件通信
  • 页面配置和页面数据

首先运行Weex代码有两种方式:

  1. 安装本地环境
  2. 使用dotwe.org

语法介绍

1
2
3
4
5
6
7
8
9
<template>
  /* (required) the structure of page */
</template>
<style>
  /* (optional) stylesheet */
</style>
<script>
  /* (optional) the definition of data, methods and life-circle */
</script>

Weex代码由<template><style><script>三个部分构成。 可以对应理解成HTML、CSS、JavaScript。

数据绑定

Weex在<template>使用mustache语法{{…}}绑定<script>中的数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<template>
  <div>
    <text style="font-size: { {size} }">{ {title} }</text>
    <text>{ {fullName} }</text>
    <text onclick="toggle">事件绑定: { {result} }</text>
  </div>
</template>

<script>
  module.exports = {
    data: {
      size: 50,
      title: 'www.openwudi.com',
      firstName:'Wu',
      lastName:'Di',
      result:true
    },
    computed:{
      fullName: function(){
        return this.firstName + ' ' + this.lastName
      }
    },
    methods:{
      toggle: function () {
        this.result = !this.result
      }
    }
  }
</script>

根据上面的例子,可以看到,data中声明了一些变量,这些变量直接对应<template>中的数据,进行绑定。通过computed属性生命计算相关的数据操作。<template>中以on...开头的就是用于事件绑定操作,对应methods中声明的方法。

样式

Weex支持CSS基础语法,由一系列的键值对进行声明,每对键值由;分隔。
width: 400; height: 50; ...

Weex声明样式有两种方式:

  • <template>中的style属性
  • <style>样式表

<template>中声明style

1
2
3
4
<template>
  <div style="width: 400; height: 50;">
  </div>
</template>

<style>中声明

1
2
3
4
5
<style>
  .wrapper { width: 600; }
  .title { width: 400; height: 50; }
  .highlight { color: #ff0000; }
</style>

class选择器

1
2
3
4
5
6
7
8
9
10
11
<template>
  <div class="wrapper">
    <text class="title">...</text>
    <text class="title highlight">...</text>
  </div>
</template>
<style>
  .wrapper { width: 600; }
  .title { width: 400; height: 50; }
  .highlight { color: #ff0000; }
</style>

注意事项

  • 为了简化页面设计和实现,屏幕的宽度统一为750像素,不同设备屏幕都会按照比例转化为这一尺寸进行长度计算。
  • 标准 CSS 支持很多样式选择器,但 Weex 目前只支持单个class name的选择器。
  • 标准 CSS 支持很多的长度单位,但 Weex 目前只支持像素,并且px单位可以忽略不写,直接使用对应的数值。更多详情请查看通用样式。
  • 子元素的样式不会继承自父元素,这一点与标准 CSS 不同,比如 color 和 font-size 等样式作用在 上层的
    上是无效的。
  • 标准 CSS 包含了非常多的样式属性,但 Weex 只支持了其中的一部分,比如盒模型、flexbox、position 等布局属性,以及 font-size、color 等其它样式。

事件处理

使用属性名为on...前缀为<template>增加事件绑定。

1
2
3
4
5
6
7
8
9
10
11
12
<template>
  <image onclick="handler" ...></image>
</template>
<script>
  module.exports = {
    methods: {
      handler: function (e) {
        // TODO
      }
    }
  }
</script>

添加参数,获取事件对象

1
2
3
4
5
6
7
8
9
10
11
12
<template>
  <image onclick="handler('arg1', $event)" ...></image>
</template>
<script>
  module.exports = {
    methods: {
      handler: function (arg1, e) {
        // TODO
      }
    }
  }
</script>

当一个事件函数被调用,它会收到的第一个参数就是事件对象。每个事件对象包含一下属性。

  • type: 事件名称, 如: click
  • target: 目标元素
  • timestamp: 事件触发的时间戳

展示逻辑控制

Weex前端语义支持通过两种特殊属性(if和repeat)的设置来确定组件的显示状态,这会使得整个页面布局显得更加灵活。

if

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<template>
<container>
    <text onclick="toggle">Toggle</text>
    <image src="..." if=""></image>
</container>
</template>
<script>
    module.exports = {
    data: {
        shown: true
    },
    methods: {
    toggle: function () {
        this.shown = !this.shown
    }
    }
}
</script>

repeat

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
  <template>
    <container>
        <container repeat="" class="">
            <image src=""></image>
            <text></text>
        </container>
    </container>
    </template>
    <style>
    .male {...}
    .female {...}
    </style>
    <script>
    module.exports = {
        data: {
            list: [
            {gender: 'male', nickname: 'Li Lei', avatar: '...'},
            {gender: 'female', nickname: 'Han Meimei', avatar: '...'},
         ...
        ]
     }
    }
</script>

渲染逻辑控制

append属性没有做数据绑定的工作。它不会改变最终的渲染效果。但是此属性确定是以一整棵树或子节点的方式添加。

append有两个关键属性,treenode,使用方法如下:

1
2
3
4
5
6
7
8
9
10
<template>
  <container>
    <container id="world" append="tree">
      <text>Hello World!</text>
    </container>
    <container id="weex" append="node">
      <text>Hello Weex!</text>
    </container>
  </container>
</template>

组件封装

经常我们会发现很多可复用的weex文件,这时候可以封装成weex组件。我们可以直接创建一个名为foo.we的文件,<foo>就是组件名。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!-- foo.we -->
<template>
  <container style="flex-direction: row;">
    <image src=""></image>
    <text></text>
  </container>
</template>
<script>
  module.exports = {
    data: {
      title: null,
      image: null
    }
  }
</script> 

foo.we的也包含<template><style><script>,定义好了后,直接用<foo>标签即可, 注意这里bar.wefoo.we是在同目录下哦,如下:

1
2
3
4
<!-- bar.we -->
<template>
  <foo title="..." image="..."></foo>
</template> 

找节点

weex中,可以通过在特定的节点上设置 id 属性,以此来唯一标识该节点。然后可以用this.$el(id)来找到该节点,以scrollToElement()为例,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<template>
  <container>
    <text id="top">Top</text>
    <container style="height: 10000; background-color: #999999;">
    </container>
    <text onclick="back2Top">Back to Top</text>
  </container>
</template>
<script>
  var dom = require('@weex-module/dom');
  module.exports = {
    methods: {
      back2Top: function () {
        var top = this.$el('top')
        dom.scrollToElement(top, { offset: 10 })
      }
    }
  }
</script> 

组件通信

子组件可以使用this.$dispatch([String type], [Object detail])方法传递消息给父组件。
第一个参数定义消息类型,第二个参数为消息对象。如果父组件中的任何子组件使用$on([String type], [Function callback])注册监听事件,则回调执行第一个参数,参数中的detail属性是消息数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<we-element name="foo">
  <template>
    <div>
      <image src="" onclick="test"></image>
      <text></text>
    </div>
  </template>
  <script>
    module.exports = {
      data: {
        title: '',
        imageUrl: ''
      },
      methods: {
        test: function () {
          this.$dispatch('notify', {a: 1})
        }
      }
    }
  </script>
</we-element>
<template>
  <foo title="..." image-url="..."></foo>
</template>
<script>
  module.exports = {
    created: function () {
      this.$on('notify', function(e) {
        //  when <foo> image tag  be clicked ,the function will be executing.
        // e.detail is  `{a: 1}`
      })
    }
  }
</script>

页面配置和页面数据

你可以在另外的<script>中写一些 配置和数据的实例,添加到 top-level weex 组件中。
* 配置实例中可以申明一些meta信息 比如SDK/Client版本。支持降级到H5 渲染方式。未来还将支持更多的扩展。 * 数据实例中可以设置外部数据替换掉默认top-level组件数据。 这些都使weex文件更具扩展和可配置,让其更容易的在其他工具和服务中工作,比如CMS系统。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<!-- definition of sub components -->
<element name="sub-component-a">...</element>
<element name="sub-component-b">...</element>
<element name="sub-component-c">...</element>
<!-- definition of top-level component -->
<template>...</template>
<style>...</style>
<script>
  module.exports = {
    data: function () {return {x: 1, y: 2}}
  }
</script>
<!-- instance config and data -->
<script type="config">
  downgrade: {
    ios: {
      os: '9', // all of 9.x.x
      app: '~5.3.2',
      framework: '^1.3', // all of 1.3.x
      deviceModel: ['AAAA', 'BBBB']
    },
    android: {
      os: '*', // all of version
      app: '^5',
      framework: '',
      deviceModel: ''
    }
  }
</script>
<script type="data">
  {y: 200}
</script>

Weex探索计划

为什么是Weex

一下子就2017年了,去年的计划还算圆满,但是一直没有更新技术博客,所以为了一个好的开头,计划探索一下Weex,作为去年最火的移动开发技术之一,背后的体系架构很有学习价值。并且,我们的团队也在一直追寻优秀的技术方案,优化现有业务的体验和性能。

接下来的计划

第一步,按照Weex官方教材快速学习,用于记录主要内容。

第二步,使用已有的组件创建静态页,完成一个小Demo。

第三步,分析Weex-Android客户端源码实现,扩展自定义组件。

Weex语法

  • 数据绑定
  • 样式
  • 事件处理
  • 展示逻辑控制
  • 渲染逻辑控制
  • 组件封装
  • 找节点
  • 组件通信
  • 页面配置和页面数据

聊聊我的2016年

我们的新团队已经组建2个多月的时间了,是时候说些什么了。

为什么离开新浪?

离开的时候正是新浪将要搬家到新大楼之前,更好的工作环境,离家也近一些,待遇不算差。其实对这些我并没有更多留恋的,留恋的只是原来身边的那些小伙伴。原来对自己的要求还是很高的,2年前回国只寻找创业团队,领导和同事给了足够的学习和锻炼的机会,也让自己提升的很快,那时候每天满脑子都是工作,周末也会自己去公司加班。后来,加入微博客户端的研发,一年的时间,业务能力打磨的更加熟练,可是总感觉和原来的状态差很多。直到一天,HY找到我,一起去做一个新的产品,一周之内就提了离职申请。DO WHAT YOU THINK IS RIGHT.

新浪小伙伴们

我们在做什么?

我们在做一款消费金融类产品,主要面向人群为年轻人。预计月底上线,敬请期待。

小伙伴们

测试机到了

我的目标

2016年已经过半了,现在说目标总感觉在时间点上比较尴尬,但是我上半年做了什么?完成了一件人生中最重要的事情,我结婚了。结束了我们10年的爱情长跑,从象牙塔到工作,她都默默的支持我,我们也一步一步的成长,走到今天。第一个目标就是,赶紧装修完新房,住到新家。第二个目标,带爸妈一起去旅行一次。第三个目标,尽快完成新产品功能,成功上线!

迁移Octopress

由于GitCafe与Coding合并,并且需要将GitCafe的代码迁移出来. 博客使用Octopress,通过以下几步即可完成迁移.

修改部署的Git库地址

rake setup_github_pages

rake会询问新的Git库地址,例如:https://git.coding.net/xxx/xxx.git. 这样再使用rake deploy后就会自动部署到Coding上.

修改source分支Git库地址

git remote set-url origin xxx.git

Activity的工作过程

从这篇文章开始将分析Android四大组件的工作过程,Activity作为最常用的组件,我们最先来分析.(源码使用API_LEVEL_15)

通常我们编写以下代码来创建一个Activity:

1
2
Intent i = new Intent(this, TestActivity.class);
startActivity(i);

1.Activity

然后Activity通过一系列的生命周期展示在我们眼前,就以Activity的startActivity这个方法为入口,发现实际上都调用了startActivityForResult方法.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public void startActivityForResult(Intent intent, int requestCode) {
    if (mParent == null) {
        Instrumentation.ActivityResult ar =
            mInstrumentation.execStartActivity(
                this, mMainThread.getApplicationThread(), mToken, this,
                intent, requestCode);
        if (ar != null) {
            mMainThread.sendActivityResult(
                mToken, mEmbeddedID, requestCode, ar.getResultCode(),
                ar.getResultData());
        }
        if (requestCode >= 0) {
            // If this start is requesting a result, we can avoid making
            // the activity visible until the result is received.  Setting
            // this code during onCreate(Bundle savedInstanceState) or onResume() will keep the
            // activity hidden during this time, to avoid flickering.
            // This can only be done when a result is requested because
            // that guarantees we will get information back when the
            // activity is finished, no matter what happens to it.
            mStartedActivity = true;
        }
    } else {
        mParent.startActivityFromChild(this, intent, requestCode);
    }
}

这里的mParent代表一个ActivityGroup,已经被弃用了,所以我们只需要关系 mParent == null 这段逻辑。Instrumentation没有图形界面,拥有启动能力并可以监控其他类的工具类,通过execStartActivity方法启动一个Activity。mMainThread.getApplicationThread()可以获得一个ApplicationThread对象,它是ActivityThread的内部类,这两个类对Activity的启动非常重要,后面会继续分析。

2.Instrumentation

现在分析一下execStartActivity方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public ActivityResult execStartActivity(
        Context who, IBinder contextThread, IBinder token, Activity target,
        Intent intent, int requestCode) {
    IApplicationThread whoThread = (IApplicationThread) contextThread;
    if (mActivityMonitors != null) {
        synchronized (mSync) {
            final int N = mActivityMonitors.size();
            for (int i=0; i<N; i++) {
                final ActivityMonitor am = mActivityMonitors.get(i);
                if (am.match(who, null, intent)) {
                    am.mHits++;
                    if (am.isBlocking()) {
                        return requestCode >= 0 ? am.getResult() : null;
                    }
                    break;
                }
            }
        }
    }
    try {
        intent.setAllowFds(false);
        int result = ActivityManagerNative.getDefault()
            .startActivity(whoThread, intent,
                    intent.resolveTypeIfNeeded(who.getContentResolver()),
                    null, 0, token, target != null ? target.mEmbeddedID : null,
                    requestCode, false, false, null, null, false);
        checkStartActivityResult(result, intent);
    } catch (RemoteException e) {
    }
    return null;
}

这里我们只需要关心两个地方,一个是ActivityManagerNative.getDefault()的startActivity方法,另一个是checkStartActivityResult(result, intent)方法。

从方法名我们就可以看出startActivity是真实的实现,但是ActivityManagerNative是一个抽象类,ActivityManagerNative.getDefault()只返回一个继承自IInterface的IActivityManager接口。

1
2
3
static public IActivityManager getDefault() {
    return gDefault.get();
}

checkStartActivityResult用于检查Activity的合法性等操作。

3.ActivityManagerService

ActivityManagerNative实现了IActivityManager接口,继承Binder,很明显这是一个Binder结构,gDefault是一个单例对象,在create初始化方法中调用asInterface进行绑定。ActivityManagerService则继承自ActivityManagerNative,拥有具体的实现。我们继续分析ActivityManagerService中的startActivity方法。

1
2
3
4
5
6
7
8
9
10
public final int startActivity(IApplicationThread caller,
        Intent intent, String resolvedType, Uri[] grantedUriPermissions,
        int grantedMode, IBinder resultTo,
        String resultWho, int requestCode, boolean onlyIfNeeded, boolean debug,
        String profileFile, ParcelFileDescriptor profileFd, boolean autoStopProfiler) {
    return mMainStack.startActivityMayWait(caller, -1, intent, resolvedType,
            grantedUriPermissions, grantedMode, resultTo, resultWho,
            requestCode, onlyIfNeeded, debug, profileFile, profileFd, autoStopProfiler,
            null, null);
}

mMainStack是一个ActivityStack对象。

4.ActivityStack

在startActivityMayWait方法中的调用顺序是:

  • startActivityLocked
  • startActivityUncheckedLocked
  • resumeTopActivityLocked
  • startSpecificActivityLocked
  • realStartActivityLocked

在realStartActivityLocked方法中有一句

1
2
3
4
5
6
app.thread.scheduleLaunchActivity(new Intent(r.intent), r.appToken,
                    System.identityHashCode(r), r.info,
                    new Configuration(mService.mConfiguration),
                    r.compat, r.icicle, results, newIntents, !andResume,
                    mService.isNextTransitionForward(), profileFile, profileFd,
                    profileAutoStop);

其中app.thread为IApplicationThread类型,这个接口的实现类为ActivityThread的内部类ApplicationThread。

5.ApplicationThread

ApplicationThread继承自ApplicationThreadNative,ApplicationThreadNative继承自Binder并实现IApplicationThread接口。和ActivityManagerService一样,都是IPC的实现方式。

现在来看一下ApplicationThread中scheduleLaunchActivity的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
 // we use token to identify this activity without having to send the
    // activity itself back to the activity manager. (matters more with ipc)
    public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident,
            ActivityInfo info, Configuration curConfig, CompatibilityInfo compatInfo,
            Bundle state, List<ResultInfo> pendingResults,
            List<Intent> pendingNewIntents, boolean notResumed, boolean isForward,
            String profileName, ParcelFileDescriptor profileFd, boolean autoStopProfiler)
    {
        ActivityClientRecord r = new ActivityClientRecord();
        r.token = token;
        r.ident = ident;
        r.intent = intent;
        r.activityInfo = info;
        r.compatInfo = compatInfo;
        r.state = state;
        r.pendingResults = pendingResults;
        r.pendingIntents = pendingNewIntents;
        r.startsNotResumed = notResumed;
        r.isForward = isForward;
        r.profileFile = profileName;
        r.profileFd = profileFd;
        r.autoStopProfiler = autoStopProfiler;
        updatePendingConfiguration(curConfig);
        queueOrSendMessage(H.LAUNCH_ACTIVITY, r);
    }

很简单只是发送了一个名字为H.LAUNCH_ACTIVITY的消息给H这个Handler做处理。在Handler中调用了handleLaunchActivity方法。

6.ActivityThread

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) {
    // If we are getting ready to gc after going to the background, well
    // we are back active so skip it.
    unscheduleGcIdler();

    if (r.profileFd != null) {
        mProfiler.setProfiler(r.profileFile, r.profileFd);
        mProfiler.startProfiling();
        mProfiler.autoStopProfiler = r.autoStopProfiler;
    }

    // Make sure we are running with the most recent config.
    handleConfigurationChanged(null, null);

    if (localLOGV) Slog.v(
        TAG, "Handling launch of " + r);
    Activity a = performLaunchActivity(r, customIntent);

    if (a != null) {
        r.createdConfig = new Configuration(mConfiguration);
        Bundle oldState = r.state;
        handleResumeActivity(r.token, false, r.isForward);

        if (!r.activity.mFinished && r.startsNotResumed) {
            // The activity manager actually wants this one to start out
            // paused, because it needs to be visible but isn't in the
            // foreground.  We accomplish this by going through the
            // normal startup (because activities expect to go through
            // onResume() the first time they run, before their window
            // is displayed), and then pausing it.  However, in this case
            // we do -not- need to do the full pause cycle (of freezing
            // and such) because the activity manager assumes it can just
            // retain the current state it has.
            try {
                r.activity.mCalled = false;
                mInstrumentation.callActivityOnPause(r.activity);
                // We need to keep around the original state, in case
                // we need to be created again.
                r.state = oldState;
                if (!r.activity.mCalled) {
                    throw new SuperNotCalledException(
                        "Activity " + r.intent.getComponent().toShortString() +
                        " did not call through to super.onPause()");
                }

            } catch (SuperNotCalledException e) {
                throw e;

            } catch (Exception e) {
                if (!mInstrumentation.onException(r.activity, e)) {
                    throw new RuntimeException(
                            "Unable to pause activity "
                            + r.intent.getComponent().toShortString()
                            + ": " + e.toString(), e);
                }
            }
            r.paused = true;
        }
    } else {
        // If there was an error, for any reason, tell the activity
        // manager to stop us.
        try {
            ActivityManagerNative.getDefault()
                .finishActivity(r.token, Activity.RESULT_CANCELED, null);
        } catch (RemoteException ex) {
            // Ignore
        }
    }
}

从源码可以看出performLaunchActivity这个方法完成了最终的启动过程。我们来分析一下方法中的几个操作步骤。

1.通过ActivityClientRecord获取Activity的基本信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
    ActivityInfo aInfo = r.activityInfo;
    if (r.packageInfo == null) {
        r.packageInfo = getPackageInfo(aInfo.applicationInfo, r.compatInfo,
                Context.CONTEXT_INCLUDE_CODE);
    }

    ComponentName component = r.intent.getComponent();
    if (component == null) {
        component = r.intent.resolveActivity(
            mInitialApplication.getPackageManager());
        r.intent.setComponent(component);
    }

    if (r.activityInfo.targetActivity != null) {
        component = new ComponentName(r.activityInfo.packageName,
                r.activityInfo.targetActivity);
    }

2.通过Instrumentation的newActivity通过ClassLoader创建Activity对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Activity activity = null;
try {
    java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
    activity = mInstrumentation.newActivity(
      cl, component.getClassName(), r.intent);
    StrictMode.incrementExpectedActivityCount(activity.getClass());
    r.intent.setExtrasClassLoader(cl);
    if (r.state != null) {
        r.state.setClassLoader(cl);
    }
} catch (Exception e) {
    if (!mInstrumentation.onException(activity, e)) {
        throw new RuntimeException(
            "Unable to instantiate activity " + component
            + ": " + e.toString(), e);
    }
}

Instrumentation的newActivity方法只有一句。

1
2
3
4
5
6
public Activity newActivity(ClassLoader cl, String className,
    Intent intent)
    throws InstantiationException, IllegalAccessException,
    ClassNotFoundException {
        return (Activity)cl.loadClass(className).newInstance();
}

3.通过LoadedApk中makeApplication方法获取Application对象,如果Application对象存在就不创建,不存在就调用Instrumentation的callApplicationOnCreate创建。

1
Application app = r.packageInfo.makeApplication(false, mInstrumentation);

4.创建ContextImpl对象,并通过Activity的attach方法来完成数据初始化。

1
2
3
4
5
6
7
8
9
10
ContextImpl appContext = new ContextImpl();
appContext.init(r.packageInfo, r.token, this);
appContext.setOuterContext(activity);
CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
Configuration config = new Configuration(mCompatConfiguration);
if (DEBUG_CONFIGURATION) Slog.v(TAG, "Launching activity "
                        + r.activityInfo.name + " with config " + config);
activity.attach(appContext, this, getInstrumentation(), r.token,
                        r.ident, app, r.intent, r.activityInfo, title, r.parent,
                        r.embeddedID, r.lastNonConfigurationInstances, config);

5.Activity生命周期逻辑,意味着Activity已经完成了启动过程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
if (customIntent != null) {
    activity.mIntent = customIntent;
}
r.lastNonConfigurationInstances = null;
activity.mStartedActivity = false;
int theme = r.activityInfo.getThemeResource();
if (theme != 0) {
    activity.setTheme(theme);
}

activity.mCalled = false;
mInstrumentation.callActivityOnCreate(activity, r.state);
if (!activity.mCalled) {
    throw new SuperNotCalledException(
    "Activity " + r.intent.getComponent().toShortString() +
    " did not call through to super.onCreate()");
}
r.activity = activity;
r.stopped = true;
if (!r.activity.mFinished) {
    activity.performStart();
    r.stopped = false;
}
if (!r.activity.mFinished) {
    if (r.state != null) {
        mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state);
    }
}
if (!r.activity.mFinished) {
    activity.mCalled = false;
    mInstrumentation.callActivityOnPostCreate(activity, r.state);
    if (!activity.mCalled) {
        throw new SuperNotCalledException(
        "Activity " + r.intent.getComponent().toShortString() +
        " did not call through to super.onPostCreate()");
    }
}

浅谈Android动画

在Android中动画分为以下4种:

  1. Tween Animation 补间动画
  2. Frame Animation 帧动画
  3. Layout Animation 布局动画
  4. Property Animation 属性动画

动画实现流程:

  1. 创建Animation
  2. 设置相关属性
  3. View调用startAnimation方法启动动画

Animation

Animation类是Android系统的一个动画抽象类,所有其他一些动画类都要继承该类中的实现方法。Animation类主要用于补间动画效果,提供了动画启动、停止、重复、持续时间等方法。Animation类中的方法适用于任何一种补间动画对象。

常用方法:

start() 启动

startNow() 立即启动

cancel() 取消动画

setDuration(long) 设置持续时间

setRepeatMode(int) 设置重复模式

setRepeatCount(int) 设置重复次数

setFillEnabled(boolean) 能否填充位置

setFillBefore(boolean) 回到起始填充位置

setFillAfter(boolean) 回到结束填充位置

setStartOffset(long) 设置延时启动时间

setInterpolator(Interpolator) 设置加速曲线

setAnimationListener(AnimationListener) 设置动画的回调

Tween Animation

该类Animations提供了旋转、移动、伸展和淡出等效果。Alpha——淡入淡出,Scale——缩放效果,Rotate——旋转,Translate——移动效果。

TranslateAnimation

TranslateAnimation类是Android系统中的位置变化动画类,用于控制View对象的位置变化,该类继承于Animation类。TranslateAnimation类中的很多方法都与Animation类一致,该类中最常用的方法便是TranslateAnimation构造方法。

public TranslateAnimation (float fromXDelta, float toXDelta, float fromYDelta, float toYDelta)

参数说明

fromXDelta:位置变化的起始点X坐标。

toXDelta:位置变化的结束点X坐标。

fromYDelta:位置变化的起始点Y坐标。

toYDelta:位置变化的结束点Y坐标。

RotateAnimation

RotateAnimation类是Android系统中的旋转变化动画类,用于控制View对象的旋转动作,该类继承于Animation类。RotateAnimation类中的很多方法都与Animation类一致,该类中最常用的方法便是RotateAnimation构造方法。

public RotateAnimation (float fromDegrees, float toDegrees, int pivotXType, float pivotXValue, int pivotYType, float pivotYValue)

参数说明

fromDegrees:旋转的开始角度。

toDegrees:旋转的结束角度。

pivotXType:X轴的伸缩模式,可以取值为ABSOLUTE、RELATIVE_TO_SELF、RELATIVE_TO_PARENT。

pivotXValue:X坐标的伸缩值。

pivotYType:Y轴的伸缩模式,可以取值为ABSOLUTE、RELATIVE_TO_SELF、RELATIVE_TO_PARENT。

pivotYValue:Y坐标的伸缩值。

ScaleAnimation

ScaleAnimation类是Android系统中的尺寸变化动画类,用于控制View对象的尺寸变化,该类继承于Animation类。ScaleAnimation类中的很多方法都与Animation类一致,该类中最常用的方法便是ScaleAnimation构造方法。

public ScaleAnimation (float fromX, float toX, float fromY, float toY, int pivotXType, float pivotXValue, int pivotYType, float pivotYValue)

参数说明

fromX:起始X坐标上的伸缩尺寸。

toX:结束X坐标上的伸缩尺寸。

fromY:起始Y坐标上的伸缩尺寸。

toY:结束Y坐标上的伸缩尺寸。

pivotXType:X轴的伸缩模式,可以取值为ABSOLUTE、RELATIVE_TO_SELF、RELATIVE_TO_PARENT。

pivotXValue:X坐标的伸缩值。

pivotYType:Y轴的伸缩模式,可以取值为ABSOLUTE、RELATIVE_TO_SELF、RELATIVE_TO_PARENT。

pivotYValue:Y坐标的伸缩值。

AlphaAnimation

AlphaAnimation类是Android系统中的透明度变化动画类,用于控制View对象的透明度变化,该类继承于Animation类。AlphaAnimation类中的很多方法都与Animation类一致,该类中最常用的方法便是AlphaAnimation构造方法。

public AlphaAnimation (float fromAlpha, float toAlpha)

参数说明

fromAlpha:开始时刻的透明度,取值范围0~1。

toAlpha:结束时刻的透明度,取值范围0~1。

AnimationSet

AnimationSet类是Android系统中的动画集合类,用于控制View对象进行多个动作的组合,该类继承于Animation类。AnimationSet类中的很多方法都与Animation类一致,该类中最常用的方法便是addAnimation方法,该方法用于为动画集合对象添加动画对象。

public void addAnimation (Animation a)

其中,参数a为Animation动画对象,可以是前述任何一种补间动作。

AnimationUtils

AnimationUtils类是Android系统中的动画工具类,提供了控制View对象的一些工具。该类中最常用的方法便是loadAnimation方法,该方法用于加载XML格式的动画配置文件。在Android系统中,除了在代码中设置动画效果外,还可以在XML配置文件中设置动画的组合动作,这种方式适用性更好。

public static Animation loadAnimation (Context context, int id)

参数说明

context:上下文对象。

id:动画配置文件的ID。

Frame Animation

AnimationDrawable类:帧动画类

AnimationDrawable类是Android系统中的帧动画类。帧动画方式类似于放电影的原理,是通过顺序播放多张图片来实现动画效果的,图片之间有一定的动作连贯性,这样人眼看来就像对象真正在运动一样。AnimationDrawable类位于android.graphics.drawable软件包中,本节将介绍帧动画类中的主要编程方法。

1
2
3
4
5
6
7
8
9
<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
                android:oneshot="true">
    <item android:drawable="@drawable/p0" android:duration="50"/>
    <item android:drawable="@drawable/p1" android:duration="50"/>
    <item android:drawable="@drawable/p2" android:duration="50"/>
    <item android:drawable="@drawable/p3" android:duration="50"/>
    <item android:drawable="@drawable/p4" android:duration="50"/>
</animation-list>

通过动画配置文件,将其加载到ImageView的背景中,再start启动。

Layout Animations

LayoutAnimationsController可以用于实现使多个控件按顺序一个一个的显示。

  1. LayoutAnimationsController用于为一个layout里面的控件,或者是一个ViewGroup里面的控件设置统一的动画效果。
  2. 每一个控件都有相同的动画效果。
  3. 控件的动画效果可以在不同的时间显示出来。
  4. LayoutAnimationsController可以在xml文件当中设置,也可以在代码当中进行设置。
LayoutAnimations
1
2
3
4
5
6
7
8
9
10
//1.加载动画set的XML文件
Animation animation = (Animation) AnimationUtils.loadAnimation(Animation2Activity.this, R.anim.list_anim);
//2.初始化LayoutAnimationController
LayoutAnimationController controller = new LayoutAnimationController(animation);
//3.设置子View动画顺序
controller.setOrder(LayoutAnimationController.ORDER_NORMAL);
//4.设置动画延迟
controller.setDelay(0.5f);
//5.将LayoutAnimation设置给ViewGroup
listView.setLayoutAnimation(controller);

Property Animation

属性动画要求对象必须实现对应的get和set方法,属性动画根据传递的初始值和最终值,以动画的效果多次调用对应的set方法,根据时间的推移越来越接近最终值。

如果需要对属性xxx做动画,必须满足两个条件:

  1. object必须提供set方法,如果不指定初始状态则必须提供get方法(系统会通过get方法拿初始属性)。
  2. object的set方法必须通过UI反应出来,不然动画无效(这点比较好理解,使UI布局更新)。

Google给出了3个解决方法:

  1. 给对象添加set和get方法,前提是拥有权限
  2. 用一个包装类,实现对应的set和get方法
  3. 采用ValueAnimator,监听动画过程,自己实现属性的改变

对象直接添加set和get方法

这个方式很简单,但是局限性较大。假如你想对Android SDK中的对象添加方法,正常是无法实现的。

使用包装类间接实现set和get方法

ViewWrapper
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
private void performAnimate() {
    ViewWrapper wrapper = new ViewWrapper(mButton);
    ObjectAnimator.ofInt(wrapper, "width", 500).setDuration(5000).start();
}

@Override
public void onClick(View v) {
    if (v == mButton) {
        performAnimate();
    }
}

private static class ViewWrapper {
    private View mTarget;

    public ViewWrapper(View target) {
        mTarget = target;
    }

    public int getWidth() {
        return mTarget.getLayoutParams().width;
    }

    public void setWidth(int width) {
        mTarget.getLayoutParams().width = width;
        mTarget.requestLayout();
    }
}

上述代码5s内让View的宽度增加到500px,为了达到这个效果,我们提供了ViewWrapper类专门用于包装View,具体到本例是包装View,然后我们对ViewWrapper的width熟悉做动画,并且在setWidth方法中修改其内部的target的宽度,而target实际上就是我们包装的Button,这样一个间接属性动画就搞定了。上述代码同样适用于一个对象的其他属性。

采用ValueAnimator,监听动画过程,自己实现属性的改变

ValueAnimator本身不作用于任何对象,也就是说直接使用它没有任何动画效果。它可以对一个值做动画,然后我们可以监听其动画过程,在动画过程中修改我们的对象的属性值,这样也就相当于我们的对象做了动画。

ValueAnimators
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
private void performAnimate(final View target, final int start, final int end) {
    ValueAnimator valueAnimator = ValueAnimator.ofInt(1, 100);

    valueAnimator.addUpdateListener(new AnimatorUpdateListener() {

        //持有一个IntEvaluator对象,方便下面估值的时候使用
        private IntEvaluator mEvaluator = new IntEvaluator();

        @Override
        public void onAnimationUpdate(ValueAnimator animator) {
            //获得当前动画的进度值,整型,1-100之间
            int currentValue = (Integer)animator.getAnimatedValue();
            Log.d(TAG, "current value: " + currentValue);

            //计算当前进度占整个动画过程的比例,浮点型,0-1之间
            float fraction = currentValue / 100f;

            //这里我偷懒了,不过有现成的干吗不用呢
            //直接调用整型估值器通过比例计算出宽度,然后再设给Button
            target.getLayoutParams().width = mEvaluator.evaluate(fraction, start, end);
            target.requestLayout();
        }
    });

    valueAnimator.setDuration(5000).start();
}

@Override
public void onClick(View v) {
    if (v == mButton) {
        performAnimate(mButton, mButton.getWidth(), 500);
    }
}

关于这个ValueAnimator我要再说一下,拿上例来说,它会在5000ms内将一个数从1变到100,然后动画的每一帧会回调onAnimationUpdate方法,在这个方法里,我们可以获取当前的值(1-100),根据当前值所占的比例(当前值/100),我们可以计算出Button现在的宽度应该是多少,比如时间过了一半,当前值是50,比例为0.5,假设Button的起始宽度是100px,最终宽度是500px,那么Button增加的宽度也应该占总增加宽度的一半,总增加宽度是500-100=400,所以这个时候Button应该增加宽度400*0.5=200,那么当前Button的宽度应该为初始宽度+ 增加宽度(100+200=300)。上述计算过程很简单,其实它就是整型估值器IntEvaluator的内部实现。

最后

  1. View动画(渐变动画)的功能是有限的,大家可以尝试使用属性动画
  2. 为了在各种安卓版本上使用属性动画,你需要采用nineoldandroids,它是GitHub开源项目,jar包和源码都可以在网上下到,如果下不到jar包,我可以发给大家
  3. 再复杂的动画都是简单动画的合理组合,再加上本文介绍的方法,可以对任何属性作用动画效果,也就是说你几乎可以做出任何动画
  4. 属性动画中的插值器(Interpolator)和估值器(TypeEvaluator)很重要,它是实现非匀速动画的重要手段,你应该试着搞懂它,最好你还能够自定义它们

参考资料:Android属性动画深入分析:让你成为动画牛人

Android绘制过程

最近群里面有些小伙伴问我,希望可以解释一下View间的绘制关系,所以简单的写一下。

Android是如何绘制View的

一旦Activity获得焦点之后,将会触发绘制布局的流程。 绘制过程是从布局的根节点(root node)开始的,根据布局的树状结构测量(measure)并绘制(draw)。 这个过程用于规定每一个View的区域,然后绘制对应的界面。 ViewGroup用于负责请求每一个它的子View进行绘制,当子View接到请求则开始绘制自己的界面。 注意树的遍历是有序的,意味着父View将在子View绘制之前被调用。

Measure与Layout

绘制布局分为两个过程:measure和layout。

measure(int, int)方法用来自上而下的遍历View,每个View都是递归的测量自己所占的大小。

layout(int, int, int, int)方法同样是自上而下的,它的功能是将子View放置在合适的位置。

当View对象调用了measure方法之后,通过getMeasuredWidth()和getMeasuredHeight()方法就可以获取到测量后的值。 注意一点,子View的宽和高不得超过在父View。这可以确保所有的子View都被父View所包含。

ViewGroup.LayoutParams

父类如何得知子类想要如何布局呢,ViewGroup.LayoutParams类用于告诉父类它们的布局预期。 我们可以使用3种配置方式:

  • 准确的值
  • MATCH_PARENT,子View想与父View一样大小
  • WRAP_CONTENT,子View的大小足以包括它的内容

MeasureSpec

MeasureSpec用于父View向下要求子View的绘制模式,MeasureSpec有3种模式:

  • UNSPECIFIED,表示大小并不明确
  • EXACTLY,表示父类给定一个准确的大小
  • AT MOST,表示父类限制了子类的最大值

上面的逻辑是不是太抽象了?简单来说就是一个View如果想要展示出来需要两步:1、计算自己所占空间的大小。2、子View摆放的位置。(所有过程都是从父View发起,直到子View计算完成向它的父View返回结果)

自定义一个ViewGroup

我们自定义一个ViewGroup通常分为以下几步:

1.继承ViewGroup并且重写父类的三个构造函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class CustomViewGroup extends ViewGroup {

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

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

  public CustomViewGroup(Context context, AttributeSet attrs, intdefStyle) {
      super(context, attrs, defStyle);
    }
}

2.重载onMeasure()方法 自定义ViewGroup的onMeasure()方法中,除了计算自身的尺寸外,还需要调用measureChildren()函数来计算子控件的尺寸。

onMeasure()的定义不是本文的讨论重点,因此这里我直接使用默认的onMeasure()定义,当然measureChildren()是必须得加的,或者我们针对性的调用子View的measure方法。 通常我们使用MeasureSpec.makeMeasureSpec(int size, int mode)来产生一个规格。 使用getChildCount()获取子View个数。使用getChildAt(i)获得对应的子View。在最后不要忘记使用setMeasuredDimension方法设置当前View的规格。

3.实现onLayout()方法 通常我们在这个方法里面调用getMeasuredWidth()和getMeasuredHeight()获取已经测量过的View大小, 然后根据这些数据来计算每个View对应的位置。通过layout(int l, int t, int r, int b)设置子View在当前View中的位置。

4.添加LayoutParams generateLayoutParams()用于返回一个LayoutParams给子View,这样子View就可以将对应layout布局的参数传入, 我们自定义的父View可以使用getLayoutParams()获取我们传入的LayoutParams。

你可以跟踪源码看看,其实XML文件中View的layout_xxx参数都是被传递到了各种自定义ViewGroup.LayoutParams派生类对象中。例如LinearLayout的LayoutParams定义的关键部分如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class LinearLayout extends ViewGroup {

    public static class LayoutParams extends ViewGroup.MarginLayoutParams {

        public float weight;
        public int gravity = -1;

        public LayoutParams(Context c, AttributeSet attrs) {

                super(c, attrs);

                TypedArray a = c.obtainStyledAttributes(attrs, com.android.internal.R.styleable.LinearLayout_Layout);
                weight = a.getFloat(com.android.internal.R.styleable.LinearLayout_Layout_layout_weight, 0);
                gravity = a.getInt(com.android.internal.R.styleable.LinearLayout_Layout_layout_gravity, -1);

                a.recycle();
        }
    }

    @Override
    public LayoutParams generateLayoutParams(AttributeSet attrs) {
        return new LinearLayout.LayoutParams(getContext(), attrs);
    }
}

这样你大概就可以理解为什么LinearLayout的子控件支持weight和gravity的设置了吧,当然我们也可以这样自定义一些属于我们ViewGroup特有的params。

这样修改之后,我们就可以在onLayout()函数中获取子控件的layout_xxx值了。

Eclipse ADT Bundle下载

Android官方主页不再提供Eclipse ADT Bundle的下载,并且声明放弃维护。

以下是20140702对应操作系统的下载地址:

windows 32: https://dl.google.com/android/adt/adt-bundle-windows-x86-20140702.zip

windows 64: https://dl.google.com/android/adt/adt-bundle-windows-x86_64-20140702.zip

Mac 64: https://dl.google.com/android/adt/adt-bundle-mac-x86_64-20140702.zip

Linux 86: https://dl.google.com/android/adt/adt-bundle-linux-x86-20140702.zip

Linux 64: https://dl.google.com/android/adt/adt-bundle-linux-x86_64-20140702.zip

JVM工作原理摘要

一、JVM的生命周期

1.JVM实例对应了一个独立运行的java程序它是进程级别

a)启动。启动一个Java程序时,一个JVM实例就产生了,任何一个拥有public static void main(String[] args)函数的class都可以作为JVM实例运行的起点

b)运行。main()作为该程序初始线程的起点,任何其他线程均由该线程启动。JVM内部有两种线程:守护线程和非守护线程,main()属于非守护线程,守护线程通常由JVM自己使用,java程序也可以标明自己创建的线程是守护线程

c)消亡。当程序中的所有非守护线程都终止时,JVM才退出;若安全管理器允许,程序也可以使用Runtime类或者System.exit()来退出

2.JVM执行引擎实例则对应了属于用户运行程序的线程它是线程级别的

二、JVM的体系结构

1.类装载器(ClassLoader)(用来装载.class文件)

2.执行引擎(执行字节码,或者执行本地方法)

3.运行时数据区(方法区、堆、java栈、PC寄存器、本地方法栈)

三、JVM类加载器

JVM整个类加载过程的步骤:

1.装载

装载过程负责找到二进制字节码并加载至JVM中,JVM通过类名、类所在的包名通过ClassLoader来完成类的加载, 同样,也采用以上三个元素来标识一个被加载了的类:类名+包名+ClassLoader实例ID。

2.链接

链接过程负责对二进制字节码的格式进行校验、初始化装载类中的静态变量以及解析类中调用的接口、类。

完成校验后,JVM初始化类中的静态变量,并将其值赋为默认值。

最后对类中的所有属性、方法进行验证,以确保其需要调用的属性、方法存在,以及具备应的权限(例如public、private域权限等),会造成NoSuchMethodError、NoSuchFieldError等错误信息。

3.初始化

初始化过程即为执行类中的静态初始化代码、构造器代码以及静态属性的初始化,在四种情况下初始化过程会被触发执行:

调用了new;

反射调用了类中的方法;

子类调用了初始化;

JVM启动过程中指定的初始化类。

JVM类加载顺序:

JVM两种类装载器包括:启动类装载器和用户自定义类装载器。

启动类装载器是JVM实现的一部分;

用户自定义类装载器则是Java程序的一部分,必须是ClassLoader类的子类。

JVM装载顺序:

Jvm启动时,由Bootstrap向User-Defined方向加载类;

应用进行ClassLoader时,由User-Defined向Bootstrap方向查找并加载类;

1.Bootstrap ClassLoader

这是JVM的根ClassLoader,它是用C++实现的,JVM启动时初始化此ClassLoader,并由此ClassLoader完成$JAVA_HOME中jre/lib/rt.jar(Sun JDK的实现)中所有class文件的加载,这个jar中包含了java规范定义的所有接口以及实现。

2.Extension ClassLoader

JVM用此classloader来加载扩展功能的一些jar包。

3.System ClassLoader

JVM用此classloader来加载启动参数中指定的Classpath中的jar包以及目录,在Sun JDK中ClassLoader对应的类名为AppClassLoader。

4.User-Defined ClassLoader

User-DefinedClassLoader是Java开发人员继承ClassLoader抽象类自行实现的ClassLoader,基于自定义的ClassLoader可用于加载非Classpath中的jar以及目录。

ClassLoader抽象类的几个关键方法:

(1)loadClass

此方法负责加载指定名字的类,ClassLoader的实现方法为先从已经加载的类中寻找,如没有则继续从parent ClassLoader中寻找,如仍然没找到,则从System ClassLoader中寻找,最后再调用findClass方法来寻找,如要改变类的加载顺序,则可覆盖此方法

(2)findLoadedClass

此方法负责从当前ClassLoader实例对象的缓存中寻找已加载的类,调用的为native的方法。

(3)findClass

此方法直接抛出ClassNotFoundException,因此需要通过覆盖loadClass或此方法来以自定义的方式加载相应的类。

(4)findSystemClass

此方法负责从System ClassLoader中寻找类,如未找到,则继续从Bootstrap ClassLoader中寻找,如仍然为找到,则返回null。

(5)defineClass

此方法负责将二进制的字节码转换为Class对象

(6)resolveClass

此方法负责完成Class对象的链接,如已链接过,则会直接返回。

四、JVM执行引擎

在执行方法时JVM提供了四种指令来执行:

  1. invokestatic:调用类的static方法

  2. invokevirtual:调用对象实例的方法

  3. invokeinterface:将属性定义为接口来进行调用

  4. invokespecial:JVM对于初始化对象(Java构造器的方法为:)以及调用对象实例中的私有方法时。

主要的执行技术有:

解释,即时编译,自适应优化、芯片级直接执行

  1. 解释属于第一代JVM,

  2. 即时编译JIT属于第二代JVM,

  3. 自适应优化(目前Sun的HotspotJVM采用这种技术)则吸取第一代JVM和第二代

JVM的经验,采用两者结合的方式

开始对所有的代码都采取解释执行的方式,并监视代码执行情况,然后对那些经常调用的方法启动一个后台线程,将其编译为本地代码,并进行优化。若方法不再频繁使用,则取消编译过的代码,仍对其进行解释执行。

五、JVM运行时数据区

第一块:PC寄存器

PC寄存器是用于存储每个线程下一步将执行的JVM指令,如该方法为native的,则PC寄存器中不存储任何信息。

第二块:JVM栈

JVM栈是线程私有的,每个线程创建的同时都会创建JVM栈,JVM栈中存放的为当前线程中局部基本类型的变量(java中定义的八种基本类型:boolean、char、byte、short、int、long、float、double)、部分的返回结果以及Stack Frame,非基本类型的对象在JVM栈上仅存放一个指向堆上的地址

第三块:堆(Heap)

它是JVM用来存储对象实例以及数组值的区域,可以认为Java中所有通过new创建的对象的内存都在此分配,Heap中的对象的内存需要等待GC进行回收。

  1. 堆是JVM中所有线程共享的,因此在其上进行对象内存的分配均需要进行加锁,这也导致了new对象的开销是比较大的

  2. Sun Hotspot JVM为了提升对象内存分配的效率,对于所创建的线程都会分配一块独立的空间TLAB(Thread Local Allocation Buffer),其大小由JVM根据运行的情况计算而得,在TLAB上分配对象时不需要加锁,因此JVM在给线程的对象分配内存时会尽量的在TLAB上分配,在这种情况下JVM中分配对象内存的性能和C基本是一样高效的,但如果对象过大的话则仍然是直接使用堆空间分配

  3. TLAB仅作用于新生代的Eden Space,因此在编写Java程序时,通常多个小的对象比大的对象分配起来更加高效。

第四块:方法区域(Method Area)

  1. 在Sun JDK中这块区域对应的为PermanetGeneration,又称为持久代。

  2. 方法区域存放了所加载的类的信息(名称、修饰符等)、类中的静态变量、类中定义为final类型的常量、类中的Field信息、类中的方法信息,当开发人员在程序中通过Class

对象中的getName、isInterface等方法来获取信息时,这些数据都来源于方法区域,同时方法区域也是全局共享的,在一定的条件下它也会被GC,当方法区域需要使用的内存超过其允许的大小时,会抛出OutOfMemory的错误信息。

第五块:运行时常量池(Runtime Constant Pool)

存放的为类中的固定的常量信息、方法和Field的引用信息等,其空间从方法区域中分配。

第六块:本地方法堆栈(Native Method Stacks)

JVM采用本地方法堆栈来支持native方法的执行,此区域用于存储每个native方法调用的状态。

六、JVM垃圾回收

JVM分别对新生代和旧生代采用的两种垃圾回收机制?

  1. 新生代的GC:新生代通常存活时间较短,因此基于Copying算法来进行回收,所谓Copying算法就是扫描出存活的对象,并复制到一块新的完全未使用的空间中,对应于新生代,就是在Eden和FromSpace或ToSpace之间copy。新生代采用空闲指针的方式来控制GC触发,指针保持最后一个分配的对象在新生代区间的位置,当有新的对象要分配内存时,用于检查空间是否足够,不够就触发GC。当连续分配对象时,对象会逐渐从eden到survivor,最后到旧生代。

  2. 旧生代的GC:旧生代与新生代不同,对象存活的时间比较长,比较稳定,因此采用标记(Mark)算法来进行回收,所谓标记就是扫描出存活的对象,然后再进行回收未被标记的对象,回收后对用空出的空间要么进行合并,要么标记出来便于下次进行分配,总之就是要减少内存碎片带来的效率损耗。

如何判断对象是否可以被回收?

  1. 引用计数法

  2. 根搜索算法

垃圾收集算法:

  1. 标记-清除(Mark-Sweep)算法

  2. 复制算法

  3. 标记-整理算法

  4. 分代收集算法

GC的基本原理:将内存中不再被使用的对象进行回收,GC中用于回收的方法称为收集器,由于GC需要消耗一些资源和时间,Java在对对象的生命周期特征进行分析后,按照新生代、旧生代的方式来对对象进行收集,以尽可能的缩短GC对应用造成的暂停

  1. 对新生代的对象的收集称为minor GC;

  2. 对旧生代的对象的收集称为Full GC;

  3. 程序中主动调用System.gc()强制执行的GC为Full GC。

不同的对象引用类型, GC会采用不同的方法进行回收,JVM对象的引用分为了四种类型:

  1. 强引用:默认情况下,对象采用的均为强引用(这个对象的实例没有其他对象引用,GC时才会被回收)

  2. 软引用:软引用是Java中提供的一种比较适合于缓存场景的应用(只有在内存不够用的情况下才会被GC)

  3. 弱引用:在GC时一定会被GC回收

  4. 虚引用:由于虚引用只是用来得知对象是否被GC

JVM和DVM的不同点

1. Dalvik 和标准 Java 虚拟机(JVM)的首要差别

Dalvik 基于寄存器,而 JVM 基于栈。基于寄存器的虚拟机对于更大的程序来说,在它们编译的时候,花费的时间更短。

2. Dalvik 和 Java 字节码的区别

Dalvik执行.dex格式的字节码,而JVM执行.class格式的字节码。android程序编译完之后生产.class文件,还有通过aapt工具生成的R.class等,然后dx工具会把.class文件处理成.dex文件,最终资源文件和.dex文件等打包成.apk文件。

3. Dalvik和Java运行环境的区别

Dalvik主要是完成对象生命周期管理,堆栈管理,线程管理,安全和异常管理,以及垃圾回收等等重要功能。 Dalvik负责进程隔离和线程管理,每一个Android应用在底层都会对应一个独立的Dalvik虚拟机实例,其代码在虚拟机的解释下得以执行。

Android开发中使用Java 8 Stream

你将从这篇文章中了解到什么是Stream,并且如何在Android开发中使用它。

不幸的是Android还不支持Java 8,Kitkat(Android 4.4+)以后的版本可以支持Java 7。 那么只能和新特性说再见了吗?当然不是,一些聪明人想出了一个解决办法:

RETROLAMBDA

如何安装RETROLAMBDA

作为热身,让我们来看看如何快速的在项目中使用Retrolambda。 (假设你已经了解Android Studio的gradle构建系统以及它是如何工作的)

1.在./build.gradle文件中添加新的classpath:

1437106139.png

2.在./app/build.gradle文件中添加Retrolambda插件:

1437106186.png

3.还需要添加:

1437106215.png

4.编译gradle

STREAM

A stream is an abstraction for specifying aggregate computations on a DataSet

Java 8 Stream API引入的目的在于弥补Java函数式编程的缺陷。对于很多支持函数式编程的语言,map()、reduce()基本上都内置到语言的标准库中了,不过,Java 8的Stream API总体来讲仍然是非常完善和强大,足以用很少的代码完成许多复杂的功能。

创建一个Stream有很多方法,最简单的方法是把一个Collection变成Stream。我们来看最基本的几个操作:

public static void main(String[] args) {
    List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
    Stream<Integer> stream = numbers.stream();
    stream.filter((x) -> {
        return x % 2 == 0;
    }).map((x) -> {
        return x * x;
    }).forEach(System.out::println);
}

集合类新增的stream()方法用于把一个集合变成Stream,然后,通过filter()、map()等实现Stream的变换。Stream还有一个forEach()来完成每个元素的迭代。

使用Stream的两个原因:

1.集合类会持有所有元素在内存中,大集合会占用大量内存。而Stream的元素是在访问的时候被计算出来,内存占用小。

2.二是集合类的迭代逻辑是调用者负责,通常是for循环,而Stream的迭代是隐含在对Stream的各种操作中,例如map()。

更多特性参考InfoQ的文章Java 8新特性:全新的Stream API

Android中使用轻量级Stream API

上一部分介绍了Java 8 Stream的使用,但是我们的目的是讨论Android如何去使用这个特性。我们可以通过 Lightweight-Stream-API,只需要增加它的classpath。

compile 'com.annimon:stream:1.0.1'

JAVA 8 vs Lightweight-Stream-API(LSA)

虽然Java 8的Stream和LSA工作方式一样,但他们间仍有少量的区别。例如使用LSA创建Stream时使用Stream.of(YourCollection),而Java 8中使用Stream。 另一个区别为排序操作,Java 8中使用‘sort()’,而LSA中使用‘sorted()’。