回到目录
内容总结
- 先说些题外话,如各位所见,这是PoRE Labs系列最后一篇更新的博客,虽然是第二个Lab。究其原因,因为它难度不低,哪怕是这会我也不敢保证我写的这些学术垃圾——可能都算不上学术垃圾——这些垃圾代码是否完全正确。这个Lab本身难度也很大,尤其是对于绝大多数同学来说,本就是初学Java,然后三节课又速成Android Programming,网上的教程虽多,但平心而论质量参差不齐。关于这个Lab,其实也有学姐写过,这里我的博客仅供大家参考,不保证代码完全正确。
- 那么回到这节课的内容,来总结一下课件上的内容。关于所有内容,如果有误,或不准确的,建议参考这里。此外,这里的总结内容仅针对考试内容,实际Lab完成中不限这些。
App Component(应用组件):作为Android应用的基本构建块,一共分成四类:Activity、Service、Broadcast Receiver、Content Provider。对于前两个,请注意它们的生命周期(Lifecycle),在课件中会有精确的图展示。在Lab和本课程的开发中,大多聚焦于前三个组件。
Intent and Intent Filter:Intent(意图),可以认为是不同组件之间用于传递消息的对象,包括但不限于启动活动(Activity)、启动服务、传递广播等。同样,意图也分为显式意图(Explicit Intent)和隐式意图(Implicit Intent)。而Intent Filter,则是作为组件接受隐式意图的一个判断条件,当一个隐式意图可以通过一个这个组件的预设的过滤器时,这个意图才会被接收。需要在Manifest中通过intent-filter元素声明过滤器。
MAC & DAC:MAC(Mandatory Access Control,强制访问控制),由操作系统或系统管理员限制和控制访问权限。DAC(Discretionary Access Control,自主访问控制),由文件客体的所有者(及管理员)控制和管理访问权限。(部分参考)
Android Permission Model:分为Install-time Permissions(Android 5.1 or earlier)和Run-time Permissions(Android 6.0 and later)。四种保护级别(Protection Level),分别为:normal、dangerous、signature、signatureOrSystem。
Lab简介与参考
- 在开始开发之前,需要搭建Android Studio以及其AVM的环境,当中可能会遇到诸多问题,如.gradle等等。关于这系列的问题,可以在站点的Q&A Site、或者是网上搜索得到。
- 这个Lab一共分成4个Task。
- Task 1: What’s in background
事实证明,这个Task是最复杂的……需要我们做这些事:
(1) 注册一个广播接收器,在开机时自动启动,并且可以未经用户授权开启后台服务;
(2) 获得设备的GPS定位,每3秒打印出经纬度;
(3) MainActivity中需要询问Location权限。
代码如下:
SecretBootReceiver.java
package com.smali.secretchallenge;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
public class SecretBootReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
//Call the service named SecretService
context.startService(new Intent(context,SecretService.class));
}
}
这个应该没有什么难度:就是简单地实现BroadcastReceiver这一接口,然后使用Intent启动服务。
SecretService.java
package com.smali.secretchallenge;
import android.Manifest;
import android.app.IntentService;
import android.content.Intent;
import android.content.Context;
import android.content.pm.PackageManager;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Bundle;
import android.os.IBinder;
import android.support.v4.app.ActivityCompat;
import android.widget.Toast;
import java.util.List;
public class SecretService extends IntentService {
public SecretService() {
super("SecretService");
}
private Intent intent_copy;
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
intent_copy=intent;
onHandleIntent(intent);
return START_REDELIVER_INTENT;
}
@Override
public IBinder onBind(Intent intent) {
//Not allowed.
return null;
}
@Override
public void onHandleIntent(Intent intent) {
LocationManager locationManager = (LocationManager)getSystemService(Context.LOCATION_SERVICE);
if (checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
Toast.makeText(this, "No Permission!", Toast.LENGTH_LONG).show();
onDestroy();
}
List<String> providerList = locationManager.getProviders(true);
String provider;
if (providerList.contains(locationManager.GPS_PROVIDER)) {
provider=locationManager.GPS_PROVIDER;
}
else if (providerList.contains(locationManager.NETWORK_PROVIDER)) {
provider=locationManager.NETWORK_PROVIDER;
}
else {
Toast.makeText(this,"No Location Provider :)",Toast.LENGTH_LONG).show();
return;
}
locationManager.requestLocationUpdates(provider, 3000, 10, new LocationListener() {
@Override
public void onLocationChanged(Location location) { }
@Override
public void onStatusChanged(String provider, int status, Bundle extras) { }
@Override
public void onProviderEnabled(String provider) { }
@Override
public void onProviderDisabled(String provider) { }
});
Location location = locationManager.getLastKnownLocation(provider);
if (location==null) {
Toast.makeText(this,"location is null",Toast.LENGTH_LONG).show();
}
String accuracy = Float.toString(location.getAccuracy());
String altitude = Double.toString(location.getAltitude());
String latitude = Double.toString(location.getLatitude());
String text = "getAccuracy:" + accuracy + "\n" + "getLatitude:" + latitude + "\n" + "getAltitude:" + altitude;
Toast.makeText(this, text, Toast.LENGTH_SHORT).show();
try {
wait(3000);
} catch (Exception e) {
e.printStackTrace();
}
onDestroy();
}
@Override
public void onDestroy() {
try {
wait(3000);
} catch (Exception e) {
e.printStackTrace();
}
startService(new Intent(this,SecretService.class));
super.onDestroy();
}
}
这里围绕代码说明几点:
(1) 最致命的一个地方是按照代码预设,确实是三秒更新一次经纬度,但Toast的打印的3秒效果没有实现(就是这个Toast会一直显示,而不是隐藏后又显示,这里当初是因为时间原因没有实现完)。也就是说,里面的几句wait(3000);似乎并没有什么用。
(2) 这里实现的Service是一个变种IntentService,关于具体细节可以见这里。作为特别的IntentService,需要特别实现的方法就是onHandleIntent()。由于IntentService在处理完所有请求后会结束,因此我这里的预期是结束后再重新启动本Service,没有测试过这是不是导致Toast效果一直不理想的原因。
(3) 这里关于providerList等的使用,都是参考网上的博客。随后大部分内容应该还比较正常……
MainActivity.java
// Task 1 TODO: How to dynamic request some sensitive permission? And how to monitor the BOOT_COMPLETED action to start a secret service to toast?
// Launch a request for relevant permission.
if (checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
//No location permissions.
requestPermissions(new String[] { Manifest.permission.ACCESS_FINE_LOCATION },0);
}
这部分应该还比较正常,参考的是这里。
- Task 2: Read the file and change the text in the screen
这个Task主要聚焦于文件的读,以及对应UI的改变。这个应该比较简单,查清对应的API就可以了。
//private String FILENAME = "hello_file";
//This is the String you get from the EditText view.
final String input = input_edit.getText().toString();
//Task 2 TODO:How to read a file from the internal storage? And how to change the text that showed in the MainActivity?
byte[] byteBuffer = new byte[99];
String text = "";
try {
FileInputStream fis = openFileInput(FILENAME);
fis.read(byteBuffer);
text = new String(byteBuffer,"utf-8");
fis.close();
} catch (Exception e) {
e.printStackTrace();
}
tv.setText(text);
- Task 3: Change UI in the thread
也如助教的提示,这里需要使用Handler和Looper来处理。关于这两个类,以及Message的使用,可以在网上找到不少资源,这里不具体展开了。
//private Handler handler1;
//在Button.onClickListener内
//The App will crash if you click the button. So edit this class.
// Task 3 TODO: In child thread how to make Dialog in main thread?
// 传递input
Message msg = Message.obtain();
msg.what = 1;
msg.obj = input;
handler1.sendMessage(msg);
//在Button.onClickListener外
new Thread(new Runnable() {
@Override
public void run() {
// Make a dialog rather than edit the text in main UI
// tv.setText(input);
//Receive the message
Looper.prepare();
handler1 = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
final AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
builder.setTitle(msg.obj.toString());
builder.show();
}
};
Looper.loop();
}).start();
这里作出几点说明:
(1) 这里的话有两个匿名内部类,关于内部类及抽象类的方法重写(Override)概念如有问题,可以参照Java Programming或百度。
(2) 这里使用了AlertDialog.Builder类,关于Dialog类可参考这里,作为AlertDialog的使用可参考这里。
(3) 这里特别记录一个错误:如果在Builder构造函数的传参传入getApplicationContext()会报错,而像上面这样传入MainActivity.this则会安然无恙,这里是参考了StackOverflow的一则Q&A。(很可惜链接找不到了)
- Task 4: How to call that method?
这个Task可能是最简单的了,它甚至与Android Programming本身无关——它涉及Java的反射(Reflection)机制。先贴代码。
// Task 4 TODO: How to get the private variable of PoRELab class? And How to invoke the private method?
//PoRELab.publicMethod(input);
try {
PoRELab poreObject = new PoRELab();
Method method = PoRELab.class.getDeclaredMethod("privateMethod", String.class, String.class);
Field field = PoRELab.class.getDeclaredField("curStr");
method.setAccessible(true);
field.setAccessible(true);
String fieldValue = (String)field.get(poreObject);
method.invoke(poreObject,input,fieldValue);
} catch (Exception e) {
e.printStackTrace();
}
这里的话需要特别注意的是method.setAccessible(true);(field同理)这一行代码。由于这里的反射机制推出的是私有方法,如果不加这两行代码的话会提示找不到字段和方法。
写在最后
- 这个Lab,如果我没有记错的话,是从当时做直到学期结束,张老师及助教和同学们交流时,大家都反馈是“最难的”一个Lab。想象一下,一个没有Java基础的同学,一个礼拜速成Java,一个礼拜又速成Android开发,这确实有一些挑战性。抛开这个Lab的实用性不谈,本身的难度要求比较大,就要求学习能力要强,可能我不太适合……
- 感谢在做这个Lab时看到的,无论是有用的还是没用的那些博客,很可惜在目前总结的阶段已经找不到绝大多数博客了,只能找到一些偏官方性的文档了。