OSS存储服务
详细介绍请查看官方sdk文档:点击打开链接
说明:
参考手机端:加入用户体验改进计划
实际就是抓取系统logcat[包括异常],通过该日志回传给开发者,参照用户使用日志对系统的作出有效的改进。
触发点:广播
该功能涉及到1.监听系统捕捉日志
2.把日志上传到服务器
图示:
具体做法:
【一】Oss的存储【IntentService】
准备:OSSAndroid SDK
提示:文中的ID指的是AccessKey ID,KEY指的是AccessKey Secret
1.解压后在libs目录下得到jar包,目前包括aliyun-oss-sdk-android-2.2.0.jar、okhttp-
3.2.0.jar、okio-1.6.0.jar
2.将以上3个jar包导入工程的libs目录
3.引入lib后在
AndroidManifest.xml文件中配置这些权限
android:name="android.permission.INTERNET"/>
4.初始化OSSClient【来自SDK】
在IntentService中的onCreate()
初始化主要完成Endpoint设置、鉴权方式设置、Client参数设置。其中,鉴权方式包含明文设置模式、自签名模式、STS鉴权模式。鉴权细节详见后面的`访问控制`章节。
String endpoint = "http://oss-cn-hangzhou.aliyuncs.com"; // 明文设置secret的方式建议只在测试时使用,更多鉴权模式请参考后面的`访问控制`章节 OSSCredentialProvider credentialProvider = new OSSPlainTextAKSKCredentialProvider("", " OSS oss = new OSSClient(getApplicationContext(), endpoint, credentialProvider);");
以及需要创建断点记录文件夹
//断点数据保存的位置 Environment.getExternalStorageDirectory().getAbsolutePath() + "/oss_record/"; File recordDir = new File(recordDirectory); // 要保证目录存在,如果不存在则主动创建 if (!recordDir.exists()) { recordDir.mkdirs(); }
5.初始化之后就可以通过OSS上传文件,这里上传采用的分片上传。也就是说在上传过程中如果遇到网络突然中断了,oss会作相对应的记录保存上传的进度。当下一次重新上传该文件的时候直接从上次保存的进度开始
在onHandleIntent(Intentintent)中除了接收intent传递过来的path外还需要判断断点记录,如果存在首先把上一次的上传完毕后再处理当前的
至于上传具体细节请看SDKorDEMO中
还需要在清单文件中:注册
android:name=".myService">
说明: 为何采用IntentService【一般采用它来下载,上传也不例外啦】
IntentService是Service类的子类,用来处理异步请求。客户端可以通过startService(Intent)方法传递请求给IntentService。IntentService在onCreate()函数中通过HandlerThread单独开启一个线程来处理所有Intent请求对象(通过startService的方式发送过来的)所对应的任务,这样以免事务处理阻塞主线程。执行完所一个Intent请求对象所对应的工作之后,如果没有新的Intent请求达到,则自动停止Service;否则执行下一个Intent请求所对应的任务。
IntentService在处理事务时,还是采用的Handler方式,创建一个名叫ServiceHandler的内部Handler,并把它直接绑定到HandlerThread所对应的子线程。 ServiceHandler把处理一个intent所对应的事务都封装到叫做onHandleIntent的虚函数;因此我们直接实现虚函数onHandleIntent,再在里面根据Intent的不同进行不同的事务处理就可以了。
6.扩展模块就是文件的下载:
由于实际开发中暂时不考虑该问题,所以该模块暂未实现.
如有需求参考上传功能 ossSDK中也提供了该接口 :
//创建保存的文件路径
finalFile f1 = new File(filepath + name);
if(!f1.exists()) {
f1.delete();
}
。。。。。。。。.
//初始化oss
。。。。。。。。
//构造下载文件请求
GetObjectRequestget = new GetObjectRequest("fly","yao/test.txt");
OSSAsyncTasktask = oss.asyncGetObject(get, new OSSCompletedCallback() {
@Override
publicvoid onSuccess(GetObjectRequest request, GetObjectResult result) {
//请求成功
//Log.d("Content-Length", "" +getResult.getContentLength());
InputStreaminputStream = result.getObjectContent();
//通过outputStream直接输出文件【保存到本地】
OutputStreamos =null;
//byte[1024]这个长度主要看文件大小来定
byte[]buffer = new byte[1024];
intlen;
try{
os= new FileOutputStream(f1);
while((len = inputStream.read(buffer)) != -1) {
//处理下载的数据
os.write(buffer,0, len);
}
}catch (IOException e) {
e.printStackTrace();
}finally{
if(inputStream!=null) {
try{
inputStream.close();
}catch (IOException e) {
e.printStackTrace();
}
}
if(os!=null);
try{
os.close();
}catch (IOException e) {
e.printStackTrace();
}
}
}
@Override
publicvoid onFailure(GetObjectRequest request, ClientExceptionclientExcepion, ServiceException serviceException) {
//请求异常
if(clientExcepion != null) {
//本地异常如网络异常等
clientExcepion.printStackTrace();
}
if(serviceException != null) {
//服务异常
Log.e("ErrorCode",serviceException.getErrorCode());
Log.e("RequestId",serviceException.getRequestId());
Log.e("HostId",serviceException.getHostId());
Log.e("RawMessage",serviceException.getRawMessage());
}
}
});
::其他的一些逻辑问题暂时不作判断。
【二】广播接收者
1.主要是接收处理logcat广播,启动服务。
2.注册:静态注册:需要实时等待接收广播
图示:
2.扩展模块:网络的监听,文件的监听
网络监听:当没有网络的时候不开启服务,直接保存记录,发现有有网络的时候自动启动服务
文件监听:监听本地断点文件记录有没有发生变化
【三】UI
声明:(这个不是必须的)
正常情况下个人认为在车机设置里添加
这一项会比较人性化。
应为考虑到上传可能需要使用用户的流量,在用户不知情下有必要提醒用户
可以设置该功能默认是启动的。
也就是说默认情况下是在监听用户使用情况。
如果用户不开启该功能则不在上传日志文件。
【oss附加功能】
1.在oss初始化化的时候还可以:
也可以在初始化的时候设置详细的ClientConfiguration:
String endpoint = "http://oss-cn-hangzhou.aliyuncs.com";//
明文设置
secret
的方式建议只在测试时使用,更多鉴权模式请参考后面的访问控制章节
OSSCredentialProvider credentialProvider = new OSSPlainTextAKSKCredentialProvider("
", " "); ClientConfiguration conf = new ClientConfiguration();
conf.setConnectionTimeout(15 * 1000); //
连接超时,默认
15
秒
conf.setSocketTimeout(15 * 1000); // socket
超时,默认
15
秒
conf.setMaxConcurrentRequest(5); //
最大并发请求书,默认
5
个
conf.setMaxErrorRetry(2); //
失败后最大重试次数,默认
2
次
OSS oss = new OSSClient(getApplicationContext(), endpoint, credentialProvider, conf);
STS
鉴权模式
OSS可以通过阿里云STS服务,临时进行授权访问。阿里云STS(Security Token Service) 是为云计算用户提供临时访问令牌的Web服务。通过STS,您可以为第三方应用或联邦用户(用户身份由您自己管理)颁发一个自定义时效和权限的访问凭证,App端称为FederationToken。第三方应用或联邦用户可以使用该访问凭证直接调用阿里云产品API,或者使用阿里云产品提供的SDK来访问云产品API。
您不需要透露您的长期密钥(AccessKey)给第三方应用,只需要生成一个访问令牌并将令牌交给第三方应用即可。这个令牌的访问权限及有效期限都可以由您自定义。
您不需要关心权限撤销问题,访问令牌过期后就自动失效。
以APP应用为例,交互流程如下图:
方案的详细描述如下:
App用户登录。App用户身份是客户自己管理。客户可以自定义身份管理系统,也可以使用外部Web账号或OpenID。对于每个有效的App用户来说,AppServer可以确切地定义出每个App用户的最小访问权限。
AppServer请求STS服务获取一个安全令牌(SecurityToken)。在调用STS之前,AppServer需要确定App用户的最小访问权限(用Policy语法描述)以及授权的过期时间。然后通过调用STS的AssumeRole(扮演角色)接口来获取安全令牌。角色管理与使用相关内容请参考《RAM使用指南》中的角色管理。
STS返回给AppServer一个有效的访问凭证,App端称为FederationToken,包括一个安全令牌(SecurityToken)、临时访问密钥(AccessKeyId,AccessKeySecret)以及过期时间。
AppServer将FederationToken返回给ClientApp。ClientApp可以缓存这个凭证。当凭证失效时,ClientApp需要向AppServer申请新的有效访问凭证。比如,访问凭证有效期为1小时,那么ClientApp可以每30分钟向AppServer请求更新访问凭证。
ClientApp使用本地缓存的FederationToken去请求AliyunService API。云服务会感知STS访问凭证,并会依赖STS服务来验证访问凭证,并正确响应用户请求。
【工作总结】
从服务器上传下载文件这类型的开发,分2类把。
第一类是第三方服务器【比如:阿里云,
mbon
等等】这些都有相对应的SDK 所有使用起来也挺方便 都为开发者提供了相对完整的框架 接口。开发者只需要申请对应的账号,获得相对应的key秘钥,把第三方的SDK引入到工程等,接着就是调用接口对数据库的增删改查了
第二类就是自己搭建的本地服务器 代码:
MainActivity:
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); switchListener(); } /** * * 主要是对Switch控件的监听 * 事件的状态对应服务,广播的开启 * @author yao * @time 16-7-12 下午5:34 */ private void switchListener() { Switch aSwitch = (Switch) findViewById(R.id.switch1); aSwitch.setChecked(false); if (myApplication.SWITCH_ON.equals(SPUtils.readData(myApplication.SPUTILS_NAME,myApplication.SPUTILS_KEY))) { aSwitch.setChecked(true); } aSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton compoundButton, boolean b) { if (b) { Toast.makeText(MainActivity.this,myApplication.SWITCH_POINT, Toast.LENGTH_SHORT).show(); SPUtils.writeData(myApplication.SPUTILS_NAME,myApplication.SPUTILS_KEY, myApplication.SWITCH_ON); initReceiver(); } else { SPUtils.writeData(myApplication.SPUTILS_NAME,myApplication.SPUTILS_KEY, myApplication.SWITCH_OFF); destoryReceiver(); } } }); } /** *停止服务 * @author yao * @time 16-7-12 下午6:53 */ private void destoryReceiver() { Intent mintent = new Intent(); mintent.setAction("fly.upload.logcat.isok"); sendBroadcast(mintent); } /** *启动广播【开机状态】【触发事件】 * @author yao * @time 16-7-12 下午5:37 * 需要传递文件路径 才能上传服务器 */ private void initReceiver() { Log.d("123321", "initReceiver: 启动广播"); Intent intent = new Intent(); intent.putExtra("path", "null"); intent.setAction("fly.upload.logcat"); this.sendBroadcast(intent); }
android:elevation="10dp" android:layout_margin="20dp" android:layout_width="match_parent" android:layout_height="56dp"> android:layout_marginTop="10dp" android:layout_marginLeft="20dp" android:gravity="center|left" android:layout_width="match_parent" android:layout_height="38dp" android:text="加入用户体验改进计划" android:id="@+id/switch1" android:layout_gravity="right" /> android:gravity="center_horizontal" android:textSize="16sp" android:text="说明:这是一个 上传服务\n 通过广播和服务把文件提交到服务器中\n 广播地址:fly.upload.logcat\n 并且需要传文件路径\n intent.setAction(‘fly.upload.logcat’);\n intent.putExtra(key,value);\n sendBroadcast(intent);\n" android:layout_width="match_parent" android:layout_height="wrap_content" />
@Override public void onReceive(Context context, Intent intent) { Intent mintent = new Intent(context, myService.class); if (("ON".equals(SPUtils.readData("CONFIG","LOGIN")))&&intent.getAction().equals(Intent.ACTION_BOOT_COMPLETED)) { //开机启动服务 mintent.putExtra("path", ""); context.startService(mintent); } if (("ON".equals(SPUtils.readData("CONFIG","LOGIN")))&&intent.getAction().equals("fly.upload.logcat")) { String path = intent.getExtras().getString("path"); if (!path.equals("null")) { mintent.putExtra("path", path); context.startService(mintent); } } }服务:
public class myService extends IntentService{ private OSS oss; private String recordDirectory; private String action; public myService() {super("myService");} @Override public IBinder onBind(Intent intent) {return super.onBind(intent);} @Override //初始化oss工作 public void onCreate() {init();super.onCreate();} /* * 注意点: * 这里采用明文模式 * 实际中不可以采用 * */ private void init() { String endpoint = "http://oss-cn-shanghai.aliyuncs.com"; // 明文设置AccessKeyId/AccessKeySecret的方式建议只在测试时使用 OSSCredentialProvider credentialProvider = new OSSPlainTextAKSKCredentialProvider("jSSSSS65SSSSS51", "SSSSDADASDSDSDSDASDDt"); oss = new OSSClient(getApplicationContext(), endpoint, credentialProvider); //断点数据保存的位置 recordDirectory = Environment.getExternalStorageDirectory().getAbsolutePath() + "/oss_record/"; File recordDir = new File(recordDirectory); // 要保证目录存在,如果不存在则主动创建 if (!recordDir.exists()) { recordDir.mkdirs(); } } @Override protected void onHandleIntent(Intent intent) { //Intent是从Activity发过来的,携带识别参数,根据参数不同执行不同的任务 action = intent.getExtras().getString("path"); String s = SPUtils.readData("PATH", "OLD"); //判断上一次是否存在没有上传完毕的数据,如果存在着首先上传,否则直接上传本次数据 if (!"".equals(s)) { upLoad(s); } else { upLoad(action); } } //上传服务 private void upLoad(final String path) { // 创建断点上传请求,参数中给出断点记录文件的保存位置,需是一个文件夹的绝对路径 具体看sdk ResumableUploadRequest request = new ResumableUploadRequest("fly", "fly/"+path, path, recordDirectory); // 设置上传过程回调 request.setProgressCallback(new OSSProgressCallback() { @Override public void onProgress(ResumableUploadRequest request, long currentSize, long totalSize) { //因为没有涉及到ui 这里不处理 Log.d("123321", "onProgress: "+currentSize); } }); OSSAsyncTask resumableTask = oss.asyncResumableUpload(request, new OSSCompletedCallback , ResumableUploadResult>() { @Override public void onSuccess(ResumableUploadRequest request, ResumableUploadResult result) { //成功后重置记录 if (path.equals(SPUtils.readData("PATH", "OLD"))) { Intent mintent = new Intent(); mintent.setAction("fly.upload.logcat"); mintent.putExtra("path", action); sendBroadcast(mintent); } SPUtils.writeData("PATH", "OLD", ""); } @Override public void onFailure(ResumableUploadRequest request, ClientException clientExcepion, ServiceException serviceException) { // 请求异常 if (clientExcepion != null) { // 本地异常如网络异常等 clientExcepion.printStackTrace(); } if (serviceException != null) { // 服务异常 Log.e("ErrorCode", serviceException.getErrorCode()); Log.e("RequestId", serviceException.getRequestId()); Log.e("HostId", serviceException.getHostId()); Log.e("RawMessage", serviceException.getRawMessage()); } //失败后保存摘要,等待下次开机上传 SPUtils.writeData("PATH", "OLD", path); } }); } @Override public void onDestroy() { oss = null; recordDirectory = null; super.onDestroy(); }
public class SPUtils { /** 上下文 */ public static Context context; public static void setContext(Context context) { SPUtils.context = context; } /** * 写入首选项文件(.xml) * @param filename * @param key * @param value */ public static void writeData(String filename,String key,String value){ //实例化SharedPreferences对象,参数1是存储文件的名称,参数2是文件的打开方式,当文件不存在时,直接创建,如果存在,则直接使用 SharedPreferences mySharePreferences = context.getSharedPreferences(filename, Context.MODE_PRIVATE); //实例化SharedPreferences.Editor对象 SharedPreferences.Editor editor =mySharePreferences.edit(); //用putString的方法保存数据 editor.putString(key, value); //提交数据 editor.commit(); } /** * 从首选项中读取值 * @param filename * @param key */ public static String readData(String filename,String key){ //实例化SharedPreferences对象 SharedPreferences mySharePerferences = context.getSharedPreferences(filename, Context.MODE_PRIVATE); //用getString获取值 String name =mySharePerferences.getString(key, ""); return name; } /** * 获取全部的键值对 * @param filename * @return */ public static Map, ?> getAll(String filename) { SharedPreferences sp = context.getSharedPreferences(filename,Context.MODE_PRIVATE); return sp.getAll(); } /** * 查询某个key是否已经存在 * @param filename * @param key * @return */ public static boolean contains(String filename, String key) { SharedPreferences sp = context.getSharedPreferences(filename,Context.MODE_PRIVATE); return sp.contains(key); } /** * 移除某个值 * @param filename * @param key */ public static void remove(String filename, String key) { SharedPreferences sp = context.getSharedPreferences(filename, Context.MODE_PRIVATE); SharedPreferences.Editor editor = sp.edit(); editor.remove(key); editor.commit(); } }
public class myApplication extends Application { public static final String SWITCH_ON = "ON"; public static final String SWITCH_OFF = "OFF"; public static final String SPUTILS_NAME = "CONFIG"; public static final String SPUTILS_KEY = "LOGIN"; public static final String SWITCH_POINT = "感谢"; /* //文件路径名称 private final String LOGCAT_DIR = "logcat_flyaudio"; //内置路径 private final String PATH_HOME = "/storage/emulated/0"; //外置路径 private final String PATH_OTHER = "/storage/sdcard1";*/ //内置SDcard卡的路径 public static final String SDCARD_PATH = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator; @Override public void onCreate() { super.onCreate(); SPUtils.setContext(this); } }
android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> android:name="android.permission.CALL_PHONE"/> android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> android:name="android.permission.INTERNET"/> android:name="android.permission.ACCESS_NETWORK_STATE"/> android:name="android.permission.ACCESS_WIFI_STATE"/> android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> android:name="android.permission.READ_EXTERNAL_STORAGE"/> android:name=".myApplication" android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:supportsRtl="true" android:theme="@style/AppTheme"> android:name=".MainActivity" android:launchMode="singleTask"> android:name="android.intent.action.MAIN" /> android:name="android.intent.category.LAUNCHER" /> android:name=".myReceiver" android:exported="true"> android:name="android.intent.action.BOOT_COMPLETED"> android:name="fly.upload.logcat"> android:name="fly.upload.logcat.isok"> android:name=".myService">