android版本更新策略

屏幕快照 2016-06-28 02.00.08.png

開發(fā)中對(duì)版本進(jìn)行檢查并更新的需求基本是所有應(yīng)用必須有的功能义屏,可是在實(shí)際開發(fā)中有些朋友就容易忽略一些細(xì)節(jié)雇毫。

版本更新的基本流程:

一般是將本地版本告訴服務(wù)器嘁锯,服務(wù)器經(jīng)過(guò)相關(guān)處理會(huì)返回客戶端相關(guān)信息皱卓,告訴客戶端需不需要更新进宝,如果需要更新是強(qiáng)制更新還是非強(qiáng)制更新官卡』茸拢客戶端得到服務(wù)器返回的相關(guān)信息后再進(jìn)一步做邏輯處理。

強(qiáng)制更新:

一般的處理就是進(jìn)入應(yīng)用就彈窗通知用戶有版本更新寻咒,彈窗可以沒有取消按鈕并不能取消哮翘。這樣用戶就只能選擇更新或者關(guān)閉應(yīng)用了,當(dāng)然也可以添加取消按鈕毛秘,但是如果用戶選擇取消則直接退出應(yīng)用饭寺。

非強(qiáng)制更新

一般的處理是在應(yīng)用的設(shè)置中添加版本檢查的操作,如果用戶主動(dòng)檢查版本則彈窗告知用戶有版本更新叫挟。這時(shí)用戶可以取消或者更新艰匙。

功能實(shí)際是比較簡(jiǎn)單清晰的,但之所以寫這篇文章抹恳,是因?yàn)樵谖覀児镜囊粋€(gè)項(xiàng)目中员凝,我把這個(gè)模塊分給了一個(gè)有著4年工作經(jīng)驗(yàn)的哥們編寫,最后這哥們花了2個(gè)小時(shí)做完了奋献。我還想這哥們寫得挺快健霹,效率很高嘛,結(jié)果一測(cè)試發(fā)現(xiàn)問題不少:

  1. 進(jìn)入首頁(yè)前關(guān)閉網(wǎng)絡(luò)瓶蚂,進(jìn)入后刷新界面發(fā)現(xiàn)強(qiáng)制更新提醒沒有彈窗
  2. 再進(jìn)入其它界面也沒有任何更新提醒
  3. 在正常更新時(shí)點(diǎn)擊確定更新糖埋,沒有判斷網(wǎng)絡(luò)狀態(tài)(wifi,移動(dòng)網(wǎng)絡(luò))直接下載apk文件窃这,如果用戶在移動(dòng)網(wǎng)絡(luò)下將耗費(fèi)非常多的流量瞳别,直接影響用戶體驗(yàn)
  4. 下載過(guò)程在應(yīng)用內(nèi)沒有進(jìn)度條提醒,通知欄也沒有進(jìn)度提醒
  5. apk文件下載過(guò)程中杭攻,如果強(qiáng)制結(jié)束應(yīng)用洒试,下載被中斷
  6. apk如果正常下載下來(lái),彈出了安裝界面朴上,這時(shí)如果用戶取消了安裝回到應(yīng)用,在需要強(qiáng)制更新的情況下并沒有再次彈窗阻止用戶進(jìn)行任何其它操作卒煞,失去了強(qiáng)制更新的意義

首先聲明下痪宰,我這絲毫沒有吐槽的意思喲,只是想說(shuō)作為一個(gè)合格的程序員大家最起碼需要做到思維嚴(yán)謹(jǐn)這點(diǎn),在有能力的情況下對(duì)用戶體驗(yàn)?zāi)芴狳c(diǎn)建議最好衣撬。自己寫的代碼一定要經(jīng)過(guò)嚴(yán)格測(cè)試再交付乖订,不要指望測(cè)試人員幫你測(cè)試再去修改,你要知道現(xiàn)在很多公司是沒有專業(yè)的測(cè)試人員甚至是沒有測(cè)試人員的喲具练。

針對(duì)以上問題出現(xiàn)的原因分析及解決方案如下:

  • 對(duì)于1,2問題
    很明顯他把檢查更新的工作只寫在了應(yīng)用的首頁(yè)(比如MainActivity)中了乍构,在其它任何界面并沒有檢查更新的操作

  • 解決方案
    每個(gè)界面都需要檢查更新,當(dāng)然咱們不能在每個(gè)Activity中都復(fù)制粘貼一樣的代碼扛点。這時(shí)定義一個(gè)BaseActivity哥遮,所有其它Activity都從它繼承就顯得很有價(jià)值了×昃浚可以把檢查更新的操作放到BaseActivity的相關(guān)方法中眠饮,比如放在onResume中,這樣每當(dāng)顯示一個(gè)界面時(shí)都將執(zhí)行檢查更新的操作

  • 對(duì)于5問題铜邮,如果把下載的操作放在了Activity中進(jìn)行仪召,如果應(yīng)用意外終止或者強(qiáng)制退出應(yīng)用,則下載線程也將被終止

  • 解決方案
    可以將下載任務(wù)放到Service中執(zhí)行松蒜,這樣即使應(yīng)用被終止Service一樣有比用活機(jī)制(startForeground)讓Service的任務(wù)有很大的機(jī)會(huì)繼續(xù)得以執(zhí)行

  • 對(duì)于6問題,如果檢查更新的操作沒有在Activity的resume時(shí)再次執(zhí)行秸苗,則回到Activity自然也就沒有檢查更新并彈窗了

  • 解決方案
    在Activity的onResume中繼續(xù)檢查更新召娜,如果是強(qiáng)制更新則彈窗阻止用戶進(jìn)行其它操作

  • 對(duì)于3,4問題,我倒是覺得不是程序問題而是態(tài)度問題难述,實(shí)際加入非wifi和進(jìn)度顯示的功能非常簡(jiǎn)單

整體解決方案

  1. 定義Service類萤晴,比如VersionUpdateService.java。主要提供版本檢查及文件下載操作
  2. 定義VersionUpdateHelper類胁后,用來(lái)使用Service并提供和前臺(tái)Activity的交互
    如果大家對(duì)Service的使用還有問題(需要頻繁更新前臺(tái)ui等)店读,建議大家閱讀android圖片壓縮上傳系列-service篇這篇文章先做了解。

核心代碼如下:

public class VersionUpdateService extends Service {
    private LocalBinder binder = new LocalBinder();

    private DownLoadListener downLoadListener;//下載任務(wù)監(jiān)聽回調(diào)接口
    private boolean downLoading;
    private int progress;

    private NotificationManager mNotificationManager;
    private NotificationUpdaterThread notificationUpdaterThread;
    private Notification.Builder notificationBuilder;
    private final int NOTIFICATION_ID = 100;

    private VersionUpdateModel versionUpdateModel;
    private CheckVersionCallBack checkVersionCallBack;//檢查結(jié)果監(jiān)聽回調(diào)接口
    public interface DownLoadListener {
        void begain();
        void inProgress(float progress, long total);
        void downLoadLatestSuccess(File file);
        void downLoadLatestFailed();
    }

    public interface CheckVersionCallBack {
        void onSuccess();
        void onError();
    }
    ...
    private class NotificationUpdaterThread extends Thread {
        @Override
        public void run() {
            while (true) {
                notificationBuilder.setContentTitle("正在下載更新" + progress + "%"); // the label of the entry
                notificationBuilder.setProgress(100, progress, false);
                ...
            }
        }
    }
    private void starDownLoadForground() {
        //創(chuàng)建通知欄
        notificationBuilder = new Notification.Builder(this);
        ...
        Notification notification = notificationBuilder.getNotification();
        startForeground(NOTIFICATION_ID, notification);
    }
    private void stopDownLoadForground() {
        stopForeground(true);
    }
    //執(zhí)行版本檢查任務(wù)
    public void doCheckUpdateTask() {
        //獲取本定版本號(hào)
        final int currentBuild = AppUtil.getVersionCode(this);
        //調(diào)用版本檢查接口
      ApiManager.getInstance().versionApi.upgradeRecords(currentBuild, new RequestCallBack() {
            @Override
            public void onSuccess(Headers headers, String response) {
                    versionUpdateModel = JSON.parseObject(response, VersionUpdateModel.class);
                    ...
                    if (checkVersionCallBack != null)
                        checkVersionCallBack.onSuccess();
            }

            @Override
            public void onError(int code, String response) {
              ...
            }
        });
    }
    public void doDownLoadTask() {
        starDownLoadForground();
        //啟動(dòng)通知欄進(jìn)度更新線程
        notificationUpdaterThread = new NotificationUpdaterThread();
        notificationUpdaterThread.start();
        //文件下載存放路徑
        final File fileDir = FolderUtil.getDownloadCacheFolder();
        ...
        downLoading = true;
        if (downLoadListener != null) {
            downLoadListener.begain();
        }
        NetManager.getInstance().download(url, fileDir.getAbsolutePath(), new DownloadCallBack() {
            @Override
            public void inProgress(float progress_, long total) {
                ...
                //執(zhí)行進(jìn)度更新
               if (downLoadListener != null) 
                  downLoadListener.inProgress(progress_, total);
              }
            @Override
            public void onSuccess(Headers headers, String response) {
                //執(zhí)行成功回調(diào)
                ...
                installApk(destFile, VersionUpdateService.this);
            }

            @Override
            public void onError(int code, String response) {
                ...
                //執(zhí)行失敗回調(diào)
            }
        });
    }

    //安裝apk
    public void installApk(File file, Context context) {
        ...
    }
}
public class VersionUpdateHelper implements ServiceConnection {
    private Context context;
    private VersionUpdateService service;
    private AlertDialog waitForUpdateDialog;
    private ProgressDialog progressDialog;

    private static boolean isCanceled;

    private boolean showDialogOnStart;

    public static final int NEED_UPDATE = 2;
    public static final int DONOT_NEED_UPDATE = 1;
    public static final int CHECK_FAILD = -1;
    public static final int USER_CANCELED = 0;

    private CheckCallBack checkCallBack;

    public interface CheckCallBack{
        void callBack(int code);
    }

    public VersionUpdateHelper(Context context) {
        this.context = context;
    }

    public void startUpdateVersion() {
        if (isCanceled)
            return;
        if (isWaitForUpdate() || isWaitForDownload()) {
            return;
        }
        if (service == null && context != null) {
            context.bindService(new Intent(context, VersionUpdateService.class), this, Context.BIND_AUTO_CREATE);
        }
    }

    public void stopUpdateVersion() {
        unBindService();
    }

    private void cancel() {
        isCanceled = true;
        unBindService();
    }

    private void unBindService() {
        if (isWaitForUpdate() || isWaitForDownload()) {
            return;
        }
        if (service != null && !service.isDownLoading()) {
            context.unbindService(this);
            service = null;
        }
    }

    ...

    private void showNotWifiDownloadDialog() {
        final AlertDialog.Builder builer = new AlertDialog.Builder(context);
        builer.setTitle("下載新版本");
        builer.setMessage("檢查到您的網(wǎng)絡(luò)處于非wifi狀態(tài),下載新版本將消耗一定的流量,是否繼續(xù)下載?");
        builer.setNegativeButton("以后再說(shuō)", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                ...
                //如果是強(qiáng)制更新 exit app
                if (mustUpdate) {
                    MainApplication.getInstance().exitApp();
                }
            }
        });
        builer.setPositiveButton("繼續(xù)下載", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                dialog.cancel();
                service.doDownLoadTask();
            }
        });
        ...
    }

    @Override
    public void onServiceConnected(ComponentName name, IBinder binder) {
        service = ((VersionUpdateService.LocalBinder) binder).getService();
        service.setCheckVersionCallBack(new VersionUpdateService.CheckVersionCallBack() {
            @Override
            public void onSuccess() {
                VersionUpdateModel versionUpdateModel = service.getVersionUpdateModel();

                //EventBus控制更新紅點(diǎn)提示
                EventBus.getDefault().postSticky(versionUpdateEvent);

                if (!versionUpdateModel.isNeedUpgrade()) {
                    if(checkCallBack != null){
                        checkCallBack.callBack(DONOT_NEED_UPDATE);
                    }
                    cancel();
                    return;
                }
                if (!versionUpdateModel.isMustUpgrade() && !showDialogOnStart) {
                    cancel();
                    return;
                }
                if(checkCallBack != null){
                    checkCallBack.callBack(NEED_UPDATE);
                }
                final AlertDialog.Builder builer = ...//更新提示對(duì)話框
                builer.setPositiveButton("立即更新", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        dialog.cancel();
                        if (NetUtil.isWifi(context)) {
                            service.doDownLoadTask();
                        } else {
                            showNotWifiDownloadDialog();
                        }
                    }
                });

                //當(dāng)點(diǎn)取消按鈕時(shí)進(jìn)行登錄
                if (!versionUpdateModel.isMustUpgrade()) {
                    builer.setNegativeButton("稍后更新", new DialogInterface.OnClickListener() {
                        public void onClick(DialogInterface dialog, int which) {
                            dialog.cancel();
                            cancel();
                            if(checkCallBack != null){
                                checkCallBack.callBack(USER_CANCELED);
                            }
                        }
                    });
                }
                builer.setCancelable(false);
                waitForUpdateDialog = builer.create();
                waitForUpdateDialog.show();
            }

            @Override
            public void onError() {
                unBindService();
                ...
            }
        });

        service.setDownLoadListener(new VersionUpdateService.DownLoadListener() {
            @Override
            public void begain() {
                VersionUpdateModel versionUpdateModel = service.getVersionUpdateModel();
                if (versionUpdateModel.isMustUpgrade()) {
                    progressDialog = ...//生成進(jìn)度條對(duì)話框
                }
            }

            @Override
            public void inProgress(float progress, long total) {
                ...//更新進(jìn)度條
            }

            @Override
            public void downLoadLatestSuccess(File file) {
                ...//執(zhí)行成功處理
                unBindService();
            }

            @Override
            public void downLoadLatestFailed() {
                ...//執(zhí)行失敗處理
                unBindService();
            }
        });

        service.doCheckUpdateTask();
    }
    ...
}

最后攀芯,使用方式還是非常簡(jiǎn)單的屯断。在BaseActivity中使用:

private VersionUpdateHelper versionUpdateHelper;
@Override
protected void onResume() {
    super.onResume();
    if(versionUpdateHelper == null)
        versionUpdateHelper = new VersionUpdateHelper(this);
    versionUpdateHelper.startUpdateVersion();
}

@Override
protected void onPause() {
    super.onPause();
    if(versionUpdateHelper != null)
        versionUpdateHelper.stopUpdateVersion();
}

保證在每進(jìn)入一個(gè)界面和離開界面時(shí)都將檢查更新(bindService)和取消檢查(unBindService)。這時(shí)有些朋友可能認(rèn)為這樣做會(huì)不會(huì)浪費(fèi)資源呢侣诺?沒有殖演!
1,如果應(yīng)用是強(qiáng)制更新年鸳,那么在網(wǎng)絡(luò)正常情況下進(jìn)入應(yīng)用就能檢查出有新版本趴久,這時(shí)彈窗后用戶不能進(jìn)入任何操作,沒有機(jī)會(huì)進(jìn)入別的界面搔确,所有沒有進(jìn)行重復(fù)檢查彼棍;如果進(jìn)入應(yīng)用主頁(yè)由于網(wǎng)絡(luò)問題灭忠,檢查失敗,這時(shí)雖然不會(huì)彈窗提示更新座硕,但是如果用戶的網(wǎng)絡(luò)恢復(fù)后進(jìn)入任何其它界面都將得到正常的版本更新檢查并彈窗提示
2弛作,如果應(yīng)用是非強(qiáng)制更新時(shí),在Helper代碼里進(jìn)行了如下的判斷:

if (!versionUpdateModel.isMustUpgrade() && !showDialogOnStart) {
    cancel();
    return;
}

這里的showDialogOnStart默認(rèn)為false华匾,也就是說(shuō)如果不是強(qiáng)制更新則檢查成功后就當(dāng)“取消”處理映琳,并在cancel方法中將變量isCanceled修改為true,這樣如果有新的請(qǐng)求想要執(zhí)行startUpdateVersion()都將被忽略蜘拉,注意isCanceled是static全局的萨西。

如果想實(shí)現(xiàn)在設(shè)置中由用戶手動(dòng)檢查更新,則只需執(zhí)行類似如下代碼:
SettingActivity.java

private VersionUpdateHelper versionUpdateHelper;

@OnClick(R.id.rl_version_update)
public void onClickVersionUpdate(View view) {
    if(updateTips.getVisibility() == View.VISIBLE){
        return;
    }
    VersionUpdateHelper.resetCancelFlag();//重置cancel標(biāo)記
    if (versionUpdateHelper == null) {
        versionUpdateHelper = new VersionUpdateHelper(this);
        versionUpdateHelper.setShowDialogOnStart(true);
        versionUpdateHelper.setCheckCallBack(new VersionUpdateHelper.CheckCallBack() {
            @Override
            public void callBack(int code) {
                //EventBus發(fā)送消息通知紅點(diǎn)消失
                VersionUpdateEvent versionUpdateEvent = new VersionUpdateEvent();
                versionUpdateEvent.setShowTips(false);
                EventBus.getDefault().postSticky(versionUpdateEvent);
            }
        });
    }
    versionUpdateHelper.startUpdateVersion();
}

寫在最后

由于代碼較多诸尽,且多數(shù)代碼和ui相關(guān)原杂,所以在文章中很多ui相關(guān)或者getter和setter方法等非核心代碼并沒有列出。演示代碼中用了EventBus和OkHttp開源控件您机,具體使用方法望大家自己找相關(guān)資料學(xué)習(xí)穿肄。本人打算有空的時(shí)候?qū)憘€(gè)EventBus系列文章,望大家多多關(guān)注际看。
文件下載也是使用的okHttp實(shí)現(xiàn)的咸产,大家可以換成任何你熟悉的下載框架。VersionUpdateService.java和VersionUpdateHelper.java的完整代碼可以到我的github上下載仲闽,由于時(shí)間關(guān)系并沒有相關(guān)用法的完整案例還望見諒脑溢,等有時(shí)間一定奉上。
如果有任何問題可以在評(píng)論中加以提問赖欣,謝謝~~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末屑彻,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子顶吮,更是在濱河造成了極大的恐慌社牲,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,406評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件悴了,死亡現(xiàn)場(chǎng)離奇詭異搏恤,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)湃交,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門熟空,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人搞莺,你說(shuō)我怎么就攤上這事息罗。” “怎么了才沧?”我有些...
    開封第一講書人閱讀 163,711評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵阱当,是天一觀的道長(zhǎng)俏扩。 經(jīng)常有香客問我,道長(zhǎng)弊添,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,380評(píng)論 1 293
  • 正文 為了忘掉前任捌木,我火速辦了婚禮油坝,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘刨裆。我一直安慰自己澈圈,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,432評(píng)論 6 392
  • 文/花漫 我一把揭開白布帆啃。 她就那樣靜靜地躺著瞬女,像睡著了一般。 火紅的嫁衣襯著肌膚如雪努潘。 梳的紋絲不亂的頭發(fā)上诽偷,一...
    開封第一講書人閱讀 51,301評(píng)論 1 301
  • 那天,我揣著相機(jī)與錄音疯坤,去河邊找鬼报慕。 笑死,一個(gè)胖子當(dāng)著我的面吹牛压怠,可吹牛的內(nèi)容都是我干的眠冈。 我是一名探鬼主播,決...
    沈念sama閱讀 40,145評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼菌瘫,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼蜗顽!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起雨让,我...
    開封第一講書人閱讀 39,008評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤雇盖,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后宫患,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體刊懈,經(jīng)...
    沈念sama閱讀 45,443評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,649評(píng)論 3 334
  • 正文 我和宋清朗相戀三年娃闲,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了虚汛。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,795評(píng)論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡皇帮,死狀恐怖卷哩,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情属拾,我是刑警寧澤将谊,帶...
    沈念sama閱讀 35,501評(píng)論 5 345
  • 正文 年R本政府宣布冷溶,位于F島的核電站,受9級(jí)特大地震影響尊浓,放射性物質(zhì)發(fā)生泄漏逞频。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,119評(píng)論 3 328
  • 文/蒙蒙 一栋齿、第九天 我趴在偏房一處隱蔽的房頂上張望苗胀。 院中可真熱鬧,春花似錦瓦堵、人聲如沸基协。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)澜驮。三九已至,卻和暖如春惋鸥,著一層夾襖步出監(jiān)牢的瞬間杂穷,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工揩慕, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留亭畜,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,899評(píng)論 2 370
  • 正文 我出身青樓迎卤,卻偏偏與公主長(zhǎng)得像拴鸵,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子蜗搔,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,724評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,110評(píng)論 25 707
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理劲藐,服務(wù)發(fā)現(xiàn),斷路器樟凄,智...
    卡卡羅2017閱讀 134,654評(píng)論 18 139
  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫(kù)聘芜、插件、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 12,102評(píng)論 4 62
  • L7: 1.天貓開專賣店的材料和成本: 一缝龄、天貓商城專營(yíng)店所需材料 1企業(yè)營(yíng)業(yè)執(zhí)照副本 2企業(yè)稅務(wù)登記證 3組織機(jī)...
    a93d7dcda8b1閱讀 168評(píng)論 0 0
  • 出國(guó)游一直是件麻煩事叔壤,除了收拾行李瞎饲,還要規(guī)劃,并且需要提前很長(zhǎng)時(shí)間炼绘。如果你選擇去美國(guó)嗅战,就更痛苦了,因?yàn)槊篮灠沉痢C篮灢?..
    小咪小不閱讀 1,548評(píng)論 0 2