over 6 years ago

压缩图片

inSampleSize : 图片缩放值,1 表示宽度和高度不缩放,2 表示缩放原图宽度和高度的 1/2. 以此类推.

bm.compress(Bitmap.CompressFormat.JPEG, 80, output);

compress 方法的参数,Bitmap.CompressFormat.JPEG 表示压缩成JPEG格式,80 表示压缩图片的质量值,output 表示压缩的图片导入对象。

package com.yibu.kuaibu.utils;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;

/**
 * Created by xufei on 4/17/15.
 */
public class ImageCompress {

    /**
     * 计算图片的缩放值
     *
     * @param options
     * @return
     */
    public static int calculateInSampleSize(BitmapFactory.Options options,
                                            int rqsW, int rqsH) {
        final int height = options.outHeight;
        final int width = options.outWidth;
        int inSampleSize = 1;
        if (rqsW == 0 || rqsH == 0) return 1;
        if (height > rqsH || width > rqsW) {
            final int heightRatio = Math.round((float) height/ (float) rqsH);
            final int widthRatio = Math.round((float) width / (float) rqsW);
            inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
        }
        return inSampleSize;
    }

    /**
     * 根据路径获得突破并压缩返回bitmap用于显示
     *
     * @return
     */
    public static Bitmap getSmallBitmap(String filePath, int width, int height) {
        final BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeFile(filePath, options);

        // Calculate inSampleSize
        options.inSampleSize = calculateInSampleSize(options, width, height);

        // Decode bitmap with inSampleSize set
        options.inJustDecodeBounds = false;

        return BitmapFactory.decodeFile(filePath, options);
    }

    /**
     * compress
     *
     * @param filePath
     * @return
     */
    public static byte[] compressImageToByte(String filePath, int width, int height) {

        Bitmap bm = getSmallBitmap(filePath, width, height);

        ByteArrayOutputStream output = new ByteArrayOutputStream();
        bm.compress(Bitmap.CompressFormat.JPEG, 80, output); // 80 image quality

        return output.toByteArray();
    }

}

Resource

http://www.eoeandroid.com/thread-310706-1-1.html
http://www.360doc.com/content/14/0429/11/11800748_373154034.shtml
http://www.oschina.net/code/snippet_216465_36833

 
almost 7 years ago

使用 Fragment 和 ViewPager 实现不同pager.

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

    <android.support.v4.view.ViewPager
        android:id="@+id/vpPager"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <android.support.v4.view.PagerTabStrip
            android:id="@+id/pager_header"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_gravity="top"
            android:paddingBottom="4dp"
            android:paddingTop="4dp" />

    </android.support.v4.view.ViewPager>

</LinearLayout>
编写 Fragment
public class FirstFragment extends Fragment {
    // Store instance variables
    private String title;
    private int page;

    // newInstance constructor for creating fragment with arguments
    public static FirstFragment newInstance(int page, String title) {
        FirstFragment fragmentFirst = new FirstFragment();
        Bundle args = new Bundle();
        args.putInt("someInt", page);
        args.putString("someTitle", title);
        fragmentFirst.setArguments(args);
        return fragmentFirst;
    }

    // Store instance variables based on arguments passed
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        page = getArguments().getInt("someInt", 0);
        title = getArguments().getString("someTitle");
    }

    // Inflate the view for the fragment based on layout XML
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_first, container, false);
        TextView tvLabel = (TextView) view.findViewById(R.id.tvLabel);
        tvLabel.setText(page + " -- " + title);
        return view;
    }
}

这里只写一个Fragment 例子,其他基本一样。

编写 Adapter
public static class MyPagerAdapter extends FragmentPagerAdapter {
    private static int NUM_ITEMS = 3;

    public MyPagerAdapter(FragmentManager fragmentManager) {
        super(fragmentManager);
    }

    @Override
    public int getCount() {
        return NUM_ITEMS;
    }

    @Override
    public Fragment getItem(int position) {
        switch (position) { // 根据 position 设置每个Fragment View
        case 0: 
            return FirstFragment.newInstance(0, "Page # 1");
        case 1: 
            return FirstFragment.newInstance(1, "Page # 2");
        case 2: 
            return SecondFragment.newInstance(2, "Page # 3");
        default:
          return null;
        }
    }

    @Override
    public CharSequence getPageTitle(int position) {
      return "Page " + position;
    }
}
FragmentActivity 中使用 Adapter

使用

public class MainActivity extends FragmentActivity {
    FragmentPagerAdapter adapterViewPager;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_home);
        ViewPager vpPager = (ViewPager) findViewById(R.id.vpPager);
        adapterViewPager = new MyPagerAdapter(getSupportFragmentManager());
        vpPager.setAdapter(adapterViewPager);
    }

    // ...
}
在 Fragment 中使用 Adapter

当 APP 本身已经用 FragmentTabHost 实现了底部 Tab 功能,现在需要在其中一个 Fragment 实现顶部 Tab 功能时,就需要在 Fragment 中使用 ViewPager 。

public class Fragment2 extends Fragment {
    private ViewPager mViewPager;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_content_2, null);
        mViewPager = (ViewPager) view.findViewById(R.id.vpPager);
        mPagerAdapter = new MyPagerAdapter(getFragmentManager());
        mViewPager.setAdapter(mPagerAdapter);
        mViewPager.setOffscreenPageLimit(3); // 设置ViewPager个数

        controlView();

        return view;
    }

    //....
}

在使用过程中遇到 Bug,重现的步骤是第一次点击 ViewPager Fragment ,再点击一个其他的 Fragment ,再点击回页面就会崩溃。报错信息:

The specified child already has a parent. You must call removeView() on the child's parent first.

在最初实现中我使用如下方式创建View

public class Fragment2 extends Fragment {
    private View mView;
    private ViewPager mViewPager;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        mView = getActivity().getLayoutInflater().inflate(R.layout.fragment_content_2, null);
        mViewPager = (ViewPager) mView.findViewById(R.id.vpPager);
        mPagerAdapter = new MyPagerAdapter(getFragmentManager());
        mViewPager.setAdapter(mPagerAdapter);
        mViewPager.setOffscreenPageLimit(2);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        return mView;
    }
}

这种方式的好处在与只需要在 onCreate 时把 View 创建好就行。但是在 Fragment 和 Fragment ViewPager 组合使用会导致Bug。

设置 OnPageChangeListener
private void controlView() {
    mViewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
        @Override
        public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
            Toast.makeText(getActivity().getApplicationContext(),
                    "选择页面: " + position, Toast.LENGTH_SHORT).show();
        }

        @Override
        public void onPageSelected(int position) {

        }

        @Override
        public void onPageScrollStateChanged(int state) {

        }
    });
}

Resource

http://stackoverflow.com/questions/23149981/fragments-the-specified-child-already-has-a-parent-you-must-call-removeview

 
almost 7 years ago

做一下如果使用FragmentTabHost来做Tab进行不同内容在展示,在同一个Activity中。

FragmentTabHost布局
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical">

    <FrameLayout
        android:id="@+id/realtabcontent"
        android:layout_width="fill_parent"
        android:layout_height="0dip"
        android:layout_weight="1" />

    <android.support.v4.app.FragmentTabHost
        android:id="@android:id/tabhost"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:background="@color/white">

        <FrameLayout
            android:id="@android:id/tabcontent"
            android:layout_width="0dp"
            android:layout_height="0dp"
            android:layout_weight="0" />
    </android.support.v4.app.FragmentTabHost>

</LinearLayout>

由于 FragmentTabHost 并不兼容Android低版本,所以需要导入一个 android.support.v4 库来支持。

设置Tab Class
mLayoutInflater = LayoutInflater.from(this);

        mTabHost = (FragmentTabHost) findViewById(android.R.id.tabhost);
        mTabHost.setup(this, getSupportFragmentManager(), R.id.realtabcontent);

        View mMenuTabView = mLayoutInflater.inflate(R.layout.tab_item, null);
        TextView mMenuTextView = (TextView) mMenuTabView.findViewById(R.id.textview);
        mMenuTextView.setText("菜单");
        mTabHost.addTab(
                mTabHost.newTabSpec("Menu").setIndicator(mMenuTabView), Fragment1.class, null);
        mTabHost.getTabWidget().getChildAt(0).setBackgroundResource(R.drawable.tab_background); // 设置按钮颜色以及点击后变化

按钮布局

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:drawable="@color/white" android:state_pressed="true" /> // 点击前效果
    <item android:drawable="@color/tabSelected" android:state_selected="true" /> // 点击后效果
</selector>
每个Tab的内容
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    return inflater.inflate(R.layout.fragment_content_1, null);
}
效果
http://a2.qpic.cn/psb?/V12FB5eo4QLuH9/OP4AsX73trW.fNX8LbKi54m*UuoYJg.qDAVj66veE*g!/b/dItp6.HhCwAA&bo=gAJFA8gEQAYDCmE!&rf=viewer_4
 
almost 7 years ago

创建

Proc.new()
proc{}

调用

调用 Proc#call 方法执行块

2.1.3 (main):0 > leap = Proc.new do |year|
2.1.3 (main):0 *   year % 4 == 0 && year % 100 != 0 || year % 400 == 0
2.1.3 (main):0 * end

2.1.3 (main):0 > leap.call(1999)
=> false

lambda

不同点
  1. lambda 的参数数量的检查更加严密。
  2. lambda 可以使用return将值从块中返回。
2.1.3 (main):0 > lam = lambda do |x|
2.1.3 (main):0 *   return x**3
2.1.3 (main):0 * end
=> #<Proc:0x007fd450531cf0@(pry):6 (lambda)>
2.1.3 (main):0 > lam.call(2)
=> 8
写法

-> (块变量) {处理}

2.1.3 (main):0 > lam2 = ->(n) { return n ** 3 }
=> #<Proc:0x007fd4504d8d30@(pry):10 (lambda)>
2.1.3 (main):0 > lam2.call(2)
=> 8
 
almost 7 years ago

[初稿]

page 110

与这些人尽可能保持密切接触的唯一方法就是成为他们中的一员。
我要做的是弄明白成为他们中的一员意味着什么,然后努力去做

page 135

选择市场。一定要谨慎地挑选你要关注的技术商业领域。如何权衡风险和收益?供需关系又会如何影响你的决定?
投资。你的知识和技术是这件商品的基础。要在这两方面合理地投资,这是市场化的重要前提。只知道如何在理论上使用VB或者Java已经远远不够了。那么在新的经济环境下,又有哪些技术是你应该具备的呢?
执行。单纯依赖技术出色的员工,并不能给公司带来利益。员工必须要有产出。那作为员工,你又如何在不把一切搞得一团糟的前提下,更好地产出呢?你又怎么能知道自己是不是在为公司创造最有利的价值呢?
市场!即使是市面上有史以来最好的产品,若是无人知晓,又怎么会有人来购买呢?你怎么做才能在公司和行业中得到认可,但又不必阿谀奉承呢?

page 190

你会花多少精力来思考此产品的消费者是谁?在产品进入生产流程之前,你又会用多少时间来弄明白这个产品到底是什么?我相信你肯定会仔仔细细地考虑其中的每个小细节,然后亲自作出决定。

page 276

发现市场上的不平衡。

page 587

要深入学习的技术。讲课是最好的学习方法。

page 689

我永远不会用自己的技术来定位自己,而是用我已经做过的和我将来做的事情来定位自己技术只是一种做事的方法

page 740

与客户的互动是非常重要的,清楚地了解客户的要求比似懂非懂然后自己填写细节要好得多。

不要要求别人来教你,要自己主动学习。

page 752

“提示”时间-在你的工具箱挑选一种非常重要却经常被忽视的工具。可能是你的版本控制系统,可能是一个你广泛使用却只知皮毛的库,也可能是你用来编程的编辑器。

选定了工具后,每天花一点时间学习这项工具的新知识,帮组你提高工作效率,或者能让你更好地掌控开发环境。

有了新的诀窍,你就可能利用一系列“如何和为什么”的问题来深入研究它的核心了。

page 835

做一名良师。
要想真正学点东西,可以试试向别人传授这些知识。清楚知道自己是否对某一知识真正理解的最好的办法就是把你的理解讲给别人听。

想要弄明白自己是不是真正懂得某一知识,那就把它讲给其他人听。

page 910

即兴创作

即兴创作就是在某种结构或者限制的基础上创造新的东西。

page 926

Code Kata - 一系列的很小却深具启发性的练习,被称之为 Code Kata。程序员可以使用他们选择的编程语言来做这些练习。每一个 Kata 都针对某一特定技术或者思考过程,这样程序员的思维就可以更加灵活。

http://codekata.pragprog.com

page 962

六西格玛方法强调测量、对过程的分析以及生产效率和用户的满意度。六西格玛严谨的系统方法直接适用于程序员的日常工作。

http://www.isixsigma.com

page 974

从现有程序中得到领悟。

研究大师的作品是成为大师的一个重要步骤。

page 1093

你为之工作的公司是要盈利的,你的工作就是帮助这个机构实现这个目标。

page 1125

随时记得问问自己“现在我们能做些什么?”

page 1172

在与系统相关的工作中,他都会精心做好准备以达到最好的工作效果。

他的目标是,每天都要有一些出色的工作报告给经理,这包括他关于如何改善这个部门的一些想法和已经做过的工作。

每天都有可汇报的成绩。

http://www.semanticnoise.com

制定目标 (每日、每周或者无论什么你能做到的) 并且记录下可以彻底改变你工作中表现的工作内容。

 shell

page 1199

我们需要与公司的目标一致,也就是说,努力确保自己能够对公司的业绩有影响力,这么说完全没错。

我们需要一个更加独立的视角,把整片大海的海水分解成一个一个可以煮沸的小水坑。

好经理的职责应该是为团队设定优先级,确保团队具备完成工作的一切需要,保证团队保持干劲和工作效率,并促使团队最终顺利完成工作。

page 1221

要有雄心,但不必路人皆知。

page 1355

永远不要高枕无忧,他一遍又一遍地重复这句话。他说自己每天早上起来都会提醒自己要清楚地知道说不定哪天自己就被公司解雇了。他会说,可能就是今天。

人一旦傲慢,就会产生盲点。

page 1603

当你能够在碰到问题的时候理智地分析引起惊慌的原因,惊慌就会黯然隐退,最终消失。

page 1635

你应该与上司讨论你的计划。当你至少完成一轮计划之后,再开始讨论。重点是,在你的领导要求和你讨论计划之前,主动与他们交流。每周收到一封员工上周工作结果汇报和下周工作计划的邮件,不会引起经理的反感。经理们正希望员工能够主动发给他们这样的邮件。

page 1826

人们会通过你的写作能力对你做出初步评价,也可以依此深刻理解你的思维活动。如果你无法用母语清楚地表达自己的想法,让别人明白,又怎么可能编程语言来清楚地表达出来呢?组织观点,带领读者思考并最终做出合乎逻辑的推断,这种能力与创作出清晰的设计和系统实施,并让功能维护者理解的能力是一样的。

你自己就是你要解释的内容。

page 1883

了解你的同事。

page 1898

商业本身关注的是结果,经营者关注的也是结果。所以,如果不使用行业语言来推销你的成就就是起不到作用的。

作为软件开发师,要把你所完成的工作放在你所服务的行业的框架里。你确实做了某些工作,但是这工作是什么?这项工作有什么用?怎样才能证明你所谓的成绩不是在浪费公司的时间呢?

page 1910

如果只把自己局限在现在工作的公司里,就会严重限制你形成新的人际关系网的机会。

第一步就是阅读网络日志。
第二步开始撰写自己的网络日志。
网络日志是训练场。要抱着为你最爱的杂志写专栏文章的态度,撰写网络日志。随着你写作技巧的增长,你也会越来越有自信。

page 2165

当心!成功使人骄傲,骄傲会使人自满。

page 2206

我们过于关注事件的结果,却忘记了全局。
如果说这才是你工作生活的核心-真正的工作-那么你已经达到目的地了。
大多数人都没有认识到过程就是终点。

page 2291

开发员们,要自我反省。
别让过时悄悄地降临到你的身上,就像你的裤子突然间就变得紧身了一样。

page 2495

一旦我感到满足的时候,我知道是该尝试点新的东西了。

 
about 7 years ago

撤销最近一个commit

    git reset --hard HEAD^

撤销指定的commit

    git reset --hard 3628164

查看撤销记录

    git reflog  
 
over 7 years ago

Linux系列的系统中的常用命令top,用来查看系统CPU,Memory情况,下面记录几种常用的方式。

使用下面命令进入

top
s - 改变画面更新频率
N - 以 PID 的大小的顺序排列表示进程列表
P - 以 CPU 占用率大小的顺序排列进程列表
M - 以内存占用率大小的顺序排列进程列表
h - 显示帮助
n - 设置在进程列表所显示进程的数量
q - 退出 top
 
over 7 years ago

使用mina+puma来部署rails项目,在最开始的时候本来想使用Capistrano+unicorn,不过在看了一些ruby-china上的帖子说Cap+unicorn很复杂和占内存,所以就选了比较新且较为简单的组合。不过刚开始弄的时侯还是走了些弯路。

gem的添加

gem 'mina'
gem 'mina-puma', :require => false  # puma运行脚本
gem 'puma' # > 2.6

配置mina

初始化部署文件

mina init # 生成 config/deploy.rb

mina的配置文件

require 'mina/bundler'
require 'mina/rails'
require 'mina/git'
require 'mina/rvm'    # for rvm support. (http://rvm.io)
require 'mina/puma'

set :domain, 'domain.com'
set :deploy_to, '/path/www/project_name'
set :repository, ''
set :branch, 'master'

set :shared_paths, ['log'] # 这个网上很多都加了database.yml这类数据库文件路径,不过我实际用内容为空,就删了
set :stage, 'production'

task :environment do
  invoke :'rvm:use[ruby-2.0.0-p353@rails]'
end

task :setup => :environment do
  queue! %[mkdir -p "#{deploy_to}/shared/log"]
  queue! %[chmod g+rx,u+rwx "#{deploy_to}/shared/log"]

  queue! %[mkdir -p "#{deploy_to}/shared/config"]
  queue! %[chmod g+rx,u+rwx "#{deploy_to}/shared/config"]

  queue! %[mkdir -p "#{deploy_to}/shared/tmp/pids"]
  queue! %[chmod g+rx,u+rwx "#{deploy_to}/shared/tmp"]
  queue! %[chmod g+rx,u+rwx "#{deploy_to}/shared/tmp/pids"]

  queue! %[mkdir -p "#{deploy_to}/shared/tmp/sockets"]
  queue! %[chmod g+rx,u+rwx "#{deploy_to}/shared/tmp/sockets"]
end

desc "Deploys the current version to the server."
task :deploy => :environment do
  deploy do
    # Put things that will set up an empty directory into a fully set-up
    # instance of your project.
    invoke :'git:clone'
    invoke :'deploy:link_shared_paths'
    invoke :'bundle:install'
    invoke :'rake:db:migrate' # 如果是mongoid的话,可以注释掉
    invoke :'rails:assets_precompile'

    to :launch do
      queue "touch #{deploy_to}/tmp/restart.txt"
    end
  end
end

配置puma

puma.rb配置文件

#!/usr/bin/env puma

environment "production"
basedir = "/path/www/project_name"
daemonize true
threads 2,16

bind  "unix:///path/www/project_name/shared/tmp/sockets/puma.sock"
pidfile  "#{basedir}/current/tmp/puma/pid"
state_path "#{basedir}/current/tmp/puma/state"
preload_app!
activate_control_app

puma的运行脚本

https://github.com/puma/puma/tree/master/tools/jungle
具体安装看 upstart和init.d 的文件夹里的README.md

Nginx的配置可以参考unicorn+nginx的配置

upstream project_name_domain {
  server unix:/tmp/project_name.unicorn.sock;
}

server {
  listen       80;
  server_name  domain.com;
  root /path/www/project_name/public;

  client_max_body_size 30m;
  access_log  /data/logs/host.access.log; # 记得自己创建文件夹

  location ~ ^/assets/ {
    expires 1y;
    add_header Cache-Control public;
    add_header Last-Modified "";
    add_header ETag "";
    break;
  }

  try_files $uri/index.html $uri @unicorn;

  location @unicorn {
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $http_host;
    proxy_redirect off;
    proxy_pass http://project_name_domain;
  }

  error_page   500 502 503 504  /50x.html;
  location = /50x.html {
    root   html;
  }

}

部署完后运行puma

mina puma:phased_restart  # Restart puma (with zero-downtime)
mina puma:restart         # Restart puma
mina puma:start           # Start puma
mina puma:stop            # Stop puma

https://github.com/sandelius/mina-puma

 
over 7 years ago

由于我们之间并不像公司的模式一样能面对面的沟通,而且项目的规划也十分模糊,各自都是根据自己的即兴来做,这样导致我们的项目到现在基本还处于开始状态。
所以我觉得今年我们应该认真的规划一下项目,对项目希望达到一个什么样的远景,和项目每个开发阶段的规划,以及团队之间如何高效的协作。

项目远景

我们之前想的是做一款阅读舒适,操作简单的小说APP。

开发流程

我觉得我们可以每周抽出部分时间来做我们这个项目,比如:每周二,四晚上和周六白天,三个人同时在线一起做写代码,最好保证实时沟通,这样有问题也可以一起讨论。

每周开始都各自计划这周自己要开发什么功能,不用太多,保证这周能完成。最好是两周左右能开发出一个小功能,并且能上线使用,保证开发周期。

团队沟通

我觉得我们应该每周有一个特定时间段,这个时间段讨论这周的开发计划(当然最好事先自己写写),各自可以说说这周开发的功能。

每一两个月我们应该一起总结这段时间团队协作,开发中的问题,有什么地方可以改进,和什么地方做的比较好的。

 
about 8 years ago

Gem dependent

rails4的基本gem, 主要就是rails的一些标配gem也升级到4版本。如果是1.9@rails3的旧项目,使用其他gem的话可以去github上看有没有支持rails4版本的更新,目前常用的gem都已经升级支持rails4或者已有beta版了,目前gem的dependent基本能很好的解决了。

gem 'rails', '4.0.0'
gem 'sass-rails', '~> 4.0.0'
gem 'coffee-rails', '~> 4.0.0'
gem 'uglifter', '~> 2.1.1'
gem 'jquery-rails'

升级时帮助向后兼容的gem,譬如你还想在项目中使用字段白名单,就可以使用protected_attributes来做兼容。不过建议只在升级时使用,等到其他部分的升级完成后,专门来做这一块的升级。

gem 'protected_attributes'
gem 'rails-observers'
gem 'actionpack-page_caching'
gem 'actionpack-action_caching'

Config

配置方面可以根据rails启动时的提示,查看那些配置具有删除或者更改。

config.eager_load
# development
config.eager_load = false
# production
config.eager_load = true
# test
config.eager_load = false
相关删除的配置
# development 
active_record.auto_explain_threshold_in_seconds

# application
whitelist_attributes
secret_token
# config/initializers/secret_token.rb
Myapp::Application.config.secret_token = 'existing secret token'
Myapp::Application.config.secret_key_base = 'new secret key base'

Model

  1. 删除了白名单 attr_accessibleattr_protected 设置。

Resource

railscasts的upgrade rails4,可以减少走弯路
http://railscasts.com/episodes/415-upgrading-to-rails-4
一个rails4的项目,可以参考一下
https://github.com/RailsApps/learn-rails
upgrade rails4的guide
http://edgeguides.rubyonrails.org/upgrading_ruby_on_rails.html

待补充
Update: 2013-9-13
Update: 2013-9-15