文章原载于我的个人博客,欢迎来访。


整理了一些Android很经典的几个问题。

其实是选修课作业,顺道吹一吹这门课(

附件:部分源代码

1、Android应用的基本组件有哪些?

  1. Activity:应用表示层(基类Activity)

一个活动表示一个可视化的用户界面,同一应用中的每个Activity是相互独立的。每一个活动都是作为Activity基类的一个子类的实现。应用程序中的每个屏幕都是通过继承和扩展基类Activity来实现的。Activity窗口内的可见内容通过基类View提供。

  1. Service:没有可见的用户界面,但能够长时间运行于后台(基类Service)

一个没有用户界面的在后台运行执行耗时操作的应用组件。其他应用组件能够启动Service,并且当用户切换到另外的应用场景,Service将持续在后台运行。另外,一个组件能够绑定到一个Service与之交互,所有这些活动都是在后台进行。Service与Activity一样都存在于当前进程的主线程中,所以一些阻塞UI的操作不能放在Service里进行。

  1. Broadcast Receiver:用户接收广播通知的组件(基类BroadcastReceiver)

广播接收者仅是接受广播公告并作出相应的反应。许多广播源自于系统,应用程序也可以发起广播。一个应用程序可以有任意数量的广播接收者去反应任何它认为重要的公告。所有的接受者继承自BroadcastReceiver基类。BroadcastReceiver自身并不实现图形用户界面,但是当它收到某个通知后,可以启动 Activity作为响应,或者通过NotificationMananger提醒用户。

  1. Content Provider:应用程序间数据通信、共享(基类ContentProvider)

内容提供者使一个应用程序的指定数据集提供给其他应用程序,继承自ContentProvider 基类并实现了一个标准的方法集,使得其他应用程序可以检索和存储数据。应用程序使用一个ContentResolver对象并调用它的方法。ContentResolver能与任何内容提供者通信,它与提供者合作来管理参与进来的进程间的通信。

  1. Intent:连接组件的纽带通信

Intent在不同的组件之间传递消息,将一个组件的请求意图传给另一个组件。因此, Intent 是包含具体请求信息的对象。针对不同的组件,Intent所包含的消息内容有所不同,且不同组件的激活方式也不同。 Intent是一种运行时绑定机制,它能够在程序运行的过程中连接两个不同的组件。通过Intent,程序可以向 Android表达某种请求或者意愿,Android会根据意愿的内容选择适当的组件来处理请求。

2、简述Android平台用户界面搭建有哪几种方式,并对这几种方式进行比较。

  1. 使用XML布局文件控制UI界面:利用XML布局文件来控制用户界面是开发人员最常使用的方法。XML文件来描述用户界面,并将其保存在资源文件夹/res/layout下。这种方法极大地简化界面设计的过程,将界面视图从Java代码中分离出来,将用户界面中静态部分定义在XML中,代替了写代码,使得程序结构更加清晰、明了。
  2. 在Java代码中控制UI界面:Android允许开发者,完全在Java代码中控制UI界面。一方面不利于高层次解耦,另一方面界面中的控件需要通过new来创建,控件属性的设置还需要调用相应的方法,因此代码显得较为臃肿,对程序开发人员来讲不论设计还是维护都较为繁琐。
  3. 使用XML布局文件和代码混合控制UI界面:使用XML布局文件和Java代码混合控制UI界面,习惯上把变化小、行为比较固定的组件放在XML布局文件中,把变化较多、行为控制比较复杂的组件交给Java代码来管理。

3、Handler消息传递机制是怎样的?试举例说明。

  1. 目标线程调用Looper.prepare()创建Looper对象和消息队列.
  2. 目标线程通过new Handler()创建Handler对象,将Handler、Looper、消息队列三者关联起来。并覆盖其handleMessage函数。
  3. 目标线程调用Looper.loop() 监听消息队列。
  4. 消息源线程调用Handler.sendMessage发送消息。
  5. 消息源线程调用MessageQueue.enqueueMessage将待发消息插入消息队列。
  6. 目标线程的loop() 检测到消息队列有消息插入,将其取出。
  7. 目标线程将取出消息通过Handler.dispatchMessage派发给Handler.handleMessage进行消息处理。

例如:模拟手机客户端下载网络数据并在手机界面显示新数据。

业务逻辑:

  1. UI线程获得用户请求;
  2. 启动子线程完成网络数据下载(网络下载过程通过强制子线程休眠若干秒来模拟);
  3. 子线程将下载的数据返回UI线程并显示。

代码如下

public class MainActivity extends AppCompatActivity {private Button mButton;private TextView mTextView;private Handler mHandler;private Thread mNetAccessThread;private ProgressDialog mProgressDialog;private int mDownloadCount = 0;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);mButton = (Button) findViewById(R.id.begin);mTextView = (TextView) findViewById(R.id.dataText);//设置按钮的点击事件监听器mButton.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {showProgressDialog("", "正在下载...");//启动子线程进行网络访问模拟mNetAccessThread = new ChildTread();mNetAccessThread.start();}});//继承Handler类并覆盖其handleMessage方法mHandler = new Handler() {//覆盖Handler类的handleMessage方法//接收子线程传递的数据并在UI显示@Overridepublic void handleMessage(Message msg) {switch (msg.what) {case 1:mTextView.setText((String) msg.obj);mTextView.setTextColor(Color.RED);dismissProgressDialog();break;default:break;}}};}class ChildTread extends Thread {@Overridepublic void run() {//休眠6秒,模拟网络访问延迟try {Thread.sleep(6000);} catch (InterruptedException e) {e.printStackTrace();}//将结果通过消息返回主线程Message msg = new Message();msg.what = 1;mDownloadCount++;msg.obj = new String("第" + mDownloadCount + "次从网上下载的数据");mHandler.sendMessage(msg);}}/*** 开启progressDialog.* @param title   对话框标题.* @param content 对话框正文.*/protected void showProgressDialog(String title, String content) {mProgressDialog = new ProgressDialog(this);if (title != null)mProgressDialog.setTitle(title);if (content != null)mProgressDialog.setMessage(content);mProgressDialog.show();}// 关闭progressDialog.protected void dismissProgressDialog() {if (mProgressDialog != null) {mProgressDialog.dismiss();}}
}

4、如何显式启动一个Activity?如何隐式启动Activity?

显式启动

显式启动是指一个Activity通过类名明确指明要启动哪个Activity。

  1. 创建Intent对象,并初始化指明要启动的Activity。可以直接用:Intent intent = new Intent(ActivityA.this, ActivityB.class);
  2. 调用启动Activity的方法启动新的Activity,如startActivity(intent),完成新Activity的启动。

隐式启动

隐式启动表示不需指明要启动哪一个Activity,只需要声明一个行为,系统会根据Activity配置,启动能够匹配这个行为的Activity。

隐式启动Activity时,系统会自动启动与Intent上的动作(Action)、附加数据(Category)以及数据(Data)相匹配的Activity。 Action用于描述要完成的动作,Category为被执行动作Action增加的附加信息。通常Action与Category结合使用,Data则用于向Action提供操作的数据。

Intent隐式启动分三个步骤:

  1. 创建Intent对象:Intent intent=new Intent();
  2. 设置用于匹配的Intent属性,Intent属性有:动作属性、动作额外附加的类别属性以及数据属性等,分别需要调用不同的方法来设置:
intent.setAction(); //设置动作属性
intent.addCategory); //设置类别属性
intent.setData(); //设置数据属性
intent.setType(); //设置数据属性
  1. 调用启动Activity方法启动新的Activity,如:startActivity(intent)

5、如何使用Bundle在Activity之间交换数据?试编程加以说明。

  1. Intent 提供了多个方法来设置欲传递的数据,例如:
  • public Intent putExtras (Bundle extras):向Intent中放入需要传递的Bundle数据extras。
  • public Intent putExtra (String name, Xxx value):向Intent中按key-value对的形式存入数据(Xxx指代各种数据类型的名称),key为name,value为val。
  1. Intent也提供了相应的取出传递过来的数据的方法,例如:
  • public Bundle getExtras ():取出Intent所“携带”的数据。
  1. Bundle类也提供了多个方法,来把数据封装到Bundle对象中,例如:
  • public void putSerializable (String key, Serializable value):向Bundle中放入一个可序列化的对象。
  • public void putInt (String key, int value):向Bundle中放入int类型的数据。
  1. Bundle类也提供了从Bundle对象中取出数据的方法:
  • public Serializable getSerializable (String key):从Bundle中取出一个可序列化的对象。
  • public int getInt (String key):从Bundle中取出Int类型的数据。

例如 ActivityA启动ActivityB,并携带key为“name”数据传递给ActivityB。

Intent intent = new Intent(ActivityA.this,ActivityB.class);
Bundle bundle = new Bundle();
bundle.putString("name","Tom");
intent.putExtras(bundle);
startActivity(intent);

ActivityB获取传递过来的数据,相应代码如下:

Intent intent = getIntent();
Bundle bundle = intent.getExtras();
String name = bundle.getString("name");

6、编程实现进程内服务,并上机调试。

例如 编写一个音乐播放器。继承Service类实现自定义Service,提供在后台播放音乐、暂停音乐、停止音乐的方法。

public class MainActivity extends AppCompatActivity implements View.OnClickListener {private Intent intent;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);Button playBtn, stopBtn, pauseBtn, exitBtn;playBtn = findViewById(R.id.play);stopBtn = findViewById(R.id.stop);pauseBtn = findViewById(R.id.pause);exitBtn = findViewById(R.id.exit);playBtn.setOnClickListener(this);stopBtn.setOnClickListener(this);pauseBtn.setOnClickListener(this);exitBtn.setOnClickListener(this);}@Overridepublic void onClick(View v) {int num = -1;intent = new Intent("com.holger.androidmusic");intent.setPackage(this.getPackageName());switch (v.getId()) {case R.id.play:Toast.makeText(this, "播放音乐...", Toast.LENGTH_SHORT).show();num = 1;break;case R.id.stop:Toast.makeText(this, "停止音乐...", Toast.LENGTH_SHORT).show();num = 2;break;case R.id.pause:Toast.makeText(this, "暂停音乐...", Toast.LENGTH_SHORT).show();num = 3;break;case R.id.exit:Toast.makeText(this, "退出...", Toast.LENGTH_SHORT);num = 4;stopService(intent);this.finish();return;}Bundle bundle = new Bundle();bundle.putInt("music", num);intent.putExtras(bundle);startService(intent);}@Overridepublic void onDestroy(){super.onDestroy();if(intent != null){stopService(intent);}}
}

在布局中添加三个按钮,用于控制音乐播放、暂停与停止

public class MyService extends Service {private static final String TAG = "MusicService";private MediaPlayer mediaPlayer;private boolean reset = false;@Overridepublic IBinder onBind(Intent intent) {return null;}@Overridepublic void onCreate() {Log.v(TAG, "onCreate");if (mediaPlayer == null) {mediaPlayer = MediaPlayer.create(this, R.raw.music);mediaPlayer.setLooping(false);}}@Overridepublic void onDestroy() {Log.v(TAG, "onDestroy");if (mediaPlayer != null) {mediaPlayer.stop();mediaPlayer.release();}}@Overridepublic int onStartCommand(Intent intent, int flags, int startId) {Log.v(TAG, "onStart");if (intent != null) {Bundle bundle = intent.getExtras();if (bundle != null) {int num = bundle.getInt("music");switch (num) {case 1: play(); break;case 2: stop(); break;case 3: pause(); break;}}}return START_STICKY;}public void play() {Log.v(TAG, "-----------" + reset + "----------");if (mediaPlayer == null)return;if (reset)mediaPlayer.seekTo(0);if (!mediaPlayer.isPlaying()) {mediaPlayer.start();}}public void pause() {reset = false;if (mediaPlayer != null && mediaPlayer.isPlaying()) {mediaPlayer.pause();}}public void stop() {reset = true;if (mediaPlayer != null) {mediaPlayer.stop();try {mediaPlayer.prepare();} catch (IOException ex) {ex.printStackTrace();}}}
}

在清单文件中声明Service,为其添加label标签,便于在系统中识别Service

<serviceandroid:name=".MyService"android:label="@string/app_name" />

界面如下:

点击“播放音乐”按钮后,在后台将会运行着名为“Android Music”的服务

7、编写一APP,实现SQLite数据库的增删改查。

设计一个课程管理APP,实现SQLite数据库的增删改查

在工程中创建MainActivity.java、InsertActivity.java、QueryActivity.java、UpdateActivity.java、DeleteActivity.java以及相应的布局文件activity_main.xml、activity_insert.xml、activity_query.xml、activity_update.xml、activity_delete.xml,以及数据库适配器DBAdapter.java、实体类Course.java。

数据库工具类DBAdapter.java,其中定义了内部类BookDBHelper,它是SQLiteOpenHelper的子类,用于建立、更新和打开数据库。主要代码:

public class DBAdapter {public static final String ID = "_id";public static final String COURSENAME = "courseName";public static final String TEACHER = "teacher";public static final String TIME = "time";private static final String DB_NAME = "course.db";private static final String DB_TABLE = "courseinfo";private static final int DB_VERSION = 1;private final Context context;private SQLiteDatabase db;private CourseDBHelper dbHelper;public DBAdapter(Context context) {this.context = context;}public void close() {   // Close the databaseif (db != null) {db.close();db = null;}}public void open() throws SQLiteException { // Open the databasedbHelper = new CourseDBHelper(context, DB_NAME, null, DB_VERSION);try {db = dbHelper.getWritableDatabase();} catch (SQLiteException ex) {db = dbHelper.getReadableDatabase();}}public long insert(Course course) {ContentValues courseValues = new ContentValues();courseValues.put(COURSENAME, course.getCourseName());courseValues.put(TEACHER, course.getTeacher());courseValues.put(TIME, course.getTime());return db.insert(DB_TABLE, null, courseValues);}public Course[] queryAll() {Cursor results =  db.query(DB_TABLE, new String[] { ID, COURSENAME, TEACHER, TIME},null, null, null, null, null);return ConvertToCourse(results);}public Course[] queryOne(long id) {Cursor results =  db.query(DB_TABLE, new String[] { ID, COURSENAME, TEACHER, TIME},ID + "=" + id, null, null, null, null);return ConvertToCourse(results);}private Course[] ConvertToCourse(Cursor cursor){int resultCounts = cursor.getCount();if (resultCounts == 0 || !cursor.moveToFirst()){return null;}Course[] courseList = new Course[resultCounts];for (int i = 0 ; i<resultCounts; i++){courseList[i] = new Course();int newId = cursor.getInt(0);courseList[i].setID(newId);String newCourseName = cursor.getString(cursor.getColumnIndex(COURSENAME));courseList[i].setCourseName(newCourseName);String newTeacher = cursor.getString(cursor.getColumnIndex(TEACHER));courseList[i].setTeacher(newTeacher);float newTime = cursor.getFloat(cursor.getColumnIndex(TIME));courseList[i].setTime(newTime);cursor.moveToNext();}return courseList;}public long deleteAll() {return db.delete(DB_TABLE, null, null);}public long deleteOne(long id) {return db.delete(DB_TABLE,  ID + "=" + id, null);}public long updateOne(long id , Course course){ContentValues courseValues = new ContentValues();courseValues.put(COURSENAME, course.getCourseName());courseValues.put(TEACHER, course.getTeacher());courseValues.put(TIME, course.getTime());return db.update(DB_TABLE, courseValues,  ID + "=" + id, null);}// 静态Helper类,用于建立、更新和打开数据库private static class CourseDBHelper extends SQLiteOpenHelper {private static final String DB_CREATE = "create table " +DB_TABLE + " (" + ID + " integer primary key autoincrement, " +COURSENAME + " text not null, " + TEACHER + " text," + TIME + " float);";public CourseDBHelper(Context context, String name, CursorFactory factory, int version) {super(context, name, factory, version);}@Overridepublic void onCreate(SQLiteDatabase _db) {_db.execSQL(DB_CREATE);}@Overridepublic void onUpgrade(SQLiteDatabase _db, int _oldVersion, int _newVersion) {_db.execSQL("DROP TABLE IF EXISTS " + DB_TABLE);onCreate(_db);}}
}

其余详细代码文件可详见附件SQLiteDemo工程文件夹。

运行结果如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3QizxtdX-1595863946493)(https://i.loli.net/2020/07/16/C5ghqGTxWRmrUH4.png)]

8、如何发送广播?如何接收广播?试编程举例说明。

发送广播

  1. 自定义BroadcastReceiver

自定义广播接收器需要继承基类BroadcastReceivre,并实现抽象方法onReceive(context, intent)方法。广播接收器接收到相应广播后,会自动回调onReceive()方法。onReceive()方法中不能执行太耗时的操作,否则将会造成ANR。 一般情况下,根据实际业务需求,onReceive()方法中都会涉及到与其他组件之间的交互,如发送Notification、启动Service等。 自定义如下:

public class MyBroadcastReceiver extends BroadcastReceiver { public static final String TAG = "MyBroadcastReceiver"; @Override public void onReceive(Context context, Intent intent) { Log.d(TAG, "intent: " + intent); String name = intent.getStringExtra("name");//… }
}

一个BroadcastReceiver对象只有在被调用onReceive(Context, Intent)时才有效,当从该函数返回后,该对象就无效了,从而结束生命周期。

  1. BroadcastReceiver注册类型

BroadcastReceiver总体上可以分为两种注册类型:静态注册和动态注册。

  • 静态注册

直接在AndroidManifest.xml文件中进行注册。规则如下:

 <receiver android:enabled = ["true" | "false"] android:exported = ["true" | "false"] android:icon = "drawable resource" android:labe l= "string resource" android:name = "string" android:permission = "string" android:process = "string" > ……
</receiver>

其中,需要注意的属性有以下:

  • android:exported 用于标识此BroadcastReceiver能否接收其他App发出的广播,其默认值是由Receiver中有无intent-filter决定的,如果有intent-filter,则默认值为true,否则为false
  • android:name BroadcastReceiver的类名。
  • android:permission 如果设置,具有相应权限的广播发送方发送的广播才能被此BroadcastReceiver所接收。
  • android:process BroadcastReceiver运行所处的进程。默认为App的进程,也可以指定独立的进程。

常见的注册形式如下:

<receiver android:name=".MyBroadcastReceiver" > <intent-filter> <action android:name="android.net.conn.CONNECTIVITY_CHANGE" /> </intent-filter> <intent-filter> <action android:name="android.intent.action.BOOT_COMPLETED" /> </intent-filter>
</receiver>

其中,intent-filter用于指定此广播接收器接收特定的广播类型。

  • 动态注册

动态注册时,无须在AndroidManifest中注册<receiver/>组件。直接在代码中通过调用Context的registerReceiver()函数,可以在程序中动态注册BroadcastReceiver。 典型的写法示例如下:

public class MainActivity extends Activity { public static final String BROADCAST_ACTION = "com.example.corn"; private BroadcastReceiver mBroadcastReceiver; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState);setContentView(R.layout.activity_main); mBroadcastReceiver = new MyBroadcastReceiver(); IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(BROADCAST_ACTION); registerReceiver(mBroadcastReceiver, intentFilter);}@Override protected void onDestroy() { super.onDestroy(); unregisterReceiver(mBroadcastReceiver); }
}

需要注意的是,Android中所有与观察者模式有关的设计中,一旦涉及到register,必定在相应的时机需要unregister

动态注册方式隐藏在代码中,比较难发现;需要特别注意的是,在退出程序前要记得调用Context.unregisterReceiver()方法。一般在Activity的onStart()方法中进行注册,在onStop()方法中进行注销。注意:如果在Activity.onResume()方法中注册了,就必须在Activity.onPause()方法中注销。


原文链接 作者:Holger

Android 入个门相关推荐

  1. Android入门第七篇之ListView (二)

    Android入门第六篇之ListView (一) ,讲的是如何制作一个具有两行文本的 自定义控件 ,作为ListView的Item的使用方法.这篇接下来也是围绕ListView和Item,更加深入地 ...

  2. 【毕业设计_课程设计】基于Android的人脸门禁系统

    文章目录 0 项目说明 1 研究目的 2 研究内容及实验结果 3 部分系统界面 4 项目源码 0 项目说明 基于Android的人脸门禁系统 提示:适合用于课程设计或毕业设计,工作量达标,源码开放 1 ...

  3. android人脸识别门禁,安卓人脸识别门禁终端DM-A1

    1.产品介绍: 安卓(Android)人脸识别门禁终端DM-A1是定位为一款功能丰富,扩展性强,稳定性高,简单维护的人脸识别一体机.它集深数科技人脸比对算法及人脸识别活体检测算法,实现5000人脸库下 ...

  4. Android 入门第四讲03-列表RecyclerView(RecyclerView使用步骤(详),RecyclerView指定一行item的数目+指定一行item的数量,并且设置列表方向)

    Android 入门第四讲03-列表RecyclerViewRecyclerView使用步骤(详),RecyclerView指定一行item的数目+指定一行item的数量,并且设置列表方向) 1.Re ...

  5. 《深度学习入门一》一入侯门深似海,深度学习深几许 2017-06-09 云栖社区 摘要: 当你和女朋友在路边手拉手一起约会的时候,你可曾想,你们之间早已碰撞出了一种神秘的智慧–深度学习。恋爱容易,相

    <深度学习入门一>一入侯门深似海,深度学习深几许 2017-06-09 云栖社区 摘要: 当你和女朋友在路边手拉手一起约会的时候,你可曾想,你们之间早已碰撞出了一种神秘的智慧–深度学习.恋 ...

  6. 一入侯门深似海,从此萧郎是路人

    <赠婢> 唐:崔郊 公子王孙逐后尘,绿珠垂泪滴罗巾. 一入侯门深似海,从此萧郎是路人. 诗的首两句应该换个顺序翻译,绿珠是美女佳人的代称,泛指容貌姣好的女子,可是这么美丽的女子却在偷偷的哭 ...

  7. android tablelayout 多行,Android入门户五篇之TableLayout (二)//生成10行,8列的表格

    当前位置:我的异常网» Android » Android入门户五篇之TableLayout (二)//生成10行,8 Android入门户五篇之TableLayout (二)//生成10行,8列的表 ...

  8. Android入门第1天-Android Studio的安装

    安装前的准备 在hosts文件中加入dl.google.com的ip映射 Android Studio在安装时大量的依赖库会从dl.google.com下载.在国内安装步骤卡死基本都是卡死在该步.那么 ...

  9. Android入门第八篇之GridView(九宫图)

    本文来自http://blog.csdn.net/hellogv/ GridView跟ListView都是比較经常使用的多控件布局,而GridView更是实现九宫图的首选!本文就是介绍怎样使用Grid ...

最新文章

  1. ACMNO.30 C语言-宏交换 定义一个带参的宏,使两个参数的值互换,并写出程序,输入两个数作为使用宏时的实参。输出已交换后的两个值。
  2. Linux中内存buffer和cache的区别
  3. [问答题] 考SQL语句的题,题太长了,实在不好回忆了。
  4. 中国风力发电设备市场需求容量与投资战略研究报告2022版
  5. 【Bapi】客户主数据快捷修改
  6. BASIC-5 查找整数
  7. 【JZOJ4964】【GDKOI2017模拟1.21】Rhyme
  8. Apache配置(转载)
  9. 启动马达接线实物图_东元伺服驱动马达
  10. 一个准毕业生的2012年总结
  11. linux设备树sysfs,迅为-iMX6开发板-设备树内核-sys方式控制GPIO
  12. LINUX下调节屏幕亮度(Intel核显)-续
  13. 【重构】SqlParameter的作用
  14. IT行业毕业生投简历或面试技巧
  15. 在开发版上用C语言写实心圆,MFC利用CPen与CBrush绘制实心圆
  16. 利用TDR (时域反射计)测量传输延时
  17. 自定义Drawable 实现图片圆角、圆形、椭圆形
  18. Teambition X 2019 校招
  19. Android Interpolator(插值器)的介绍和使用
  20. 如何更电计算机共享名称,手机电脑电视之间如何共享、投屏?

热门文章

  1. php网站 类设计,PHP网站开发与设计
  2. 牛客算法周周练14 BCircle D 绝地求生(gcd,思维) E
  3. 英文阅读技巧操练---Article 1:The Product-Minded Software Engineer《一》
  4. Flexsim Database Connectors连接数据库
  5. element ui 表格里面放图片
  6. 6.824 Spring 2021 -- Lab 2A
  7. fstat、stat和lstat用法
  8. the selection cannot be run on any server解决办法
  9. 网易杭研院汪源:网易云大量使用人工智能技术
  10. 国内研究团队提出基于120个刺激编码视觉诱发电位的脑机接口