[安卓]手机管家(十一) 外拨电话 & 自定义toast
来源:互联网 发布:睡觉打呼知乎 编辑:程序博客网 时间:2024/04/30 10:19
这里的receiver应该是静态注册吗?没有启动也能监听到,方便后台,但是用户没办法关掉,应该写到监听来电的里面,用户可以选择是否开启,所以应该是动态注册
点击事件写在settingactivity里,settingactivity里注册broadcastreceiver,如果在这注册,就和activity生命周期相同,一旦挂电话,activity销毁掉就不行了,电话来了activity马上就被盖住看不见了,receiver就收不到了
如何解决?应该注册在service里,他的生命周期长,而且他的启动关闭和用户点击与否绑定在一起
showcalllocation里需要一个receiver,写到其中的service里,成为一个内部类,注册到onStartCommand里,随着service而启动
@Overridepublic int onStartCommand(Intent intent, int flags, int startId) {// TODO Auto-generated method stubtm = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);listener = new MyPhoneCallListener(); tm.listen(listener, PhoneStateListener.LISTEN_CALL_STATE); MyOutGoingCallReceiver myOutGoingCallReceiver = new MyOutGoingCallReceiver(); IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(Intent.ACTION_NEW_OUTGOING_CALL); registerReceiver(myOutGoingCallReceiver, intentFilter); System.out.println("ShowCallLocation.onStartCommand()");return super.onStartCommand(intent, flags, startId);}class MyOutGoingCallReceiver extends BroadcastReceiver{@Overridepublic void onReceive(Context context, Intent intent) {// TODO Auto-generated method stub//去做当外拨电话的时候,显示toast提示框String num= getResultData();System.out.println("ShowCallLocation.MyOutGoingCallReceiver.onReceive()"+num);String addr = AdressQueryDao.queryAddr(num);showMyToast(addr);}}
这里无需注册,但是需要权限,没权限可以收到但不能toast
<uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS"/>
进设置电商后。拨打正确的号码能查到,不正确的会显示一个图标
从这里可以看到,接受广播的时候可能会需要权限
现在用户客制化toast背景,仿照之前的自定义组件来做,settingItem2
之前的checkbox换成imageview就好,注意有的系统资源里的图片没有权限用,拷进来生成不了ID
<ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentRight="true" android:layout_centerVertical="true" android:src="@drawable/ic_btn_search_go"/>
相应的activity_setting也要改一下
<com.rjl.mobilephonemanager.ui.SettingItem2 android:id="@+id/settingitem_setToastbg" android:layout_width="fill_parent" android:layout_height="wrap_content" rjl:itemtitle="设置号码提示框的背景"/></LinearLayout>
settingactivity里声明找到
private SettingItem2 settingItem_setToastBg;private SharedPreferences sp;@Overrideprotected void onCreate(Bundle savedInstanceState) {// TODO Auto-generated method stubsuper.onCreate(savedInstanceState);setContentView(R.layout.activity_setting);settingItem_autoupdate = (SettingItem) findViewById(R.id.settingitem_autoupdate);settingItem_showAddress = (SettingItem) findViewById(R.id.settingitem_showAddr);settingItem_setToastBg = (SettingItem2) findViewById(R.id.settingitem_setToastbg);
然后初始化方法
@Overrideprotected void onResume() {// TODO Auto-generated method stubinitAutoUpdateItem( ); initShowAddressItem(); initSetToastBgItem(); System.out.println("SettingActivity.onResume()");super.onResume();}private void initSetToastBgItem() {}
主要是一个onclicklisten
private void initSetToastBgItem() {settingItem_setToastBg.setdescription("默认背景");settingItem_setToastBg.setOnClickListener(new OnClickListener(){@Overridepublic void onClick(View v) {// TODO Auto-generated method stub}});}
注意这是新控件,settingItem2里面需要改初始化函数,填充的是item2,复制过来的时候item,原来涉及到checkbox都可以删了
private void init() {// TODO Auto-generated method stub View v= View.inflate(getContext(), R.layout.setting_item2, this); tv_setting_title = (TextView) v.findViewById(R.id.tv_setting_title); tv_setting_description = (TextView) v.findViewById(R.id.tv_setting_description); }
现在来实现onclicklisten,并看看onclick能不能显示出来
finalString[] items={"半透明","活力橙","卫士蓝","金属灰","苹果绿"};settingItem_setToastBg.setdescription("默认背景");settingItem_setToastBg.setOnClickListener(new OnClickListener(){@Overridepublic void onClick(View v) {// TODO Auto-generated method stubBuilder builder = new Builder(SettingActivity.this);builder.setTitle("请选择自定号码归属地的背景");//不能直接写onclicklisten,同名冲突了,需加上包名builder.setSingleChoiceItems(items, 0, new DialogInterface.OnClickListener() {@Overridepublic void onClick(DialogInterface dialog, int which) {// TODO Auto-generated method stubToast.makeText(getApplicationContext(), items[which], 0).show();}});builder.show();}
让用户点一下即确认,而不是选好之后再一次点确认,返回的时候就已经选好了,不返回还可以接着选,也就是说点击一次后要记住并且让这个dialog取消,builder内部也是实现的dialog
要通过SharedPreferences记住选了哪个
public void onClick(DialogInterface dialog, int which) {// TODO Auto-generated method stubToast.makeText(getApplicationContext(), items[which], 0).show(); Editor editor = sp.edit(); editor.putInt("toastbg", which); editor.commit(); dialog.dismiss();}记住之后出来的时候,就设好了相应的背景,这是回显,在设置description那获取的的应该是保存的数据,并且要转成string,加上Item[]包起来,把对应的位置信息转成名称
但是这么做还不行,由于activity的生命周期,没有进入onresume,所以实际上没有变化
public void onClick(View v) {// TODO Auto-generated method stubBuilder builder = new Builder(SettingActivity.<span style="color:#ff0000;">this</span>);看看这里,其实回到的还是目前的activity,引用的是this,而我们说生命周期的改变,比如resume,是当前activity被别的activity覆盖,这里只是一个dialog控件,最后回到的还是自己的activity。
所以应在dialog响应内加上一个获取用户的选择,
@Overridepublic void onClick(DialogInterface dialog, int which) {// TODO Auto-generated method stub/*Toast.makeText(getApplicationContext(), items[which], 0).show();*/ Editor editor = sp.edit(); editor.putInt("toastbg", which); editor.commit(); <span style="color:#ff0000;">settingItem_setToastBg.setdescription(items[which]);</span> dialog.dismiss();}
还有一个问题,不能记住位置,每次用户进来设置都被默认设置成了一个背景,而不是显示之前选择保存的,在setSingleChoiceItems的第二个参数应该是从sp里获取
builder.setSingleChoiceItems(items, <span style="color:#ff6600;">sp.getInt("toastbg", 0)</span>, new DialogInterface.OnClickListener()
再来设值toast背景变化的实现,showcalllocation里,需要从sp里获取
private void showMyToast(String addr) {/*tv = new TextView(this);tv.setText("中国联通");*///自选背景int[] resid = new int[]{R.drawable.call_locate_white,R.drawable.call_locate_orange, R.drawable.call_locate_blue,R.drawable.call_locate_gray, R.drawable.call_locate_green };LayoutInflater inflate = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE); v = inflate.inflate(R.layout.mytoast_showaddr, null); //从之前存的背景数据库中选择 sp = getSharedPreferences("config", MODE_PRIVATE); v.setBackgroundResource(resid[sp.getInt("toastbg", 0)]); TextView tv = (TextView)v.findViewById(R.id.tv_mytoast_addr); tv.setText(addr);
OK~新功能,锦上添花的的,toast的位置
activity_setting里加上layout
<com.rjl.mobilephonemanager.ui.SettingItem2 android:id="@+id/settingitem_setToastbg" android:layout_width="fill_parent" android:layout_height="wrap_content" rjl:itemtitle="设置号码提示框的背景"/> <com.rjl.mobilephonemanager.ui.SettingItem2 android:id="@+id/settingitem_setToastPosition" android:layout_width="fill_parent" android:layout_height="wrap_content" rjl:itemtitle="设置号码提示框的位置"/>
settingactivity中初始化
private SettingItem2 settingItem_setToastPosition;
settingItem_setToastPosition= (SettingItem2) findViewById(R.id.settingitem_setToastPosition);
initSetToastPostionItem();
private void initSetToastPostionItem() {// TODO Auto-generated method stubsettingItem_setToastPosition.setdescription("单击可进入设置");settingItem_setToastPosition.setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View v) {// TODO Auto-generated method stub}});}
完成响应,实现位置的变化应该是可以拖动,而不是让用户设置位置参数
需要一个新的activity,DragToastActivity
注册,让背景好一点,效果透明什么的
<activity android:name=".DragToastActivity" android:theme="@android:style/Theme.Translucent.NoTitleBar"> </activity>
public class DragToastActivity extends Activity {@Overrideprotected void onCreate(Bundle savedInstanceState) {// TODO Auto-generated method stubsuper.onCreate(savedInstanceState);}}
一个新的layout,后面需要处理,要一个ID
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:textColor="#000000" android:text="按住提示框到任意位置 \n按返回之后立即生效" android:background="@drawable/btn_green_normal"/> <LinearLayout android:id="@+id/ll_dragtoast_toast" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="vertical" android:background="@drawable/call_locate_gray"> </LinearLayout>
我们要从settingactivity跳过来
@Overridepublic void onClick(View v) {// TODO Auto-generated method stubIntent intent = new Intent(SettingActivity.this,DragToastActivity.class);startActivity(intent);}
实现拖动效果,要在DragToastActivity里获得这个linerlayout控件,
同时有一个ontouch事件,并且能记住位置,需要listener
@Overrideprotected void onCreate(Bundle savedInstanceState) {// TODO Auto-generated method stubsuper.onCreate(savedInstanceState);setContentView(R.layout.activity_dragtoast);toast = (LinearLayout) findViewById(R.id.ll_dragtoast_toast);toast.setOnTouchListener(new OnTouchListener(){@Overridepublic boolean onTouch(View v, MotionEvent event) {// TODO Auto-generated method stubreturn false;}});}
在ontouch里识别各种滑动操作,
@Overridepublic boolean onTouch(View v, MotionEvent event) {// TODO Auto-generated method stubswitch(event.getAction()){case MotionEvent.ACTION_DOWN: break; case MotionEvent.ACTION_MOVE: break; case MotionEvent.ACTION_UP:break;default:break;}return false;}
关键的位置识别的小算法
手滑动的坐标变化可以通过touch去记住
另外设计到UI的代码最好都弄一个trace,debug起来比较快
先是获得起始位置,按下去时的位置;然后是移动后的距离,需要计算一下;最后显示出layout的新的位置,这里要用到toast的方法
toast.layout(l, t, r, b);
l是距离窗体左边的
t是上面
r是小空间右边缘距父控件的左边框的距离
b是底部
移动后layout的新位置
左边和上边方便,就是原位置加上算出来的移动距离
右边距就是新的左边距加上宽度
底部就是新的上边距加上高度
注意当移动好,下一次再移动,他的起始位置应该是上一次最后的位置
switch(event.getAction()){//获得起始位置case MotionEvent.ACTION_DOWN: startx = (int) event.getRawX();starty = (int) event.getRawY();System.out.println("DragToastActivity onTouch down():statx / starty"+startx+"/"+starty);break;//获得滑动距离 case MotionEvent.ACTION_MOVE: int endx= (int) event.getRawX(); int endy= (int) event.getRawY(); int dx = endx-startx; int dy = endy-starty; //重新画出layout的位置,用到toast int toast_newleft = toast.getLeft()+dx; int toast_newtop = toast.getTop()+dy; int toast_newrigth = toast_newleft+toast.getWidth(); int toast_newbottom =toast_newtop+toast.getHeight(); toast.layout(toast_newleft, toast_newtop, toast_newrigth, toast_newbottom); //终点位置变成一下次的起始位置 startx= endx; starty =endy;break;
要注意这个控件最后的返回值
move事件必须在down事件发生后才能开始,也就是说用户按下去,没有把手拿开,才能一直move
如果down后什么事也没做,没有move,那么返回一个false,这个事件会给别的控件报move
所以这里返回值应该是true,详情请看另一篇转载而来的博文<[安卓]Android onTouch事件解析>
回显,记住每次设置后停留的位置,保存起来
case MotionEvent.ACTION_UP://每次进来显示的上次停留的位置int toast_last_left = toast.getLeft();int toast_last_top = toast.getTop(); Editor editor =sp.edit();editor.putInt("toast_last_left", toast_last_left);editor.putInt("toast_last_top", toast_last_top);editor.commit(); System.out.println("DragToastActivity.onTouch() up: "+ "toast_last_left/toast_last_top" +toast_last_left+"/"+toast_last_top);break;
每次启动oncreate时显示上次位置
//回显toast的位置:从sp中取出。int left = sp .getInt("toast_last_left", 100);int top = sp .getInt("toast_last_top", 200);System.out.println("DragToastActivity.onCreate() toast left/top"+left+"/"+top);//显示出来toast.layout(left, top, left+toast.getWidth(), top+toast.getHeight());
到这里走一个,发现trace的位置是对的,但进去再退出再进去的时候,位置还是在左上角,而没有保留上次离开时的位置
在oncreate里看看是什么情况
int right=left+toast.getWidth(); int bottom =top+toast.getHeight();System.out .println("DragToastActivity.onCreate() taost rgith/bottom"+right+"/"+bottom); toast.layout(left, top, right, bottom);
发现这个打印出来的right和buttom相等,说明后面没能获取他们的高度和宽度,故而没能实现回显
这与控件的初始化步骤有关,oncreate时要对控件执行下列步骤,而刚刚的代码里只是直接去获取长度宽度,渲染的步骤还没来得及进行,故而获取不到
控件的初始化步骤:渲染
onMesure
onlayout
ondraw
咋toast的源码里,要初始化一些参数
mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE); // We can resolve the Gravity here by using the Locale for getting // the layout direction final Configuration config = mView.getContext().getResources().getConfiguration(); final int gravity = Gravity.getAbsoluteGravity(mGravity, config.getLayoutDirection()); mParams.gravity = gravity; if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.FILL_HORIZONTAL) { mParams.horizontalWeight = 1.0f; } if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.FILL_VERTICAL) { mParams.verticalWeight = 1.0f; } <span style="color:#ff0000;"> mParams.x = mX; mParams.y = mY;</span>
系统会看我们设没设,我们设置了系统就会以我们这个为准
所以一开始我们就设置一个参数,系统就会调用他,显示我们要的位置
关于这个参数,有几点要注意:
1.应该看这个控件的父控件是在什么布局里,我们的这个"setContentView(R.layout.activity_dragtoast);",通过这个activity看到他的布局是在一个LinearLayout里,所以上面必须是指定LinearLayout下的parameter,很多东西都有parameter
2.此处去设置该控件的参数的时候,应该将该控件原本的参数拿出来做修改,或新增一些参数,而不是 new一个新的parameter
3.另外在强转类型的时候,由于导包错误一直无法强转,下面这个包不对
还有两个问题:
1.小控件在实现的时候位置会有一个上下的偏移
2.当控件滑到边缘时超出边线,就变形了(可以判断下,当超出时矫正下,不过这里用限制用户的方法),这里要获取当前的宽度和高度,获取高度时要注意原来有个标题栏的高度要去掉
public class DragToastActivity extends Activity {private LinearLayout toast;private SharedPreferences sp;private int width ; private int height; @Overrideprotected void onCreate(Bundle savedInstanceState) {// TODO Auto-generated method stubsuper.onCreate(savedInstanceState);setContentView(R.layout.activity_dragtoast);sp=getSharedPreferences("config", MODE_PRIVATE);//获取宽度和高度Display display = getWindowManager().getDefaultDisplay();width = display.getWidth();height = display.getHeight();toast = (LinearLayout) findViewById(R.id.ll_dragtoast_toast);//回显toast的位置:从sp中取出。int left = sp .getInt("toast_last_left", 100);int top = sp .getInt("toast_last_top", 200);System.out.println("DragToastActivity.onCreate() toast left/top"+left+"/"+top);/*//显示出来toast.layout(left, top, left+toast.getWidth(), top+toast.getHeight());*///测试问题出在哪,高度和宽度没获取到,都算成了0/*int right =left+toast.getWidth(); int bottom =top+toast.getHeight();System.out .println("DragToastActivity.onCreate() taost rgith/bottom"+right+"/"+bottom); toast.layout(left, top, right, bottom);*/ //此处去设置该控件的参数的时候,应该将该控件原本的参数拿出来做修改,或新增一些参数,而不是 new一个新的parameter //LinearLayout.LayoutParams mParams = new LinearLayout.LayoutParams(p);LinearLayout.LayoutParams mParams = (LayoutParams) toast.getLayoutParams();//使用的规则:应该看这个控件的父控件是在什么布局里,我们的这个"setContentView(R.layout.activity_dragtoast);"//通过这个activity看到他的布局是在一个LinearLayout里,所以上面必须是指定LinearLayout下的parameter,很多东西都有parameter mParams.gravity = Gravity.LEFT|Gravity.TOP; mParams.leftMargin = left; //有一个上下位置的偏移 mParams.topMargin = top-20; //toast的参数由这个mParams设定的 toast.setLayoutParams(mParams);toast.setOnTouchListener(new OnTouchListener(){int startx =0;int starty= 0;@Overridepublic boolean onTouch(View v, MotionEvent event) {// TODO Auto-generated method stubswitch(event.getAction()){//获得起始位置case MotionEvent.ACTION_DOWN: startx = (int) event.getRawX();starty = (int) event.getRawY();System.out.println("DragToastActivity onTouch down():statx / starty"+startx+"/"+starty);break;//获得滑动距离case MotionEvent.ACTION_MOVE: int endx= (int) event.getRawX(); int endy= (int) event.getRawY(); int dx = endx-startx; int dy = endy-starty; //重新画出layout的位置,用到toast int toast_newleft = toast.getLeft()+dx;int toast_newtop = toast.getTop()+dy;int toast_newrigth = toast_newleft+toast.getWidth(); int toast_newbottom =toast_newtop+toast.getHeight(); //不要让用户移动我们的控件的时候,移除屏幕//左边距或者上边距小于0,右边大于宽度,底部边距大于高度,减掉标题栏的高度 if (toast_newleft<0||toast_newtop<0||toast_newrigth>width ||toast_newbottom>height-20 ) {break;} toast.layout(toast_newleft, toast_newtop, toast_newrigth, toast_newbottom); //终点位置变成一下次的起始位置startx= endx; starty =endy;break;case MotionEvent.ACTION_UP://每次进来显示的上次停留的位置int toast_last_left = toast.getLeft();int toast_last_top = toast.getTop(); Editor editor =sp.edit();editor.putInt("toast_last_left", toast_last_left);editor.putInt("toast_last_top", toast_last_top);editor.commit(); System.out.println("DragToastActivity.onTouch() up: "+ "toast_last_left/toast_last_top" +toast_last_left+"/"+toast_last_top);break; default:break;}//确保down后还是这个控件move,false的话有可能让别的控件继续return true;}});}}
现在让这个控件位置信息的改变能够在来电时体现出来
在ShowCallLocation获取存储起来的信息
//获取用户设置toast弹出的位置: int left= sp .getInt("toast_last_left", 200); int top= sp .getInt("toast_last_top", 200); mWM = (WindowManager)getSystemService(Context.WINDOW_SERVICE);final WindowManager.LayoutParams params = new WindowManager.LayoutParams(); params.height = WindowManager.LayoutParams.WRAP_CONTENT; params.width = WindowManager.LayoutParams.WRAP_CONTENT; params.format = PixelFormat.TRANSLUCENT; //根据回显的信息来设置位置 params.gravity = Gravity.LEFT|Gravity.TOP; params.x = left; params.y = top-20; //params.windowAnimations = com.android.internal.R.style.Animation_Toast; params.type = WindowManager.LayoutParams.TYPE_TOAST; params.setTitle("Toast"); params.flags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; mWM.addView(v, params);}
现在实现双击居中功能
<TextView android:id="@+id/tv_mytoast_addr" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:textColor="#000000" android:drawableLeft="@android:drawable/stat_sys_phone_call" android:shadowColor="#BB000000" android:shadowRadius="2.75" android:text="双击居中"/>
toast.setOnClickListener(new OnClickListener(){@Overridepublic void onClick(View v) {// TODO Auto-generated method stub}});
检测用户的双击事件
保留第一次点击的时间,当第二次与第一次间隔小于500ms时视为有效的双击
另外,由于上面的touch设置了一个返回true,这里再加一个click,会发现操作无效,要改为false
toast.setOnClickListener(new OnClickListener(){private long firsttime=0;private long secondtime=0;//firsttime位0是就是第一次点击,点击了有数值不为0了,就是第二次点击@Overridepublic void onClick(View v) {//if(firsttime!=0){secondtime = System.currentTimeMillis();if(secondtime-firsttime < 500){ //出发产生一次双击事件 让控件居中显示 int width_half= width/2; int heigth_haft=height/2; int left =width_half - toast.getWidth()/2; int top = heigth_haft - toast.getHeight()/2; int rigth = left+ toast.getWidth(); int bottom = top+ toast.getHeight(); toast.layout(left, top, rigth, bottom);}firsttime = 0;}else{firsttime = System.currentTimeMillis();}}});
这里还有一个bug,点了第一次后,隔了一会双击了,这个时候操作无效
第一次点击记录一个事件保存起来,隔了一会双击,也就是第二次第三次
第二次的时候会判断下,第一次不为0,所以记录了第二次的时间,但是这两次的间隔大于500,又把第一次置0了,那么第三次的时候获取了个时间,OK~什么也没做
两种方法,可以做一个判断,如果是大于500ms,则把第2次值给第1次
@Overridepublic void onClick(View v) {//if(firsttime!=0){secondtime = System.currentTimeMillis();if(secondtime-firsttime < 500){ //出发产生一次双击事件 让控件居中显示 int width_half= width/2; int heigth_haft=height/2; int left =width_half - toast.getWidth()/2; int top = heigth_haft - toast.getHeight()/2; int rigth = left+ toast.getWidth(); int bottom = top+ toast.getHeight(); toast.layout(left, top, rigth, bottom); firsttime = 0;}else{firsttime = secondtime;}}else{firsttime = System.currentTimeMillis();}}
还有用线程的方法,如果大于500ms,则把第一次的置为0
else{firsttime = System.currentTimeMillis(); /*new Thread(){ public void run() {try {sleep(500);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();} firsttime = 0;};}.start();*/}
- [安卓]手机管家(十一) 外拨电话 & 自定义toast
- [安卓]手机管家(四)自定义属性
- [安卓]手机管家(二十一)杀毒UI及SlidingDrawer(抽屉效果)
- 安卓自定义Toast
- 安卓自定义Toast
- 监听外拨电话
- 外拨电话广播
- [安卓]手机管家(一)splash
- [安卓]手机管家(三)homeActivity
- [安卓]手机管家(十二)通讯卫士
- [安卓]手机管家(十六)进程管理
- [安卓]手机管家(六)防盗之UI及自定义样式
- 外拨电话广播,监听外拨电话
- Android 拦截外拨电话
- 立波锁屏管家:安卓手机锁屏变得简简单单
- [安卓]手机管家(二)splash续(附加签名问题)
- [安卓]手机管家(五)防盗之加密
- [安卓]手机管家(七)防盗之左右划屏
- C++ 常用函数
- Windows驱动学习笔记之二:VS2013集成IDE驱动调试
- quick中的静态布局和动态展示
- WSAAccept()函数使用解析
- 文章标题
- [安卓]手机管家(十一) 外拨电话 & 自定义toast
- mtd驱动框架分析
- 字节对齐
- poj 1651 Multiplication Puzzle(区间dp 矩阵链乘法)
- 时间类型
- mmc驱动框架分析1
- 用户认证授权系统方案思考
- 深入浅出Mybatis-插件原理(OPT)
- 【Android SDK Manager 无法更新】 解决方案