20165320 毕业设计 第四周总结
任务及完成情况
周一 | 周二 | 周三 | 周四 | 周五 | 周六 | 周天 |
---|---|---|---|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
内容总结
一、数据存储方案
1、持久化技术简介
- 数据持久化指的是将内存中的瞬时数据保存到存储设备中,在设备关机的情况下,这些数据仍然不会丢失。Android系统中主要提供了3种方式用于简单地实现数据持久化功能,即文件存储、sharedPreference存储以及数据库存储,除此之外还有一种相对不安全的SD卡。
2、文件存储
2.1 文件写入
-
Context类中提供了一个openFileOutput()方法,将数据存储到指定的文件中。第一个参数是文件名,第二个参数是操作模式,主要有两种模式可选,MODE_PRIVATE和MODE_APPEND。
-
MODE_PRIVATE模式:当指定同样文件名的时候,所写入的内容将覆盖原文件中的内容。
-
MODE_APPEND模式:如果文件已存在,就往文件里面追加内容,不存在就创建新文件。
-
PS:除此之外,本来还有两种模式,MODE_WORLD_READABLE和MODE_WORLD_WRITEABLE,这两种模式由于允许其它应用程序对本身程序中的文件进行读写操作,容易引起安全漏洞,已在Android4.2版本中被遗弃。
-
-
openFileOutput()方法返回一个FileOutputStream对象,得到这个对象后可以使用Java流的方式将数据写入到文件中。简单实例如下:
public class MainActivity extends AppCompatActivity { private EditText edit; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); edit = (EditText)findViewById(R.id.edit); } @Override protected void onDestroy(){ super.onDestroy(); String inputText = edit.getText().toString(); save(inputText); } public void save(String inputText){ FileOutputStream out = null; BufferedWriter writer= null; try { out = openFileOutput("data", Context.MODE_PRIVATE); writer = new BufferedWriter(new OutputStreamWriter(out)); writer.write(inputText); }catch (IOException e){ e.printStackTrace(); }finally { try { if (writer!=null){ writer.close(); } }catch (IOException e){ e.printStackTrace(); } } } }
2.2 文件读取
-
Context类还提供了一个openFileInput()方法,用于从文件中读取数据。该方法只接收一个参数,需要读取的文件名,然后系统会到/data/data/ /files/目录下去加载这个文件,并返回一个FileInputStream对象,然后利用Java流读取数据。
-
在2.1的例子中做如下修改:
protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); edit = (EditText)findViewById(R.id.edit); String inputText = load(); if(!TextUtils.isEmpty(inputText)){ edit.setText(inputText); edit.setSelection(inputText.length()); Toast.makeText(this,"Restoring succeeded",Toast.LENGTH_SHORT).show(); } } public String load(){ FileInputStream in = null; BufferedReader reader = null; StringBuilder content = new StringBuilder(); try { in = openFileInput("data"); reader = new BufferedReader(new InputStreamReader(in)); String line = ""; while((line = reader.readLine())!=null){ content.append(line); } }catch (IOException e){ e.printStackTrace(); } finally { if (reader != null){ try { reader.close(); }catch (IOException e){ e.printStackTrace(); } } } return content.toString(); }
3、SharedPreferences存储
- 该存储方式是使用键值对的方式来存储数据的,同时支持不同的数据类型存储,如果存储的数据类型是整型,那么读取出来的数据也是整型。
3.1 将数据存储到SharedPreferences中
-
首先需要获取SharedPreferences对象,Android提供了3种方法:
-
Context类种的getSharedPreferences()方法,此方法接收两个参数,第一个是文件名称,第二个参数是操作模式,目前只有MODE_PRIVATE模式可选。
-
Activity类种的getPreferences()方法,该方法只接收一个操作模式参数,并将当前活动过的类名作为SharedPreferences的文件名。
-
PreferenceManager类种的getDefaultSharedPreferences()方法,它接收一个Context参数,并自动使用当前应用程序的包名作为前缀命名SharedPreferences文件。
-
-
得到SharedPreferences对象后,分三步走:
-
调用SharedPreferences对象的edit()方法,获取一个SharedPreferences.Editor对象。
-
向该对象添加数据,如果是字符串之类的,就使用putString()方法,以此类推。
-
调用apply()方法将添加的数据提交,完成数据存储操作。
-
-
简单例子:
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Button saveData =(Button)findViewById(R.id.save_data); saveData.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { SharedPreferences.Editor editor = getSharedPreferences("data",MODE_PRIVATE).edit(); editor.putString("name","SXZ"); editor.putInt("age",22); editor.putBoolean("married",true); editor.apply(); } }); } }
3.2 从SharedPreferences中读取数据
-
SharedPreferences对象中提供了一系列的get方法,每一种数据对应一种,第一个参数是当初存储数据时所用的键值,第二个参数是默认值,如果对应键值没找到数据,则返回默认值。
-
在3.1基础上修改的简单例子:
Button restoreData= (Button)findViewById(R.id.restore_data); restoreData.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { SharedPreferences pref = getSharedPreferences("data",MODE_PRIVATE); String name = pref.getString("name",""); int age = pref.getInt("age",0); boolean married = pref.getBoolean("married",false); Log.d("MainActivity","name is "+name); Log.d("MainActivity","age is "+age); Log.d("MainActivity","married is "+married); } });
4、SQLite数据库存储
-
Android为了方便开发人员管理数据库,专门提供了一个SQLiteOpenHelper帮助类,借助这个类可以非常简单地对数据库进行创建和升级。如果需要使用的话,得先创建一个帮助类继承它,并重写onCreate()与onUpgrade()方法,在这两个方法中实现创建、升级数据库的逻辑。
-
SQLiteOpenHelper中还有两个非常重要的实例方法:getReadableDatabase()和getWritableDatabase(),这两个方法都可以创建或者打开一个现有的数据库,并返回一个可对数据库进行读写操作的对象。
-
SQLiteOpenHelper中有两个构造方法可供重写,一般用参数较少的。第一个参数是Context,第二个参数是数据库名,第三个参数一般是null,第四个参数是当前数据库的版本号。
-
一个简单实例:
public class MyDatabaseHelper extends SQLiteOpenHelper { public static final String CREATE_BOOK = "create table Book (" +"id integer primary key autoincrement," + "author text," + "price real," + "pages integer," + "name text)"; private Context mContext; public MyDatabaseHelper(@Nullable Context context, @Nullable String name, @Nullable SQLiteDatabase.CursorFactory factory, int version) { super(context, name, factory, version); mContext = context; } @Override public void onCreate(SQLiteDatabase db) { db.execSQL(CREATE_BOOK); Toast.makeText(mContext,"Create succeeded",Toast.LENGTH_SHORT).show(); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { } }
4.1创建数据库
-
在主活动的onCreate()方法中构建一个MyDataHelper对象,再通过其构造函数的参数,指定数据库名与版本号,在按钮的点击事件中调用getWritableDatabase()方法。由于第一次点击时,当前程序没有BookStore.db该数据库,所以会调用MyDatabaseHelper类中的onCreate()方法。
public class MainActivity extends AppCompatActivity { private MyDatabaseHelper dbhelper; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); dbhelper = new MyDatabaseHelper(this,"BookStore.db",null,1); Button createDatabase = (Button)findViewById(R.id.create_database); createDatabase.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { dbhelper.getWritableDatabase(); } }); } }
-
使用adb连接模拟器,再使用sqlite命令打开数据库查看:
- 如果需要对数据库更新,修改SQLiteOpenHelper的构造方法第四个参数比当前版本大,就会执行onUpgrade()方法。
4.2 添加数据
-
SQLite 提供了一个insert()方法用于添加数据。第一个参数是表名,第二个参数用于在未指定添加数据的情况下给某些可为空的列自动赋值NULL,第三个参数是一个ContentValues对象,它提供了一系列put()方法的重载,用于向ContentValues中添加数据,只需要将表中每个列名以及相应的待添加数据传入。
-
简单例子:
SQLiteDatabase db = dbhelper.getWritableDatabase(); ContentValues values = new ContentValues(); Log.d("MainActivity","NIUB"); values.put("name","The Xiang"); values.put("author","ZCH"); values.put("pages",388); values.put("price",1999); db.insert("Book",null,values);
4.3 更新数据
-
SQLiteDatabase中提供了update()方法,用于对数据进行更新,前两个参数与insert()方法一样,分别是表名与ContentValues对象,第三个与第四个参数用于定位需要更新的数据。
-
简单例子:
SQLiteDatabase db = dbhelper.getWritableDatabase(); ContentValues values = new ContentValues(); values.put("price",10); db.update("Book",values,"name = ?",new String[] {"The Genji Book"});
4.4 查询数据
-
SQLiteDatabase提供了一个query()方法用于对数据进行查询,其中需要填写的参数有7个之多.....,具体如下:
-
调用query()方法后,返回一个Cursor对象,查询到的所有数据都将从这个对象中取出。
-
PS:由于数据库不是学习重点,就在此略过吧。
二、内容提供器
1、简介
- 内容提供器(Content Provider)主要用于在不同的应用程序之间实现数据共享的功能,比如系统的电话簿程序,为许多第三方程序提供了便利。它提供了一套完整的机制,允许一个程序访问另一个程序中的数据,同时还能保证被访数据的安全性。目前,使用内容提供器是Android实现程序共享数据的标准方式。
2、权限机制
-
在Android6.0之前,应用程序中涉及用户设备安全性的操作必须在AndroidManifest.xml文件中加入权限声明。同时在安装应用程序时,用户可以知晓该程序一共申请了哪些权限,从而决定是否要安装这个程序。
-
Android这种权限设计思路很简单,决定权在用户手上,但是现实生活中,很多软件会申请一些不必要的权限,如果我们拒绝它的权限申请,就没法安装使用。
3、运行时申请权限
-
在Android6.0之后,加入了运行时权限功能,用户不需要在安装的时候一次性授权软件所有申请的权限,而是在使用过程中,根据用户自己实际情况进行授权,这样就可以在拒绝某项权限申请的同时使用软件其它功能,而不是无法安装。
-
Android目前将所有的权限归为两类,一类是普通权限,一类是危险权限。对于不会直接威胁到用户的安全和隐私的权限,系统会自动进行授权,只有危险权限申请才需要经过用户手动授权。具体危险权限如下:
-
运行时权限的实现步骤如下:
-
首先就是要判断用户是不是已经给过应用授权,借助的是ContextCompat.checkSelfPermission()方法,该方法接收两个参数,第一个参数是Context,第二个参数是具体的权限名。然后使用该方法的返回值与PackageManager.PERMISSION_GRANTED做比较,相等则代表用户已经授权,不等就表示用户没有授权。
-
如果已授权,直接执行相关权限的逻辑操作,如果没有授权,则需要调用ActivityCompat.requestPermissions()方法向用户申请授权,requestPermissions()方法接收3个参数,第一个参数要求是Activity的实例,第二个参数是一个String数组,存放权限名,第三个参数是请求码,只要是唯一值就行。
-
调用requestPermissions()方法之后,系统会弹出一个权限申请的对话框,然后用户选择同意或拒绝我们的权限申请。
-
不论结果如何,最终都会回调到onRequestPermissionResult()方法,授权的结果封装到grantResults参数。这里只需要判断授权结果再执行相应的逻辑就行。
-
-
简单例子如下:
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Button makecall = (Button)findViewById(R.id.make_call); makecall.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if(ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED){ ActivityCompat.requestPermissions(MainActivity.this,new String[]{Manifest.permission.CALL_PHONE},1); }else { call(); } } }); } private void call(){ try{ Intent intent = new Intent(Intent.ACTION_CALL); intent.setData(Uri.parse("tel:10086")); startActivity(intent); }catch (SecurityException e){ e.printStackTrace(); } } }
4、访问其它程序中的数据
-
对于每一个应用程序来说,如果想要访问内容提供器中共享的数据,需要借助ContentResolver类,该类提供了一系列的方法对于数据进行CRUD操作。与SQLiteDatabase中的几个方法类似,在参数上有区别。
-
ContentResolver类中的CRUD方法,使用一个Uri参数,该参数为内容提供器中的数据建立了唯一标识符,由authority和path两部分组成:
- authority:对应包名.provider.
- path:对不同的数据表进行区分.
- 例:content://com.example.provider/table1
-
得到字符串内容后,需要使用Uri.parse()方法将URI字符串解析成Uri对象作为参数传入。
- 例:Uri uri = Uri.parse("content://com.example.provider/table1")
-
随后即可调用getContentResolver.query()方法进行数据查询:
Cursor cursor = get ContentResolver().query( uri, projection, selection, selectionArgs, sortOrder);
-
查询完成后,返回一个Cursor对象,此时通过移动游标的位置来遍历Cursor,再取出对应的数据:
if(cursor!=null){ while(cursor.moveToNext()){ String column1 = getString(cursor.getColumnIndex("column1"); } }
-
以访问联系人信息为例:
private void readContacts() { Cursor cursor = null; try { cursor = getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI,null,null,null,null); if (cursor != null){ while (cursor.moveToNext()){ String displayName = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME)); String number = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER)); contactsList.add(displayName+"\n"+number); } adapter.notifyDataSetChanged(); } } catch (Exception e) { e.printStackTrace(); }finally { if (cursor != null){ cursor.close(); } } }
5、创建内容提供器并实现跨程序数据共享
-
为了实现跨程序共享数据的功能,官方推荐的方式是使用内容提供器,新建一个类继承ContentProvider,重写6个抽象方法。
-
随后借助UriMatcher这个类实现匹配内容URI的功能。主要利用到addURI()方法,接收三个参数,authority,path和自定义代码。简单例子如下:
static { uriMatcher = new UriMatcher(UriMatcher.NO_MATCH); uriMatcher.addURI(AUTHORITY,"book",BOOK_DIR); uriMatcher.addURI(AUTHORITY,"book/#",BOOK_ITEM); }
-
一个简单的内容提供器实例:
public class DatabaseProvider extends ContentProvider { public static final int BOOK_DIR = 0; public static final int BOOK_ITEM = 1; public static final String AUTHORITY = "com.example.databasetest.provider"; private static UriMatcher uriMatcher; private MyDatabaseHelper dbHelper; static { uriMatcher = new UriMatcher(UriMatcher.NO_MATCH); uriMatcher.addURI(AUTHORITY,"book",BOOK_DIR); uriMatcher.addURI(AUTHORITY,"book/#",BOOK_ITEM); } public String getType(Uri uri) { // TODO: Implement this to handle requests for the MIME type of the data // at the given URI. switch (uriMatcher.match(uri)){ case BOOK_DIR: return "vnd,android.cursor.dir/vnd.com.example.databasetest.provider.book"; case BOOK_ITEM: return "vnd.android.cursor.item/vnd.com.example.databasetest.provider.book"; } return null; } public boolean onCreate() { // TODO: Implement this to initialize your content provider on startup. dbHelper = new MyDatabaseHelper(getContext(),"BookStore.db",null,1); return true; } public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { SQLiteDatabase db = dbHelper.getReadableDatabase(); Cursor cursor =null; switch (uriMatcher.match(uri)){ case BOOK_DIR: cursor = db.query("Book",projection,selection,selectionArgs,null,null,sortOrder); break; case BOOK_ITEM: String bookId = uri.getPathSegments().get(1); break; default: break; } return cursor; // TODO: Implement this to handle query requests from clients. }
-
新建ProviderTest项目,对Database项目中内容提供器中添加的数据进行查询操作,核心代码如下:
Uri uri = Uri.parse("content://com.example.databasetest.provider/book"); Cursor cursor = getContentResolver().query(uri,null,null ,null,null); if(cursor != null){ while (cursor.moveToNext()){ String name = cursor.getString(cursor.getColumnIndex("name")); String author = cursor.getString(cursor.getColumnIndex("author")); int pages = cursor.getInt(cursor.getColumnIndex("pages")); double price = cursor.getDouble(cursor.getColumnIndex("price")); Log.d("MainActivity","book name is"+name); Log.d("MainActivity","book author is "+author); Log.d("MainActivity","book pages is "+pages); Log.d("MainActivity","book price is "+price); } cursor.close(); } }
三、手机多媒体的运用
1、通知的使用
-
当某个应用程序希望向用户发出一些提示信息,而该应用程序又不在前台运行时,可以借助通知实现。就是状态栏中的信息。
-
首先需要调用Context的getSystemService()方法获取一个NotificationManager来对通知进行管理,该方法接收一个字符串参数用于确定获取系统的哪个服务。简单例子如下:
NotificationManager manager = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);
-
然后通过使用一个Builder构造器来创建Notification对象。
Notification notification = new NotificationCompat.Builder(context).build();
-
在调用最终的build()方法之前,可以连缀任意多的设置方法。
.setContentTitle() //指定通知的标题内容 .setContentText() //指定通知的正文内容 .setWhen() //指定通知被创建的时间 ······
-
以上工作完成后,只需要调用NotificationManager的notify()方法就可以让通知显式出来了。该方法接收两个参数,第一个参数是id,每个通知都是不同的,第二个参数是Notification对象。
manager.nofify(1,notification);
-
简单例子:
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Button sendNotice = (Button) findViewById(R.id.send_notice); sendNotice.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { switch (v.getId()){ case R.id.send_notice: NotificationManager manager = (NotificationManager)getSystemService(NOTIFICATION_SERVICE); Notification notification = new NotificationCompat.Builder(MainActivity.this) .setContentTitle("This is content title") .setContentText("This is content text") .setWhen(System.currentTimeMillis()) .setSmallIcon(R.mipmap.ic_launcher) .setLargeIcon(BitmapFactory.decodeResource(getResources(),R.mipmap.ic_launcher)) .build(); manager.notify(1,notification); break; default: break; } } }); } }
-
按理来说,此时点击该条通知应该会有相应的点击效果,这个时候需要用到PendingIntent,与Intent类似,但PendingIntent更加倾向于在某个合适的时机执行某个动作,可以理解为延迟执行的Intent。NotificationCompat.Builder中,可以连缀一个setContentIntent()方法,接收的参数正是一个PendingIntent对象。简单例子如下:
Intent intent = new Intent(this, NotificationActivity.class); final PendingIntent pi = PendingIntent.getActivities(this,0, new Intent[]{intent},0); .....// .setContentIntent(pi)
-
如果没有在代码中对通知进行取消,它会一直显示在系统的状态栏上。两种解决方法如下:
-
在NotificationCompat.Builder中再连缀一个setAutocancel()方法。
-
调用NotificationManager
的cancel()方法取消。
-
2、由于该章节与之后毕设内容相关性不大,先放这。。
四、网络技术
1、WebView的用法
-
Android为了解决在应用中加载和显式网页的需求,提供了一个WebView控件,借助此控件可以在应用中嵌入一个浏览器,用来展示各种网页。
-
常用方法:
- getSettings():设置一些浏览器的属性。
- setWebViewClient:设置该方法后不需要使用系统浏览器来打开网页。
- loadUrl():加载网页。
-
简单例子如下:
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); WebView webView = (WebView) findViewById(R.id.web_view); webView.getSettings().setJavaScriptEnabled(true); webView.setWebViewClient(new WebViewClient()); webView.loadUrl("http://www.baidu.com"); } }
2、使用http协议访问网络
-
Android6.0系统中,HttpClient由于API数量过多,扩展困难等缺点被移除,官方建议使用HttpURLConnection
,具体用法如下: -
首先new出一个URL对象,传入目标的网络地址,然后获取HttpURLConnection实例,调用openConnection方法。如下:
URL url = new URL("http://www.baidu.com"); HttpURLConnection connection = (HttpURLConnection) url.openConnection();
-
得到HttpURLConnection实例后,设置HTTP请求所使用的方法。GET表示希望从服务器那里获取数据,而POST表示希望提交数据给服务器。如下:
connection.setRequestMethod("GET");
-
之后再调用getInputStream()方法就可以获取到服务器返回的输入流了,剩下的任务就是对输入流进行读取。如下:
InputStream in = connection.getInputStream();
-
最后调用disconnect()方法关闭HTTP连接。如下:
connection.disconnect();
-
简单实例如下:
public class MainActivity extends AppCompatActivity { TextView responseText; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); final Button sendRequest = (Button) findViewById(R.id.send_request); responseText = (TextView) findViewById(R.id.response_text); sendRequest.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { sendRequestWithHttpURLConnection(); } }); } private void sendRequestWithHttpURLConnection() { new Thread(new Runnable() { @Override public void run() { HttpURLConnection connection = null; BufferedReader reader = null; try { URL url = new URL("http://www.baidu.com"); connection = (HttpURLConnection) url.openConnection(); connection.setRequestMethod("GET"); connection.setConnectTimeout(8000); connection.setReadTimeout(8000); InputStream in = connection.getInputStream(); reader = new BufferedReader(new InputStreamReader(in)); StringBuilder response = new StringBuilder(); String line; while ((line = reader.readLine())!=null){ response.append(line); } showResponse(response.toString()); } catch (Exception e) { e.printStackTrace(); } } }).start(); } private void showResponse(final String response) { runOnUiThread(new Runnable() { @Override public void run() { responseText.setText(response); } }); } }
-
如果是要提交数据到服务器,将GET方法改为POST,并在获取输入流之前把要提交的数据写出。如下:
connection.setRequestMethod("POST"); DataOutputStream out = new DataOutputStream(connection.getOutputStream());
3、OkHttp的使用
-
Okhttp是出色的网络通信库之一,目前已成为首选。
-
在使用之前需要在项目中添加Okhttp库的依赖。
implementation("com.squareup.okhttp3:okhttp:4.2.2") implementation("com.squareup.okio:okio:2.4.2")
-
首先创建一个OkHttpClient实例,再创建一个Request实例,发起HTTP请求。
OkHttpClient client = new OkHttpClient(); Request request = new Request.Builder().url("http://www.baidu.com").build();
-
随后调用OkHttpClient的newcall()方法来创建一个Call对象,并调用它的execute()方法来发送请求并获取服务器返回的数据,其中Response对象就是服务器返回的数据:
Response response = client.newcall(request),execute(); String responseData = response.body().string();
-
如果是发起一条POST请求,会比GET请求稍微复杂一点,需要先构造出一个RequestBody对象来存放待提交的参数,如下:
RequestBody requestBody = new FormBody.Builder() .add("username","admin") .add("password","123456") .build();
-
然后在Request.Builder中调用post()方法,将RequestBody对象传入:
Request request = new Request.Builder() .url("http://www.baidu.com") .post(requestBody) .build();
-
运行效果与HttpURLConnection一致。
五、探究服务
1、简介
- 服务是Android中实现程序后台运行的解决方案,它适合去执行那些不需要和用户交互而且还要求长期运行的任务。不依赖任何用户界面,即使程序被切换到后台,或者用户打开了另外一个应用,服务仍然能够正常运行。
- 服务依赖于创建服务时所在的应用程序进程,当某个应用程序进程被kill掉时,所有依赖于该进程的服务也会停止运行。
- 服务默认运行在主线程中,需要在服务的内部手动创建子线程,并在子线程中执行具体的任务。
2、Android多线程编程
2.1 线程的基本用法
-
定义一个类继承自Thread,然后重写父类的run()方法,并在里面编写耗时逻辑:
class MyThread extends Thread{ @Override public void run(){ //处理逻辑 } }
-
启动线程需要new出线程实例,调用start()方法:
new MyThread().start();
-
目前最常用的写法是使用匿名类的方法:
new Thread(new Runnable(){ @Override public void run(){ //处理逻辑 } }).start();
2.2 子线程中更新UI
-
Android不允许在子线程中进行UI,这个时候需要使用到Android提供的异步消息处理机制。一个简单的例子如下:
public class MainActivity extends AppCompatActivity { private TextView text; public static final int UPDATE_TEXT = 1; private Handler handler = new Handler(){ @Override public void handleMessage(Message msg){ switch (msg.what){ case UPDATE_TEXT: text.setText("NICE to meet you"); Toast.makeText(MainActivity.this,"修改成功", LENGTH_SHORT).show(); break; default: break; } } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); text = (TextView)findViewById(R.id.text); Button changeText = (Button)findViewById(R.id.change_text); changeText.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Message message = new Message(); message.what = UPDATE_TEXT; handler.sendMessage(message); } }); } }
2.3 异步消息处理机制:
-
Android中的异步消息处理主要由4个部分组成:
-
Message:在线程之间传递的消息,可以在内部携带少量信息,用于在不同线程之间交换数据。
-
Handler:用于放松和处理消息,分别对应Handler的sendMessage()方法于handleMessage()方法。
-
MessageQueue:消息队列,用于存放所有通过Handler发送的消息,每个线程中只会由一个MessageQueue对象。
-
Looper:每个线程的MessageQueue管家,调用Looper的loop()方法后,每当MessageQueue中存在一条消息,便会取出并传递到Handler的handleMessage()方法中,每个线程中也只会由一个Looper对象。
-
2.4 AsyncTask
-
为了更加方便在子线程中对UI进行操作,Android提供了AsyncTask工具,其背后的实现原理基于异步消息处理机制,只是做了相应的封装。
-
使用AsyncTask需要创建一个子类继承它,继承时需要为AsyncTask类指定3个泛型参数:
-
Params:执行AsyncTask时需要传入的参数,可用于在后台任务中使用。
-
Progress:后台任务执行时,如果需要在界面上显示当前的进度,则使用这里指定的泛型作为进度单位。
-
Result:当任务执行完毕后,如果需要对结果进行返回,则使用这里指定的泛型作为返回值类型。
-
简单例子如下:
class DownloadTask extends AsyncTask
{ ··· }
-
-
需要重写的四个方法如下:
- onPreEXEcute():这个方法会在后台任务开始执行之前调用,用于进行一些界面上的初始化操作。
- doInBackground(Params...):这个方法中的所有代码都会在子线程中运行,在这里处理所有的耗时任务,一旦完成通过return语句来将任务的执行结果返回。
- onProgressUpdate(Params...):当在后台任务中调用了publishProgress(Progress...)方法后,该方法会被调用,携带的参数就是在后台任务中传递过来的,在这个方法中可以对UI进行操作。
- onPostExecute(Result):后台任务执行完毕并通过return语句进行返回时,该方法被调用。
-
总体原则:在doInBackground()方法中执行具体的耗时任务,在onProgressUpdate()方法中进行UI操作,在onPostExecute()方法中执行任务的收尾,启动任务只需要编写以下代码:
new AsyncTask().execute();
3、服务的基本用法
-
创建一个Service类后,需要重写部分方法,其中最常用的如下:
- onCreate():服务第一次创建时被调用。
- onStartCommand():每次服务启动后执行的操作。
- onDestroy():服务销毁时回收一些不再使用过的资源。
-
服务的启动与关闭:首先需要构建一个Intent对象,并调用Context类中的startService()方法来启动MyService这个服务。(服务的关闭一样)
4、活动与服务的通信
-
为了让服务与活动的联系更加紧密,Service类中提供了一个onBind方法,创建一个专门的Binder对象来对服务的功能进行管理。
-
首先需要新建一个类继承自Binder,在其内部提供具体方法。随后在服务类中创建Binder实例,在onBind()方法里返回这个实例。
private DownloadBinder mbinder = new DownloadBinder(); class DownloadBinder extends Binder { public void startDownload(){ Log.d("MyService","startDownload executed"); } public int getProgress(){ Log.d("MyService","getProgress executed"); return 0; } } public IBinder onBind(Intent intent) { // TODO: Return the communication channel to the service. return mbinder; }
-
第二步是在需要与服务绑定的活动中创建一个ServiceConnection的匿名类,重写onServiceConnected()方法与onServiceDisconnected()方法。这两个方法会在活动与服务成功绑定以及解除绑定的时候调用。在onServiceConnected()方法中,可以获取一个binder实例,调用其中的方法。
private MyService.DownloadBinder downloadBinder; private ServiceConnection connection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { downloadBinder = (MyService.DownloadBinder)service; downloadBinder.startDownload(); downloadBinder.getProgress(); } @Override public void onServiceDisconnected(ComponentName name) { } };
-
最后就是调用bindService()方法将活动与服务进行绑定。该方法接收3个参数,第一个是Intent对象,第二个参数是ServiceConnection实例,第三个参数是一个标志位。
case R.id.bind_service: Intent bindIntent = new Intent(this,MyService.class); bindService(bindIntent,connection,BIND_AUTO_CREATE); default: break;
5、服务的更多技巧
5.1 前台服务
-
服务几乎都是在后台运行的,但是系统出现内存不足的情况时,就有可能回收掉正在后台运行的服务,如果需要服务可以一直保持运行状态,可以使用前台服务。核心是调用startForeground()方法使服务类变成一个前台服务,并在系统状态栏显示出来。
-
一个简单的例子如下:
public void onCreate() { super.onCreate(); Log.d("MyService","onCreate execute"); Intent intent = new Intent(this,MainActivity.class); PendingIntent pi = PendingIntent.getActivity(this,0,intent,0); Notification notification = new NotificationCompat.Builder(this) .setContentText("This is content text") .setContentTitle("This is content title") .setWhen(System.currentTimeMillis()) .setSmallIcon(R.mipmap.ic_launcher) .setLargeIcon(BitmapFactory.decodeResource(getResources(),R.mipmap.ic_launcher)) .setContentIntent(pi) .build(); startForeground(1,notification); }
5.2 使用IntentService
-
服务中的代码很多都是默认运行在主线程中,所以应该在服务的每个具体的方法里开启一个子线程用来处理那些耗时的逻辑,防止出现ANR。
-
Android专门提供了一个IntentService类,解决忘记开启线程或忘记调用stopSelf()方法的尴尬。只需要在其内部调用父类的有参构造函数,然后实现onHandleIntent()这个抽象方法,在此方法中处理一些耗时逻辑(这个方法本身是在子线程中运行的)。
public class MyIntentService extends IntentService{ public MyIntentService(){ super("MyIntentService") } @Override protected void onHandleIntent(Intent intent){ //耗时逻辑 } @Override public void onDestroy(){ super.onDestroy(); } }
待解决的问题&下周的计划
问题
- 这周由于学习内容与上周相比难度较大,内容较多,同时还有一些其它杂七杂八的事情需要处理,没能完成既定的学习任务,需要面壁思过。。
下周的计划
- 这一周完成了Android应用开发基础知识的学习,下一周就开始进入Android安全方面的学习与研究,争取实现一些简单的攻击Demo,再研究其防护措施。如果还有剩余的精力,开始进入Android逆向的学习。