研究了Google I/O 2014 发布 Material Design设计,人性化的风格,丰富的色彩,使人机交互更完美。中文学习地址http://wiki.jikexueyuan.com/project/material-design/(这个好像是极客学院翻译的),当然如果你的引文OK的话,也可以去看官方英文文档http://www.google.com/design/spec/material-design/。
月度归档: 2020 年 5 月
Android获取SD卡路径及SDCard内存的方法
Android获取SD卡路径及SDCard内存的方法。
代码如下:
1 2 3 4 5 6 7 8 9 10 |
public String getSDPath(){ File sdDir = null; boolean sdCardExist = Environment.getExternalStorageState() .equals(android.os.Environment.MEDIA_MOUNTED);//判断sd卡是否存在 if(sdCardExist) { sdDir = Environment.getExternalStorageDirectory();//获取跟目录 } return sdDir.toString(); } |
然后:在后面加上斜杠,在加上文件名
如下:
1 |
String fileName = getSDPath() +"/" + name;//以name存在目录中 |
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 |
1、讲述 Environment 类 Environment 是一个提供访问环境变量的类。 Environment 包含常量: MEDIA_BAD_REMOVAL 解释:返回getExternalStorageState() ,表明SDCard 被卸载前己被移除 MEDIA_CHECKING 解释:返回getExternalStorageState() ,表明对象正在磁盘检查。 MEDIA_MOUNTED 解释:返回getExternalStorageState() ,表明对象是否存在并具有读/写权限 MEDIA_MOUNTED_READ_ONLY 解释:返回getExternalStorageState() ,表明对象权限为只读 MEDIA_NOFS 解释:返回getExternalStorageState() ,表明对象为空白或正在使用不受支持的文件系统。 MEDIA_REMOVED 解释:返回getExternalStorageState() ,如果不存在 SDCard 返回 MEDIA_SHARED 解释:返回getExternalStorageState() ,如果 SDCard 未安装 ,并通过 USB 大容量存储共享 返回 MEDIA_UNMOUNTABLE 解释:返回getExternalStorageState() ,返回 SDCard 不可被安装 如果 SDCard 是存在但不可以被安装 MEDIA_UNMOUNTED 解释:返回getExternalStorageState() ,返回 SDCard 已卸掉如果 SDCard 是存在但是没有被安装 Environment 常用方法: 方法:getDataDirectory() 解释:返回 File ,获取 Android 数据目录。 方法:getDownloadCacheDirectory() 解释:返回 File ,获取 Android 下载/缓存内容目录。 方法:getExternalStorageDirectory() 解释:返回 File ,获取外部存储目录即 SDCard 方法:getExternalStoragePublicDirectory(String type) 解释:返回 File ,取一个高端的公用的外部存储器目录来摆放某些类型的文件 方法:getExternalStorageState() 解释:返回 File ,获取外部存储设备的当前状态 方法:getRootDirectory() 解释:返回 File ,获取 Android 的根目录 2、讲述 StatFs 类 StatFs 一个模拟linux的df命令的一个类,获得SD卡和手机内存的使用情况 StatFs 常用方法: getAvailableBlocks() 解释:返回 Int ,获取当前可用的存储空间 getBlockCount() 解释:返回 Int ,获取该区域可用的文件系统数 getBlockSize() 解释:返回 Int ,大小,以字节为单位,一个文件系统 getFreeBlocks() 解释:返回 Int ,该块区域剩余的空间 restat(String path) 解释:执行一个由该对象所引用的文件系统 3、完整例子读取 SDCard 内存 存储卡在 Android 手机上是可以随时插拔的,每次的动作都对引起操作系统进行 ACTION_BROADCAST,本例子将使用上面学到的方法,计算出 SDCard 的剩余容量和总容量。代码如下: |
代码如下:
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 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 |
package com.terry; import java.io.File; import java.text.DecimalFormat; import android.R.integer; import android.app.Activity; import android.os.Bundle; import android.os.Environment; import android.os.StatFs; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.ProgressBar; import android.widget.TextView; import android.widget.Toast; public class getStorageActivity extends Activity { private Button myButton; /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); findView(); viewHolder.myButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View arg0) { // TODO Auto-generated method stub getSize(); } }); } void findView(){ viewHolder.myButton=(Button)findViewById(R.id.Button01); viewHolder.myBar=(ProgressBar)findViewById(R.id.myProgressBar); viewHolder.myTextView=(TextView)findViewById(R.id.myTextView); } void getSize(){ viewHolder.myTextView.setText(""); viewHolder.myBar.setProgress(0); //判断是否有插入存储卡 if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)){ File path =Environment.getExternalStorageDirectory(); //取得sdcard文件路径 StatFs statfs=new StatFs(path.getPath()); //获取block的SIZE long blocSize=statfs.getBlockSize(); //获取BLOCK数量 long totalBlocks=statfs.getBlockCount(); //己使用的Block的数量 long availaBlock=statfs.getAvailableBlocks(); String[] total=filesize(totalBlocks*blocSize); String[] availale=filesize(availaBlock*blocSize); //设置进度条的最大值 int maxValue = Integer.parseInt(availale[0]) * viewHolder.myBar.getMax() / Integer.parseInt(total[0]); viewHolder.myBar.setProgress(maxValue); String Text="总共:" + total[0]+total[1]+"/n" + "可用:"+availale[0] + availale[1]; viewHolder.myTextView.setText(Text); } else if(Environment.getExternalStorageState().equals(Environment.MEDIA_REMOVED)){ Toast.makeText(getStorageActivity.this, "没有sdCard",1000).show(); } } //返回数组,下标1代表大小,下标2代表单位 KB/MB String[] filesize(long size){ String str=""; if(size>=1024){ str="KB"; size/=1024; if(size>=1024){ str="MB"; size/=1024; } } DecimalFormat formatter=new DecimalFormat(); formatter.setGroupingSize(3); String result[] =new String[2]; result[0]=formatter.format(size); result[1]=str; return result; } } |
参考链接
Android 获取View在屏幕中的位置
Android里面提供了一些方法可以获取View在屏幕中的位置。
getLocationOnScreen,计算该视图在全局坐标系中的x,y值,获取在当前屏幕内的绝对坐标(该值从屏幕顶端算起,包括了通知栏高度)。
getLocationInWindow,计算该视图在它所在的widnow的坐标x,y值。
getLeft, getTop, getBottom, getRight, 这一组是获取相对在它父亲布局里的坐标。
注意:如果在Activity的OnCreate()事件输出那些参数,是全为0,要等UI控件都加载完了才能获取到这些数据。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
button2.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { int[] location = new int[2]; v.getLocationOnScreen(location); x = location[0]; y = location[1]; Log.d("test", "Screenx--->" + x + " " + "Screeny--->" + y); v.getLocationInWindow(location); x = location[0]; y = location[1]; Log.d("test", "Window--->" + x + " " + "Window--->" + y); Log.d("test", "left:" + v.getLeft()); Log.d("test", "right:" + v.getRight()); Log.d("test", "Top:" + v.getTop()); Log.d("test", "Bottom:" + v.getBottom()); Log.d("test", "Width:"+v.getWidth()); Log.d("test", "Height:"+v.getHeight()); } |
getDimension()、getDimensionPixelSize()和getDimensionPixelOffset()的区别
今天写代码的时候,需要从资源文件中读取一个长度值,用来设置控件的宽高,误以为getDimension()返回的就是资源里定义的dp值,后来发现不是我理解的那样。下面介绍一下getDimension()、getDimensionPixelSize()和getDimensionPixelOffset()的区别;
先看我在资源中的定义:
<dimen name="guide_first_cover_image_width">171dp</dimen>
然后打印出它们的值看看
QLog.i(TAG,String.format("getDimension: %f",mContext.getResources().getDimension(R.dimen.guide_first_cover_image_width)) );
QLog.i(TAG,String.format("getDimensionPixelSize: %d",mContext.getResources().getDimensionPixelSize(R.dimen.guide_first_cover_image_width)));
QLog.i(TAG,String.format("getDimensionPixelOffset:%d",mContext.getResources().getDimensionPixelOffset(R.dimen.guide_first_cover_image_width)));
结果如下:
getDimension: 256.500000
getDimensionPixelSize: 257
getDimensionPixelOffset:256
我的模拟器是480*800的,屏幕密度是1.5,从打印结果就可以推知,getDimension()、getDimensionPixelSize()和getDimenPixelOffset()的结果值都是将资源文件中定义的dip值乘以屏幕密度,即171*1.5=256.5,只是getDimension()返回的是float,其余两个返回的是int, 其中getDimensionPixelSize()返回的是实际数值的四舍五入,而getDimensionPixelOffset返回的是实际数值去掉后面的小数点; 再跟踪代码查看这三个函数的具体实现,可以了解得更具体。总然而之,这三个函数返回的都是dip值乘以屏幕密度,如果你在资源文件中定义的长度单位不是dip,而是px的话,程序会直接抛出异常。
参考链接
getDimension()、getDimensionPixelSize()和getDimensionPixelOffset()的区别
Android Scroller实现View弹性滑动完全解析
说起View的滑动效果,实现的方法有多种,例如使用动画,或者通过改变View的布局参数,其实除了这两种外,在Android中View已经为我们提供了scrollTo()和scrollBy()方法来实现滑动效果,这两个方法也是我们接下来要重点讨论的...
但是呢,这两个方法实现的滑动效果都是瞬时完成的,并不能带来良好的体验哦,所以此时就需要我们的重量级嘉宾Scroller登场了,通过Scroller可以实现View在一定时间间隔内的弹性滑动,以带来更好的视觉体验,这么说可能有点抽象,举个例子,当我们使用ViewPager时,手指滑动一段距离松开后ViewPager会自动的滑动一段距离后停止,这个效果就是通过Scroller实现的,这样说应该能更好的理解Scroller的用途了。
本着从理论到实践的原则,我们来一步步的揭秘Scroller...
1. 你应该知道的scrollTo()和scrollBy()
首先看一下这两个方法的源码:
1 2 3 4 5 6 7 8 9 10 |
/** * Move the scrolled position of your view. This will cause a call to * {@link #onScrollChanged(int, int, int, int)} and the view will be * invalidated. * @param x the amount of pixels to scroll by horizontally * @param y the amount of pixels to scroll by vertically */ public void scrollBy(int x, int y) { scrollTo(mScrollX + x, mScrollY + y); } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
/** * Set the scrolled position of your view. This will cause a call to * {@link #onScrollChanged(int, int, int, int)} and the view will be * invalidated. * @param x the x position to scroll to * @param y the y position to scroll to */ public void scrollTo(int x, int y) { if (mScrollX != x || mScrollY != y) { int oldX = mScrollX; int oldY = mScrollY; mScrollX = x; mScrollY = y; invalidateParentCaches(); onScrollChanged(mScrollX, mScrollY, oldX, oldY); if (!awakenScrollBars()) { postInvalidateOnAnimation(); } } } |
两个方法第一个参数x表示相对于当前位置横向移动的距离,正值向左移动,负值向右移动。第二个参数y表示相对于当前位置纵向移动的距离,正值向上移动,负值向下移动,单位都是像素。
不同的是,scrollTo()方法让View相对于初始的位置滚动某段距离,scrollBy()方法则是让View相对于当前的位置滚动某段距离。同时可以发现scrollBy()是通过scrollTo()方法实现的。
上边我们说过,两个方法第一个参数x表示相对于当前位置横向移动的距离,正值向左移动,负值向右移动。这是为什么呢???看下scrollTo()方法中的mScrollX = x;
一行,我们传入的x值被赋值给了mScrollX ,mScroller又是什么东东呢?其实View有一个getScrollX(),继续走进源码的世界:
1 2 3 |
public final int getScrollX() { return mScrollX; } |
可以看到通过getScrollX()方法可以返回mScrollX,也就是View在 水平方向移动的距离,其实关于mScrollX的值有个变化规律:mScrollX等于View左边缘和View内容左边缘的水平方向x坐标的差值,同样的道理mScrollY 等于View上边缘和View内容上边缘的垂直方向y坐标的差值。
通过scrollTo()或scrollBy()实现View的移动,只是View的内容的移动,View本身的位置并不会发生改变。此时的View可以理解为一个ViewGroup,而内容可以理解成ViewGroup中的子View,假设View的坐标原点为(0, 0),如果View内容从左向右移动,则View的内容左边缘x值将大于0,而View的左边缘x值始终为0,所以View左边缘和View内容左边缘的水平方向x坐标的差值小于0,也就是mScrollX的值小于0,这也就解释了为什么当scrollTo()或scrollBy()的x参数为负值,看起来View是向右移动的,这样也就可以理解x参数为正值的情况了,同理纵向上的y参数值也是一个道理。
说了这么多scroll相关的方法,也该聊聊我们的Scroller了...
2. Scroller弹性滑动原理
首先看一下Scroller的典型用法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
Scroller mScroller = new Scroller(mContext); private void smoothScroll(int destX, int destY) { int scrollX = getScrollX(); int deltaX = destX - scrollX; mScroller.startScroll(scrollX, 0, deltaX, 0, 500); invalidate(); } @Override public void computeScroll() { super.computeScroll(); if (mScroller.computeScrollOffset()) { scrollTo(mScroller.getCurrX(), mScroller.getCurrY()); invalidate(); } } |
目测startScroll()方法和View的滑动有关,继续看源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
public void startScroll(int startX, int startY, int dx, int dy, int duration) { mMode = SCROLL_MODE; mFinished = false; mDuration = duration; mStartTime = AnimationUtils.currentAnimationTimeMillis(); mStartX = startX; mStartY = startY; mFinalX = startX + dx; mFinalY = startY + dy; mDeltaX = dx; mDeltaY = dy; mDurationReciprocal = 1.0f / (float) mDuration; } |
原来startScroll()方法只是进行相关参数的初始化,其中startX、startY代表滑动的起点,dx、dy代表需要滑动的距离,duration代表整个滑动需要的时间。骗子...这里并没有View滑动的相关逻辑。那么View如何通过Scroller实现弹性滑动呢?其实是通过startScroll()下面的invalidate();
方法,略抽象,可能我们更多的知道invalidate()方法能够使得View重绘,其实奥秘就在这里,通过使View重绘,会间接的执行computeScroll()方法,继续看源码:
1 2 |
public void computeScroll() { } |
原来是一个空方法,所以需要我们自行实现,so sad...
而在computeScroll()中,我们首先通过computeScrollOffset()方法判断滑动是否结束,这个方法如何工作呢?继续看源码:
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 |
public boolean computeScrollOffset() { if (mFinished) { return false; } int timePassed = (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime); if (timePassed < mDuration) { switch (mMode) { case SCROLL_MODE: final float x = mInterpolator.getInterpolation(timePassed * mDurationReciprocal); mCurrX = mStartX + Math.round(x * mDeltaX); mCurrY = mStartY + Math.round(x * mDeltaY); break; case FLING_MODE: final float t = (float) timePassed / mDuration; final int index = (int) (NB_SAMPLES * t); float distanceCoef = 1.f; float velocityCoef = 0.f; if (index < NB_SAMPLES) { final float t_inf = (float) index / NB_SAMPLES; final float t_sup = (float) (index + 1) / NB_SAMPLES; final float d_inf = SPLINE_POSITION[index]; final float d_sup = SPLINE_POSITION[index + 1]; velocityCoef = (d_sup - d_inf) / (t_sup - t_inf); distanceCoef = d_inf + (t - t_inf) * velocityCoef; } mCurrVelocity = velocityCoef * mDistance / mDuration * 1000.0f; mCurrX = mStartX + Math.round(distanceCoef * (mFinalX - mStartX)); // Pin to mMinX <= mCurrX <= mMaxX mCurrX = Math.min(mCurrX, mMaxX); mCurrX = Math.max(mCurrX, mMinX); mCurrY = mStartY + Math.round(distanceCoef * (mFinalY - mStartY)); // Pin to mMinY <= mCurrY <= mMaxY mCurrY = Math.min(mCurrY, mMaxY); mCurrY = Math.max(mCurrY, mMinY); if (mCurrX == mFinalX && mCurrY == mFinalY) { mFinished = true; } break; } } else { mCurrX = mFinalX; mCurrY = mFinalY; mFinished = true; } return true; } |
看第一个if条件,如果滑动动画已经finish,则返回fasle,继续看第二个if条件,如果执行滑动动画已经花费的时间小于整个滑动动画需要的时间,则计算出mCurrX和mCurrY的值,也就是当前View内容左边缘的x、y坐标的值,同时返回true,所以,如果滑动的动画未结束则返回true否则返回false。当返回true时,则调用scrollTo(mScroller.getCurrX(), mScroller.getCurrY())
进行View的滑动,其中参数mScroller.getCurrX()、mScroller.getCurrY()得到的就是上边的mCurrX和mCurrY,scrollTo之后继续执行invalidate()方法,此时又会导致View重绘,又一次执行computeScroll()方法...,这样的反复执行,直到computeScrollOffset()方法返回flase,即完成View的滑动。
通过上边的分析可以看出,仅仅通过Scroller,并不能实现View的滑动效果,同时需要配合View的invalidate()、computeScroll()、scrollTo()方法才可以完成。
说了这么多的理论,也该实践一下了...
Java中double/float四舍五入取整(ceil/floor/round)
1 2 3 4 5 6 7 8 |
public class mathExample { public static void main(String[] args) { System.out.println("向上取整:" + (int) Math.ceil(96.1));// 97 (去掉小数凑整:不管小数是多少,都进一) System.out.println("向下取整" + (int) Math.floor(96.8));// 96 (去掉小数凑整:不论小数是多少,都不进位) System.out.println("四舍五入取整:" + Math.round(96.1));// 96 (这个好理解,不解释) System.out.println("四舍五入取整:" + Math.round(96.8));// 97 } } |
执行测试:
1 2 3 |
$ javac mathExample.java $ java mathExample |
参考链接
Android 控件 RecyclerView
概述
RecyclerView是什么
从Android 5.0开始,谷歌公司推出了一个用于大量数据展示的新控件RecylerView,可以用来代替传统的ListView,更加强大和灵活。RecyclerView的官方定义如下:
A flexible view for providing a limited window into a large data set.
从定义可以看出,flexible(可扩展性)是RecyclerView的特点。
RecyclerView是support-v7包中的新组件,是一个强大的滑动组件,与经典的ListView相比,同样拥有item回收复用的功能,这一点从它的名字Recyclerview即回收view也可以看出。
RecyclerView的优点
RecyclerView并不会完全替代ListView(这点从ListView没有被标记为@Deprecated可以看出),两者的使用场景不一样。但是RecyclerView的出现会让很多开源项目被废弃,例如横向滚动的ListView, 横向滚动的GridView, 瀑布流控件,因为RecyclerView能够实现所有这些功能。
比如:有一个需求是屏幕竖着的时候的显示形式是ListView,屏幕横着的时候的显示形式是2列的GridView,此时如果用RecyclerView,则通过设置LayoutManager一行代码实现替换。
RecylerView相对于ListView的优点罗列如下:
- RecyclerView封装了viewholder的回收复用,也就是说RecyclerView标准化了ViewHolder,编写Adapter面向的是ViewHolder而不再是View了,复用的逻辑被封装了,写起来更加简单。
直接省去了listview中convertView.setTag(holder)和convertView.getTag()这些繁琐的步骤。 - 提供了一种插拔式的体验,高度的解耦,异常的灵活,针对一个Item的显示RecyclerView专门抽取出了相应的类,来控制Item的显示,使其的扩展性非常强。
- 设置布局管理器以控制Item的布局方式,横向、竖向以及瀑布流方式
例如:你想控制横向或者纵向滑动列表效果可以通过LinearLayoutManager这个类来进行控制(与GridView效果对应的是GridLayoutManager,与瀑布流对应的还StaggeredGridLayoutManager等)。也就是说RecyclerView不再拘泥于ListView的线性展示方式,它也可以实现GridView的效果等多种效果。 - 可设置Item的间隔样式(可绘制)
通过继承RecyclerView的ItemDecoration这个类,然后针对自己的业务需求去书写代码。 - 可以控制Item增删的动画,可以通过ItemAnimator这个类进行控制,当然针对增删的动画,RecyclerView有其自己默认的实现。
但是关于Item的点击和长按事件,需要用户自己去实现。
todo-mvvm-databinding源码分析
MVVM框架 todo-mvvm-databinding 项目例子。
MVP框架 todo-mvp 项目例子。
目的
分析和学习官方mvvm框架的设计模式和数据绑定在其中的具体用法,制作一套符合当前公司业务场景的mvvm框架。其中也分析一下数据源在项目中的设计以及框架中单元测试的实施。
设计模式
MVVM框架中的ViewModel
相比MVP框架中的Presenter
起着类似的作用。两种框架结构的不同之处在于View分别与ViewModel
或Presenter
进行通信:
- 当MVVM框架中数据发生变化时会影响
ViewModel
的改变,View
会自动更新库或框架。你不能直接通过ViewModel
来修改View
。 - 在这个项目中,你可以使用布局文件将
ViewModel
中的变量绑定到特定的UI元素上(如TextView
或ImageView
)。数据绑定可以确保View和ViewModel保持双向同步,如下图所示。
SQL中MAX函数与Group By获取同一字段重复的数据最大的一条
SQL中MAX函数与Group By一起使用,获取同一字段重复的数据只取记录最大的一条,如下:
1 2 3 4 5 6 7 8 9 10 11 12 |
mysql> select * from test; +----+-------+------+-------+ | id | name | age | clz | +----+-------+------+-------+ | 1 | qiu | 22 | 1 | | 2 | liu | 42 | 1 | | 4 | zheng | 20 | 2 | | 3 | qian | 20 | 2 | | 0 | wang | 11 | 3 | | 6 | li | 33 | 3 | +----+-------+------+-------+ 6 rows in set (0.00 sec) |
如果想找到每个clz里面的最大的age,则需要使用Group By和Max。
如下的SQL语句,则输出结果有错误:
1 2 3 4 5 6 7 8 9 |
mysql> select id,name,max(age),clz from test group by clz; +----+-------+----------+-------+ | id | name | max(age) | clz | +----+-------+----------+-------+ | 1 | qiu | 42 | 1 | | 4 | zheng | 20 | 2 | | 0 | wang | 33 | 3 | +----+-------+----------+-------+ 3 rows in set (0.00 sec) |
虽然找到的age是最大的age,但是与之匹配的用户信息却不是真实的信息,而是Group By分组后的第一条记录的基本信息。
如果我使用以下的语句进行查找,则可以返回真实的结果。
1 2 3 4 5 6 7 8 9 10 11 |
mysql> select * from ( -> select * from test order by age desc) as b -> group by clz; +----+-------+------+-------+ | id | name | age | clz | +----+-------+------+-------+ | 2 | liu | 42 | 1 | | 4 | zheng | 20 | 2 | | 6 | li | 33 | 3 | +----+-------+------+-------+ 3 rows in set (0.00 sec) |
参考链接
resize2fs:Memory alloction failed while trying to resize
最近入手一块 12T 的西数红盘,打算安装到 WD MyCloud Gen1 上,从 4TB 版本升级到 12TB 版本,作为 TimeMachine 来用。
使用 拯救死翘翘了的WD MyCloud 方式恢复镜像之后,最后一步执行
1 |
$ resize2fs /dev/sda4 |
结果报告如下错误:
1 2 3 4 5 6 |
$ resize2fs /dev/sda4 resize2fs 1.42.5 (29-Jul-2012) Resizing the filesystem on /dev/sda4 to 2928542715 (4k) blocks. resize2fs: Memory allocation failed while trying to resize /dev/sda4 Please run 'e2fsck -fy /dev/sda4' to fix the filesystem after the aborted resize operation. |
这个原因网上查询了很久,基本上断定是 e2fsprogs 1.42.5 的 BUG,这个问题在 e2fsprogs 1.42.9 版本修复。修复内容参考代码下的 debian/changelog。
关键日志如下:
1 2 3 4 |
e2fsprogs (1.42.9-1) unstable; urgency=low * Fixed a large number of bugs in resize2fs, e2fsck, debugfs, to handle bigalloc and 64-bit file systems. |
编译过程参考 How to successfully build packages for WD My Cloud from source 中对于 e2fsprogs 1.42.13 的编译。