首先创建保存备忘录信息的表:
DbHelper.java
[java] view plaincopy在CODE上查看代码片派生到我的代码片
package com.cjq.androidbwl;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteDatabase.CursorFactory;
import android.database.sqlite.SQLiteOpenHelper;
/**
*
* @author cjianquan
* @since 2014年10月31日
*/
public class DbHelper extends SQLiteOpenHelper {
public DbHelper(Context context, String name, CursorFactory factory,
int version) {
super(context, name, factory, version);
// TODO Auto-generated constructor stub
}
@Override
public void onCreate(SQLiteDatabase db) {
StringBuilder sql = new StringBuilder();
sql.append("create table if not exists tb_bwl(")
.append("id integer primary key autoincrement,")
.append("title varchar(50),")
.append("content varchar(200),")
.append("createDate varchar(10),")
.append("noticeDate varchar(10),")
.append("noticeTime varchar(5) )");
db.execSQL(sql.toString());
}
@Override
public void onUpgrade(SQLiteDatabase arg0, int arg1, int arg2) {
// TODO Auto-generated method stub
}
}
MainActivity.java
[java] view plaincopy在CODE上查看代码片派生到我的代码片
package com.cjq.androidbwl;
import java.util.Calendar;
import android.app.Activity;
import android.app.AlarmManager;
import android.app.Dialog;
import android.app.PendingIntent;
import android.app.TimePickerDialog;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.os.Bundle;
import android.util.Log;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.AdapterContextMenuInfo;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ImageButton;
import android.widget.ListView;
import android.widget.SimpleCursorAdapter;
import android.widget.TextView;
import android.widget.TimePicker;
import android.widget.Toast;
/**
*
* @author cjianquan
* @since 2014年10月31日
*/
public class MainActivity extends Activity {
private AlarmManager alarmManager=null;
Calendar cal=Calendar.getInstance();
final int DIALOG_TIME = 0; //设置对话框id
private DbHelper dbhelper;
private SQLiteDatabase db;
SimpleCursorAdapter adapter = null;
ListView lv;
/* (non-Javadoc)
* @see android.app.Activity#onCreate(android.os.Bundle)
*/
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
alarmManager=(AlarmManager)getSystemService(Context.ALARM_SERVICE);
dbhelper = new DbHelper(this, "db_bwl", null, 1);
db = dbhelper.getReadableDatabase();
Cursor cursor = db.query("tb_bwl", new String[]{"id as _id","title","content","noticeDate","noticeTime"}, null, null, null, null,null);
lv = (ListView)findViewById(R.id.lv_bwlList);
adapter = new SimpleCursorAdapter(this, R.layout.list_item_bwl, cursor,
new String[]{"title","noticeDate","noticeTime","content"},
new int[]{R.id.title,R.id.noticeDate,R.id.noticeTime,R.id.content});
// ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.layout.simple_expandable_list_item_1, getDatas());
lv.setAdapter(adapter);
this.registerForContextMenu(lv);
lv.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position,
long id) {
String title = ((TextView)view.findViewById(R.id.title)).getText().toString();
String content = ((TextView)view.findViewById(R.id.content)).getText().toString();
String noticeDate = ((TextView)view.findViewById(R.id.noticeDate)).getText().toString();
String noticeTime = ((TextView)view.findViewById(R.id.noticeTime)).getText().toString();
Intent intent = new Intent();
intent.setClass(MainActivity.this,AddBwlActivity.class);
Bundle bundle = new Bundle();
bundle.putLong("id", id);
bundle.putString("title", title);
bundle.putString("content", content);
bundle.putString("noticeDate", noticeDate);
bundle.putString("noticeTime", noticeTime);
intent.putExtras(bundle);
startActivity(intent);
}
});
//添加备忘录按钮
ImageButton btnAdd = (ImageButton)findViewById(R.id.btnAdd);
btnAdd.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent(MainActivity.this,AddBwlActivity.class);
startActivity(intent);
}
});
// //创建两个Intent,一个是用于AlarmReceiver类处理的,一个是用于广播的
// AlarmManager am = (AlarmManager)getSystemService(Context.ALARM_SERVICE);
// Intent intent = new Intent(MainActivity.this,AlarmReceiver.class);
// intent.setAction("ALARM_ACTION");
// PendingIntent pendingIntent = PendingIntent.getBroadcast(MainActivity.this, 0, intent, 0);
//
// //设置真正能让alarm起作用的参数
// //设置alarm是以何种方式发生的,这里用的是以UTC时间为基准,并在alarm发生时同时唤醒设备的模式。
// am.set(AlarmManager.RTC_WAKEUP,System.currentTimeMillis(),pendingIntent);
// am.setRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + 10*1000, 60*60*1000, pendingIntent);
}
//长按的上下文菜单
public void onCreateContextMenu(ContextMenu menu,View view,ContextMenuInfo menuInfo){
menu.setHeaderIcon(R.drawable.alarm);
menu.add(0,3,0,"修改");
menu.add(0,4,0,"删除");
}
public boolean onContextItemSelected(MenuItem item){
AdapterContextMenuInfo menuInfo = (AdapterContextMenuInfo)item.getMenuInfo();
switch(item.getItemId()){
case 3:
String title = ((TextView)menuInfo.targetView.findViewById(R.id.title)).getText().toString();
String content = ((TextView)menuInfo.targetView.findViewById(R.id.content)).getText().toString();
String noticeDate = ((TextView)menuInfo.targetView.findViewById(R.id.noticeDate)).getText().toString();
String noticeTime = ((TextView)menuInfo.targetView.findViewById(R.id.noticeTime)).getText().toString();
Intent intent = new Intent();
intent.setClass(this,AddBwlActivity.class);
Bundle bundle = new Bundle();
bundle.putLong("id", menuInfo.id);
bundle.putString("title", title);
bundle.putString("content", content);
bundle.putString("noticeDate", noticeDate);
bundle.putString("noticeTime", noticeTime);
intent.putExtras(bundle);
startActivity(intent);
break;
case 4:
dbhelper = new DbHelper(this, "db_bwl", null, 1);
db = dbhelper.getWritableDatabase();
int status = db.delete("tb_bwl", "id=?", new String[]{""+menuInfo.id});
if(status!=-1){
//删除后更新listview
Cursor cursor = db.query("tb_bwl", new String[]{"id as _id","title","content","noticeDate","noticeTime"}, null, null, null, null,null);
adapter.changeCursor(cursor);
// adapter.notifyDataSetChanged();
Toast.makeText(this, "删除成功", Toast.LENGTH_LONG).show();
}else{
Toast.makeText(this, "删除失败", Toast.LENGTH_LONG).show();
}
break;
}
return true;
}
@Override
protected Dialog onCreateDialog(int id) {
Dialog dialog=null;
switch (id) {
case DIALOG_TIME:
dialog=new TimePickerDialog(
this,
new TimePickerDialog.OnTimeSetListener(){
public void onTimeSet(TimePicker timePicker, int hourOfDay,int minute) {
Calendar c=Calendar.getInstance();//获取日期对象
c.setTimeInMillis(System.currentTimeMillis()); //设置Calendar对象
c.set(Calendar.HOUR, hourOfDay); //设置闹钟小时数
c.set(Calendar.MINUTE, c.get(Calendar.MINUTE)+1); //设置闹钟的分钟数
c.set(Calendar.SECOND, 0); //设置闹钟的秒数
c.set(Calendar.MILLISECOND, 0); //设置闹钟的毫秒数
Intent intent = new Intent(MainActivity.this, AlarmReceiver.class); //创建Intent对象
// intent.setAction("ALARM_ACTION");
PendingIntent pi = PendingIntent.getBroadcast(MainActivity.this, 0, intent, 0); //创建PendingIntent
//参数说明:http://www.eoeandroid.com/blog-119358-2995.html
alarmManager.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis()+30000, pi); //设置闹钟,当前时间就唤醒
Toast.makeText(MainActivity.this, "闹钟设置成功", Toast.LENGTH_LONG).show();//提示用户
}
},
cal.get(Calendar.HOUR_OF_DAY),
cal.get(Calendar.MINUTE),
true);
break;
}
Log.e("AndroidBWL", "onCreateDialog end...");
return dialog;
}
@Override
protected void onResume() {
super.onResume();
Cursor cursor = db.query("tb_bwl", new String[]{"id as _id","title","content","noticeDate","noticeTime"}, null, null, null, null,null);
adapter.changeCursor(cursor);
}
}
<p style="color: rgb(51, 51, 51); font-family: Arial; font-size: 14px; line-height: 26px;">添加activity</p><p style="color: rgb(51, 51, 51); font-family: Arial; font-size: 14px; line-height: 26px;">AddBwlActivity.java</p><p style="color: rgb(51, 51, 51); font-family: Arial; font-size: 14px; line-height: 26px;">
</p><p style="color: rgb(51, 51, 51); font-family: Arial; font-size: 14px; line-height: 26px;"></p><pre name="code" class="java">[java] view plaincopy在CODE上查看代码片派生到我的代码片
package com.cjq.androidbwl;
import java.util.Calendar;
import java.util.Date;
import android.app.Activity;
import android.app.AlarmManager;
import android.app.DatePickerDialog;
import android.app.PendingIntent;
import android.app.DatePickerDialog.OnDateSetListener;
import android.app.Dialog;
import android.app.TimePickerDialog;
import android.app.TimePickerDialog.OnTimeSetListener;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.database.sqlite.SQLiteDatabase;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.DatePicker;
import android.widget.EditText;
import android.widget.TimePicker;
import android.widget.Toast;
/**
*
* @author cjianquan
* @since 2014年10月31日
*/
public class AddBwlActivity extends Activity {
private EditText etDate = null,etTime=null,etTitle=null,etContent=null;
private Button btnSave = null;
static final int DATE_DIALOG_ID = 0;
static final int TIME_DIALOG_ID = 1;
private DbHelper dbhelper;
Bundle bundle;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.add_bwl);
dbhelper = new DbHelper(this, "db_bwl", null, 1);
etTitle = (EditText)findViewById(R.id.etTitle);
etContent = (EditText)findViewById(R.id.etContent);
etDate = (EditText)findViewById(R.id.etDate);
etDate.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//并会调用 onCreateDialog(int)回调函数来请求一个Dialog
showDialog(DATE_DIALOG_ID);
}
});
etTime = (EditText)findViewById(R.id.etTime);
etTime.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//并会调用 onCreateDialog(int)回调函数来请求一个Dialog
showDialog(TIME_DIALOG_ID);
}
});
btnSave = (Button)findViewById(R.id.btnSave);
btnSave.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
ContentValues value = new ContentValues();
String title = etTitle.getText().toString();
String content = etContent.getText().toString();
String noticeDate = etDate.getText().toString();
String noticeTime = etTime.getText().toString();
value.put("title", title);
value.put("content", content);
value.put("noticeDate", noticeDate);
value.put("noticeTime", noticeTime);
SQLiteDatabase db = dbhelper.getWritableDatabase();
long id = 0;
long status = 0;
if(bundle!=null){
id = bundle.getLong("id");
status = db.update("tb_bwl", value, "id=?", new String[]{bundle.getLong("id")+""});
}else{
status = db.insert("tb_bwl", null, value);
id = status;
}
if(status!=-1){
setAlarm(id);
Toast.makeText(AddBwlActivity.this, "保存成功", Toast.LENGTH_LONG).show();
}else{
Toast.makeText(AddBwlActivity.this, "保存失败", Toast.LENGTH_LONG).show();
}
}
});
//获取上一个activity的传值
bundle = this.getIntent().getExtras();
if(bundle!=null){
etDate.setText(bundle.getString("noticeDate"));
etTime.setText(bundle.getString("noticeTime"));
etTitle.setText(bundle.getString("title"));
etContent.setText(bundle.getString("content"));
}
}
private OnDateSetListener dateSetListener = new OnDateSetListener() {
@Override
public void onDateSet(DatePicker datePicker, int year, int month, int day) {
StringBuilder dateStr = new StringBuilder();
dateStr.append(year).append("-")
.append(month+1).append("-")
.append(day);
etDate.setText(dateStr.toString());
}
};
private OnTimeSetListener timeSetListener = new OnTimeSetListener() {
@Override
public void onTimeSet(TimePicker timePicker, int hour, int minute) {
StringBuilder timeStr = new StringBuilder();
timeStr.append(hour).append(":")
.append(minute);
etTime.setText(timeStr.toString());
}
};
/**
* 当Activity调用showDialog函数时会触发该函数的调用
*/
protected Dialog onCreateDialog(int id){
Calendar cal = Calendar.getInstance();
cal.setTime(new Date());
switch(id){
case DATE_DIALOG_ID:
DatePickerDialog dpd = new DatePickerDialog(this,dateSetListener, cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), cal.get(Calendar.DAY_OF_MONTH));
dpd.setCancelable(true);
dpd.setTitle("选择日期");
dpd.show();
break;
case TIME_DIALOG_ID:
TimePickerDialog tpd = new TimePickerDialog(this, timeSetListener, cal.get(Calendar.HOUR_OF_DAY), cal.get(Calendar.MINUTE), true);
tpd.setCancelable(true);
tpd.setTitle("选择时间");
tpd.show();
break;
default:
break;
}
return null;
}
private AlarmManager alarmManager=null;
public void setAlarm(long id){
Log.e("AndroidBWL", "setAlarm start...");
String noticeDate = etDate.getText().toString();
String noticeTime = etTime.getText().toString();
Calendar calendar = Calendar.getInstance();
calendar.set(Integer.parseInt(noticeDate.split("-")[0]),
Integer.parseInt(noticeDate.split("-")[1])-1,
Integer.parseInt(noticeDate.split("-")[2]),
Integer.parseInt(noticeTime.split(":")[0]),
Integer.parseInt(noticeTime.split(":")[1]));
Log.e("AndroidBWL", ""+(calendar.getTimeInMillis()-System.currentTimeMillis()));
alarmManager=(AlarmManager)getSystemService(Context.ALARM_SERVICE);
Intent intent = new Intent(AddBwlActivity.this, AlarmReceiver.class); //创建Intent对象
Bundle bundle = new Bundle();
bundle.putLong("id", id);
bundle.putString("title", etTitle.getText().toString());
bundle.putString("content", etContent.getText().toString());
bundle.putString("noticeDate", etDate.getText().toString());
bundle.putString("noticeTime", etTime.getText().toString());
intent.putExtras(bundle);
//PendingIntent.getBroadcast intent 数据不更新。
//传不同的 action 来解决这个问题
intent.setAction("ALARM_ACTION"+calendar.getTimeInMillis());
PendingIntent pi = PendingIntent.getBroadcast(AddBwlActivity.this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); //创建PendingIntent
//参数说明:http://www.eoeandroid.com/blog-119358-2995.html
alarmManager.set(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis()+5000, pi); //设置闹钟,当前时间就唤醒
Log.e("AndroidBWL", "setAlarm end...");
}
}
提醒的广播接收:
AlarmReceiver.java
[java] view plaincopy在CODE上查看代码片派生到我的代码片
package com.cjq.androidbwl;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
/**
*
* @author cjianquan
* @since 2014年10月29日
*/
public class AlarmReceiver extends BroadcastReceiver {
private Intent mIntent = null;
private PendingIntent mPendingIntent = null;
private Notification mNotification = null;
private NotificationManager mNotificationManager = null;
@Override
public void onReceive(Context context, Intent intent) {
Log.e("AndroidBWL", "AlarmReceiver:onReceive");
mIntent = intent;
Bundle bundle = mIntent.getExtras();
mNotificationManager = (NotificationManager)context.getSystemService(context.NOTIFICATION_SERVICE);
// mIntent = new Intent(context,AddBwlActivity.class);
mIntent.setClass(context,AddBwlActivity.class);
mPendingIntent = PendingIntent.getActivity(context, 0, mIntent, 0);
mNotification = new Notification();
mNotification.icon = R.drawable.alarm;
mNotification.tickerText="备忘录";
//设置默认声音、默认振动、和默认闪光灯
mNotification.defaults = Notification.DEFAULT_ALL;
//点击通知后自动取消
mNotification.flags |= Notification.FLAG_AUTO_CANCEL;
mNotification.setLatestEventInfo(context, bundle.getString("title"), bundle.getString("content"), mPendingIntent);
mNotificationManager.notify(1,mNotification);
}
}
<span style="color: rgb(51, 51, 51); font-family: Arial; font-size: 14px; line-height: 26px;">AndroidManifest.xml</span>
<span style="color: rgb(51, 51, 51); font-family: Arial; font-size: 14px; line-height: 26px;"></span><pre name="code" class="html">[html] view plaincopy在CODE上查看代码片派生到我的代码片
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.cjq.androidbwl"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="14"
android:targetSdkVersion="14" />
<application
android:allowBackup="true"
android:icon="@drawable/alarm"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name="com.cjq.androidbwl.MainActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name="com.cjq.androidbwl.AlarmActivity" />
<activity android:name="com.cjq.androidbwl.NotifitionActivity" />
<activity android:name="com.cjq.androidbwl.AddBwlActivity" />
<receiver android:name="com.cjq.androidbwl.AlarmReceiver" android:process=":remote"></receiver>
</application>
<!-- 添加操作振动器的权限-->
<uses-permission android:name="android.permission.VIBRATE"></uses-permission>
<!-- 添加操作闪光灯的权限 -->
<uses-permission android:name="android.permission.FLASHLIGHT"></uses-permission>
</manifest>
布局文件:
activity_main.xml
<span style="color: rgb(51, 51, 51); font-family: Arial; font-size: 14px; line-height: 26px;"></span><pre name="code" class="html">[html] view plaincopy在CODE上查看代码片派生到我的代码片
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:layout_gravity="right"
tools:context=".MainActivity" >
<ImageButton android:id="@+id/btnAdd"
android:paddingLeft="10dp"
android:alpha="70"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/add" />
<ListView android:id="@+id/lv_bwlList"
android:layout_width="match_parent"
android:layout_height="match_parent">
</ListView>
</LinearLayout>
list_item_bwl.xml
<span style="color: rgb(51, 51, 51); font-family: Arial; font-size: 14px; line-height: 26px;"><span style="color: rgb(51, 51, 51); font-family: Arial; font-size: 14px; line-height: 26px;"></span></span><pre name="code" class="html">[html] view plaincopy在CODE上查看代码片派生到我的代码片
<?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" >
<LinearLayout android:layout_width="match_parent"
android:layout_height="fill_parent"
android:width="0dp"
android:orientation="horizontal"
android:gravity="right">
<TextView
android:id="@+id/title"
android:width="0dp"
android:layout_weight="1.0"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textSize="20sp" />
<TextView android:id="@+id/noticeDate"
android:textSize="10sp"
android:width="0dp"
android:layout_weight="3.0"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<TextView android:id="@+id/noticeTime"
android:textSize="10sp"
android:width="0dp"
android:layout_weight="3.0"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
<TextView android:id="@+id/content"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
add_bwl.xml
<span style="color: rgb(51, 51, 51); font-family: Arial; font-size: 14px; line-height: 26px;"><span style="color: rgb(51, 51, 51); font-family: Arial; font-size: 14px; line-height: 26px;"><span style="color: rgb(51, 51, 51); font-family: Arial; font-size: 14px; line-height: 26px;"></span></span></span><pre name="code" class="html">[html] view plaincopy在CODE上查看代码片派生到我的代码片
<?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:layout_gravity="left"
android:textSize="25sp"
android:orientation="vertical" >
<TextView android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/title"/>
<EditText android:id="@+id/etTitle"
android:hint="请输入标题"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<TextView android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="日期"/>
<EditText android:id="@+id/etDate"
android:hint="请选择日期"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<TextView android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="时间"/>
<EditText android:id="@+id/etTime"
android:hint="请选择时间"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<TextView android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/content"/>
<EditText android:id="@+id/etContent"
android:hint="请输入内容"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<Button android:id="@+id/btnSave"
android:text="保存"
android:textSize="40sp"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
</pre>
设置全局背景图片:
styles.xml
[html] view plaincopy在CODE上查看代码片派生到我的代码片
<resources xmlns:android="http://schemas.android.com/apk/res/android">
<!--
Base application theme, dependent on API level. This theme is replaced
by AppBaseTheme from res/values-vXX/styles.xml on newer devices.
-->
<style name="AppBaseTheme" parent="android:Theme.Light">
<!--
Theme customizations available in newer API levels can go in
res/values-vXX/styles.xml, while customizations related to
backward-compatibility can go here.
-->
</style>
<!-- Application theme. -->
<style name="AppTheme" parent="AppBaseTheme">
<!-- All customizations that are NOT specific to a particular API-level can go here. -->
<item name="android:windowBackground">@drawable/shade</item>
</style>
</resources>
from :http://blog.csdn.net/kuangfengbuyi/article/details/40658209