为了保证App运行的流畅性,大部分耗时操作,比如文件的读/写,网络的请求等都不能放在主线程来实现,而是需要另开线程或者通过开启服务组件放在后台执行.

很多项目中都会有用户修改头像或者类似的功能。该功能会访问用户的相册、相机来获取图片,然后显示到页面上。实现该功能还是比较简单的,网上的资料也非常多,简单查阅之后复制粘贴便能实现,但是很多细节其实并不理解。并且由于Android安全性的提升,包括Android6.0的权限系统升级、Android7.0的私有文件访问限制,很多地方稍不注意就会发生崩溃。最近再次用到了这个功能,这次打算用一篇文章来详细记录这个功能点所对应的知识点,并解决掉之前的很多疑问。整个项目已经上传至GitHub,可下载安装进行体验。

本文为原创文章,如需转载请注明出处,谢谢!

多线程

使用多线程也可以实现类似后台服务的功能.Android中有三种实现多线程的方式.

匿名类

通过Thread类可以开启一个线程,
Runnable接口中的方法run,会在调用start方法开启线程后执行.

 //1匿名类 new Thread(new Runnable() { @Override public void run() { Log.i("Thread", "run: " + "Thread"); } }).start();

实现接口类

让TestThread类实现接口,然后传入开启的线程testThread.

public class TestThread implements Runnable { @Override public void run() { Log.i("TestThread", "run: " + "TestThread"); }}TestThread testThread = new TestThread();new Thread(testThread).start();

继承Thread类

让子类SonThread继承Thread,并实现run方法.

public class SonThread extends Thread { @Override public void run() { Log.i("SonThread", "run: " + "SonThread"); }}SonThread sonThread = new SonThread();sonThread.start();

子线程注意事项

通过Thread开启的子线程可以处理逻辑或数据操作,但是却不能更新UI.只有在主线程(original
thread)中,才可以更新UI.不同线程间的通信可以通过Handler类或者AsyncTask类来实现.

打开手机相册的方式有多种:第一种:

在平常的开发当中我们经常需要用到ViewPager,而与ViewPager结合使用的
功能一般是如下三种

异步消息处理机制

Handler

private TextView textView;public static final int UPDATE_TEXT = 1;private Handler handler = new Handler(){ @Override public void handleMessage(Message msg) { switch { case 1: textView.setText("Hello world!"); break; default: break; } }};new Thread(new Runnable() { @Override public void run() { Message msg = new Message(); msg.what = UPDATE_TEXT; handler.sendMessage.start();

当子线程完成任务后,通过Handler类发送带有参数的消息Message.然后handler接收到消息后,可以调用接口handleMessage来更新UI.

Hander处理异步消息,通过Message,MessageQueue,Looper来实现.MessageQueue相当于一个容器,用于存储线程中的Message消息.线程开启后,Looper会调用自身的loop方法,不断检测线程中的Message消息,发现后就会加入到MessageQueue中,可以把它看做MessageQueue的管家.Hander会根据Looper将Message加入MessageQueue的顺序,在主线程中进行处理.

AsyncTask

public class DownloadTask extends AsyncTask<Void, Integer, Boolean> { private ProgressBar progressBar; private Context context; public DownloadTask(ProgressBar progressBar, Context context){ this.progressBar = progressBar; this.context = context; } @RequiresApi(api = Build.VERSION_CODES.O) @Override protected void onPreExecute() { progressBar.setMin; progressBar.setMax; progressBar.setProgress; progressBar.setVisibility(View.VISIBLE); } @Override protected Boolean doInBackground(Void... voids) { int progress = 0; try{ while (progress < 10000){ progress += 1; publishProgress; } }catch (Exception e){ e.printStackTrace(); return false; } return true; } @Override protected void onProgressUpdate(Integer... values) { progressBar.setProgress(values[0]); } @Override protected void onPostExecute(Boolean aBoolean) { progressBar.setVisibility(View.INVISIBLE); if { Toast.makeText(context, "Success", Toast.LENGTH_SHORT).show(); }else { Toast.makeText(context, "Failure", Toast.LENGTH_SHORT).show(); } }}//调用new DownloadTask(progressBar, this).execute();

AsyncTask有三个泛型,第一个代表执行任务时参数类型,第二个是执行过程中的进度,最后一个是执行结果.
在onPreExecute方法中做一些执行任务前的辅助工作;
doInBackground方法用于处理耗时逻辑,执行的进度progress可以通过调用publishProgress方法传出,然后就会调用onProgressUpdate方法;在onProgressUpdate可以更新UI,显示在界面上;任务执行完毕后,会调用onPostExecute方法来返回任务执行的结果.

Intent intent = new Intent();intent.setAction(Intent.ACTION_PICK);// 设置文件类型intent.setType("image/*");activity.startActivityForResult(intent, requestCode);

1、ViewPager当中填充xml布局View2、ViewPager结合Fragment使用3、ViewPager结合ImageView,以及增加一些自定义的View使用,最典型的便是应用启动导航页

使用服务

服务类似子线程,可以很方便的处理耗时操作.

定义

public class TestService extends Service { public TestService() { } @Override public void onCreate() { super.onCreate(); } @Override public int onStartCommand(Intent intent, int flags, int startId) { return super.onStartCommand(intent, flags, startId); } @Override public void onDestroy() { super.onDestroy(); } @Override public IBinder onBind(Intent intent) { // TODO: Return the communication channel to the service. throw new UnsupportedOperationException("Not yet implemented"); }}

继承至Service,实现抽象方法onBind.其中onCreate方法在服务被创建时执行,一个服务只能执行一次;onStartCommand在Service被调用时执行,可以反复调用多次;
onDestroy方法在服务被停止时调用,一个服务也只能调用一次.

调用

正确调用Service,首先需要在AndroidManifest中的Application标签中进行注册.

<service android:name=".TestService" android:enabled="true" android:exported="true"></service>

通过Intent可以很方便的开启和关闭服务.

public void startService(View view){ Intent startIntent = new Intent(this, MyService.class); startService(startIntent);}public void stopService(View view){ Intent stopIntent = new Intent(this, MyService.class); stopService(stopIntent);}

通过Service的前台服务,可以让App运行的信息始终显示在手机通知栏中.

@Overridepublic void onCreate() { super.onCreate(); Log.i("MyService", "onCreate: " + "MyService"); Intent intent = new Intent(this, MainActivity.class); PendingIntent pi = PendingIntent.getActivity(this, 0, intent, 0); Notification notify = new NotificationCompat.Builder .setContentTitle("This is title") .setContentText("This is text") .setWhen(System.currentTimeMillis .setSmallIcon(R.mipmap.ic_launcher) .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher)) .setContentIntent .build(); startForeground(1, notify);}

通过IBinder在Activity和Service中进行通信

class MyBinder extends Binder { public void startDownload(){ Log.i("startDownload", "startDownload"); } public int getProgress(){ Log.i("getProgress", "getProgress"); return 0; }}private MyBinder myBinder = new MyBinder();@Overridepublic IBinder onBind(Intent intent) { return myBinder;}

修改MyService中IBinder的实现,并添加内部类MyBinder.然后可以在MainActivity通过ServiceConnection调用MyBinder中的方法.

private MyService.MyBinder downloadBinder;//通过serviceConnection可以获取服务的Binder, 然后调用Binder中的方法;private ServiceConnection serviceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName componentName, IBinder iBinder) downloadBinder = (MyService.MyBinder)iBinder; downloadBinder.startDownload(); downloadBinder.getProgress(); } @Override public void onServiceDisconnected(ComponentName componentName) { }};public void bindService(View view){ Intent bindIntent = new Intent(this, MyService.class); bindService(bindIntent, serviceConnection, BIND_AUTO_CREATE);}public void unbindService(View view){ Intent bindIntent = new Intent(this, MyService.class); unbindService(serviceConnection);}

销毁的三种方式

  1. 通过startService启动的服务,可以通过stopService的方式销毁.通过bindService启动的服务,可以通过unbindService的方式销毁.这两种方式,都会调用服务的onDestroy方法.如果在活动中既调用startService又调用bindService,那么就需要同时调用两者的停止方法来销毁.

2.在服务的执行方法onStartCommand调用stopSelf()方法.

3.让服务继承至IntentService.这样当服务中的onStartCommand方法执行完毕,会自动销毁.

第二种:

为了更好的理解ViewPager的使用,我特地写了一个模拟三种情况的demo供有需要的朋友参考。万变不离其宗,所有复杂的功能都可以在着基础上更改以达到效果。首先给大家上图看一下基本的效果:

总结

多线程和服务的存在,让一个App在运行期间,可以同时处理多个任务,保证了界面运行的流畅性.通过Thread类开启线程,将任务放在线程的run方法中.用Handle类和AsyncTask类可以实现子线程与主线程的通信.服务类似新开一个进程,通过Activity开启后会一直存在,直到调用stopSelf方法或者相应的关闭方法.为了防止服务忘记关闭或者关闭失败造成的资源浪费,可以让服务继承IntentService类.这样开启的服务,在任务执行完毕后会自动停止.

Intent intent = new Intent();intent.setAction(Intent.ACTION_GET_CONTENT);// 设置文件类型intent.setType("image/*");activity.startActivityForResult(intent, requestCode);

图片 1

第三种:

具体的实现过程就不赘述,代码中的注释我已经写的很清楚了。

Intent intent = new Intent();intent.setAction(Intent.ACTION_OPEN_DOCUMENT);// 设置文件类型intent.setType("image/*");activity.startActivityForResult(intent, requestCode);

最后在附上项目的github代码地址:github项目代码地址

这几种方式都可以在获取到读取文件权限的前提下,完美实现图片选择。第三种ACTION_OPEN_DOCUMENT是在Android5.0之后新添加的意图,如果使用的话需要进行

如果文章当中有任何不正确的地方,还请广大读者纠正,非常感谢!

if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.KITKAT){ //TODO}

我们这里先不介绍ACTION_OPEN_DOCUMENT。第二种ACTION_GET_CONTENT与第一种ACTION_PICK这两个意图类型的作用也非常类似,都是用来获取手机内容,包括联系人、相册等。通过intent.setType("image/*")来指定MIME
Type
,让系统知道要打开的应用。这里需要注意,必须指定MIME
Type
,否则项目会崩溃:

android.content.ActivityNotFoundException: No Activity found to handle Intent { act=android.intent.action.GET_CONTENT }android.content.ActivityNotFoundException: No Activity found to handle Intent { act=android.intent.action.ACTION_PICK }

根据不同的MIME
Type
,可以跳转到不同的应用。那么这两者有什么区别呢?ACTION_GET_CONTENT与ACTION_PICK的官方解释在这里。英语比较差,跟着百度翻译看了半天还是不懂。

图片 2英语好的同学可自行食用上面的链接,应该不需要翻墙。两者的区别介绍都写在了ACTION_GET_CONTENT,大概是在说:如果你有一些特定的集合想让用户选择,使用ACTION_PICK。如果让用户基于MIME
Type
选择数据,使用ACTION_GET_CONTENT。在平局的情况下,建议使用ACTION_GET_CONTENT。这个还是需要各位看官自己好好理解,我也没能完全了解两者的使用区别。并且我发现两者返回的Uri格式是不同的:关于Android中Uri的介绍,可以参考这篇文章。

两种意图分别唤起相册后,选择同一张图片的回调,也就是在onActivityResult中接收:

@Overrideprotected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (resultCode != RESULT_OK) { return; } switch (requestCode) { case REQUEST_CODE_ALBUM://相册 Uri dataUri = data.getData(); Log.i("mengyuanuri","uri:"+dataUri.getScheme()+":"+dataUri.getSchemeSpecificPart; break; }}

接下来我们来看看两个意图类型下选择同一张照片返回的数据:ACTION_GET_CONTENT:

content://com.android.providers.media.documents/document/image:2116

发表评论

电子邮件地址不会被公开。 必填项已用*标注