之前公司的apk更新都是一个链接跳转到应用市场,让用户去应用市场下载。但最近产品觉得要减少用户操作的步骤,将下载改为从自己的后台进行下载,直接在应用内更新。而且要求下载时要在界面上弹出一个显示进度的对话框,下载结束后跳转安装apk的界面。
好吧,需求来了咱们就得实现。这个其实不难,就是开个Service,之后在里面创建个子线程做网络文件下载,下载开始发个通知告诉界面弹个对话框。下载中不断发送下载进度更新下载进度的显示。下载完成后关闭对话框,并且启动安装apk的界面。
但是作为一个有追求的码农。我们要想一件事情,怎么架构这个下载操作会好一点。试想一下,这个Service只会用来下载apk么?后面完全可能会有下载文件,下载图片,下载数据库的需求。
我们发现,这些需求的大致流程都是一样的,比如都是下载开始,下载中,下载结果(成功,失败)。只是在部分状态可能有一些差别。比如apk下载的话,下载结束要开启安装apk的界面;下载文件下载结束不做什么操作;下载数据库,为了防止一些三方框架的缓存问题,下载结束我们可能需要重启app……那我们能不能共用这个Service代码,而对每个不同需求的特殊阶段又有特殊的实现呢?
接下来我们引进一下这个开发中非常常用的设计模式——模板方法模式。他不仅常用,在Android中还极其常见。后面我们会介绍Android中模板方法模式的应用,相信你一定会大吃一惊,因为你每天都在跟模板方法模式打交道。
先上模板方法模式的UML静态类图:
模板方法有这样几个主要的角色:
抽象父类:flow方法中封装了一套逻辑流程,这个流程一般是固定的。为了防止子类随意覆盖该方法,一般这个方法都会声明为final的。stepOne,stepTwo等方法是整个流程中的几个步骤,不同的实现可能在这几个步骤上有所差别。
实现子类:SubClass01,SubClass02等等是继承了抽象父类的子类。他们根据自己的特殊需要覆盖相应的步骤方法。
这样,在保证完整流程不变的情况下,对其中的细节步骤做了按需实现。
回到我们开头提到的例子,我们先把需求明确一下。
现在我们需要完成一个下载工具,暂定下载两种:
(1)下载数据库,下载时通过dialog显示进度,下载完成后消除dialog。
(2)下载apk,下载时通过通知栏显示进度,下载完成后消除通知栏。
一般下载我们都是在一个Service中开启一个子线程来进行。方便起见我们可以直接使用IntentService。下载部分我们这里就简单模拟一个下载过程,不去真的下载文件了。
(1)首先我们来完成抽象父类角色:
public abstract class DownloadService extends IntentService {
public DownloadService() {
super("DownloadService");
}
@Override
protected final void onHandleIntent(@Nullable Intent intent) {
//下载开始的回调
onDownloadStart();
int progress = 0;
while (progress < 100) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
progress++;
//下载中的回调
onDownloadProgress(progress);
}
//下载成功的回调
onDownloadSuccess();
}
protected void onDownloadStart() {
Log.d("Download", "Abs_DownloadStart");
}
protected void onDownloadProgress(int progress) {
Log.d("Download", "Abs_DownloadProgress:" + progress);
}
protected void onDownloadSuccess() {
Log.d("Download", "Abs_DownloadSuccess");
}
protected void onDownloadFail() {
Log.d("Download", "Abs_DownloadFail");
}
}
这里我们定义了四个回调方法,即下载开始,下载中进度更新,下载成功,下载失败。这四个步骤其实就是可能会随着需求不同,而导致细节变化的步骤。
onHandlerIntent方法其实就是UML中的flow方法。我们这里设置为final,防止子类随意重写固定的流程。里面我们仅仅是做了个假的更新,并不断更新Progress的值。
(2)实现下载apk的Service
public class DownloadAPKService extends DownloadService {
private NotificationManager mManager;
private NotificationCompat.Builder mBuilder;
private static final int NOTIFICATION_ID = 1001;
@Override
public void onCreate() {
super.onCreate();
mManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
}
@Override
protected void onDownloadStart() {
super.onDownloadStart();
mBuilder = new NotificationCompat.Builder(this);
mBuilder.setProgress(100, 0, false);
mBuilder.setSmallIcon(R.mipmap.ic_launcher);
mBuilder.setContentTitle("下载APK");
mBuilder.setContentText("正在下载");
mManager.notify(NOTIFICATION_ID, mBuilder.build());
}
@Override
protected void onDownloadProgress(int progress) {
super.onDownloadProgress(progress);
mBuilder.setProgress(100, progress, false);
mManager.notify(NOTIFICATION_ID, mBuilder.build());
}
@Override
protected void onDownloadSuccess() {
super.onDownloadSuccess();
mManager.cancel(NOTIFICATION_ID);
}
}
这里我们实际根据需要覆写了onDownloadStart方法,onDownloadProgress方法,onDownloadSuccess方法。在onDownloadStart方法中我们开启了一个通知栏。在onDownloadProgress方法中我们更新了通知栏的进度条。在onDownloadSuccess方法中我们关闭了通知栏。这样就实现了我们对于apk下载的需求。
(3)实现下载db的Service
这里为了便于弹出提示框和更新UI,我们还是借助一下EventBus吧。
首先贴一下提示框的布局和代码,这里我们只是简单写一下。
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="48dp">
<TextView
android:id="@+id/tv_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="下载db"
android:textColor="@android:color/black"
android:textSize="28sp" />
<TextView
android:id="@+id/tv_progress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/tv_title"
android:textColor="@color/colorPrimary"
android:textSize="48sp"
tools:text="80" />
<TextView
android:id="@+id/tv_percent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/tv_title"
android:layout_toRightOf="@+id/tv_progress"
android:text="%"
android:textColor="@android:color/black"
android:textSize="48sp" />
</RelativeLayout>
public class ProgressDialogFragment extends android.support.v4.app.DialogFragment {
public static final String PROGRESS_DIALOG_FRAGMENT_TAG = "PROGRESS_DIALOG_FRAGMENT_TAG";
private TextView tv_progress;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EventBus.getDefault().register(this);
}
@Nullable
@Override
public View onCreateView(
LayoutInflater inflater,
@Nullable ViewGroup container,
Bundle savedInstanceState
) {
super.onCreateView(inflater, container, savedInstanceState);
View view = inflater.inflate(R.layout.fragment_progress_dialog, null);
tv_progress = (TextView) view.findViewById(R.id.tv_progress);
return view;
}
@Subscribe(threadMode = ThreadMode.MAIN)
public void onReceivedDownloadDBProgressEvent(DownloadDBProgressEvent downloadDBProgressEvent){
tv_progress.setText(String.valueOf(downloadDBProgressEvent.getProgress()));
}
@Override
public void onDestroy() {
super.onDestroy();
EventBus.getDefault().unregister(this);
}
}
之后我们新建一个DownloadDBService类,继续继承我们的抽象父类DownloadService。
public class DownloadDBService extends DownloadService {
@Override
protected void onDownloadStart() {
super.onDownloadStart();
EventBus.getDefault().post(
new DownloadDBStartEvent()
);
}
@Override
protected void onDownloadProgress(int progress) {
super.onDownloadProgress(progress);
EventBus.getDefault().post(
new DownloadDBProgressEvent(progress)
);
}
@Override
protected void onDownloadSuccess() {
super.onDownloadSuccess();
EventBus.getDefault().post(
new DownloadDBSuccessEvent()
);
}
}
我们覆写了父类的onDownloadStart方法,onDownloadProgress方法和onDownloadSuccess方法。只是简单在这三个方法中用EventBus发送了相应事件,便于更新UI。
之后我们在MainActivity中添加两个方法,当接收到下载开始的事件时开启dialog,当接收到下载结束的事件时关闭dialog。
public class MainActivity extends AppCompatActivity {
private ProgressDialogFragment mProgressDialogFragment;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
EventBus.getDefault().register(this);
Button btn_download_apk = (Button) findViewById(R.id.btn_download_apk);
btn_download_apk.setOnClickListener(
new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent(MainActivity.this, DownloadAPKService.class);
startService(intent);
}
}
);
Button btn_download_db = (Button) findViewById(R.id.btn_download_db);
btn_download_db.setOnClickListener(
new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent(MainActivity.this, DownloadDBService.class);
startService(intent);
}
}
);
}
@Subscribe
public void onReceivedDownloadDBStartEvent(DownloadDBStartEvent downloadDBStartEvent){
mProgressDialogFragment = new ProgressDialogFragment();
mProgressDialogFragment.show(
getSupportFragmentManager(),
ProgressDialogFragment.PROGRESS_DIALOG_FRAGMENT_TAG
);
}
@Subscribe
public void onReceeivedDownloadDBSuccessEvent(DownloadDBSuccessEvent downloadDBSuccessEvent){
mProgressDialogFragment.dismiss();
}
@Override
protected void onDestroy() {
super.onDestroy();
EventBus.getDefault().unregister(this);
}
}
由于例子写的比较简单,我们就不上效果图了。
通过这个例子我们可以看到模板方法模式的好处。首先他保证了主流程不变,并且利用继承提高了代码的复用性。其次,它允许特殊需求的实现。可以看到我们的DownloadAPKService和DownloadDBService对几个步骤细节的实现是完全不一样的。最后,由于模板方法模式,使得DownloadAPKService和DownloadDBService解耦。并且如果后续有其他新的下载需求,再写一个Service即可。
想象一下如果不用模板方法模式的情况吧,一个Service里面根据传入的内容不同做一堆if判断。根据条件执行相应代码。后面只要来一个新需求,这个Service类的代码就得改动。而且随着需求越来越多,这个类会越来越庞大。我们现在知道了模板方法模式,回头看这种实现是不是觉得烂透了?
最后提几个Android中模板方法模式的运用吧。其实相信大家看了上面代码应该有答案。没错,我们整天打交道的Activity的生命周期方法,Fragment的生命周期方法,还有AsyncTask的几个回调方法用的就是模板方法模式。
好了,模板方法模式就介绍到此,希望对各位有所帮助。