1. 程式人生 > >iOS 如何實現 AppStore 中App 的自動下載

iOS 如何實現 AppStore 中App 的自動下載

這次的分享是關於如何在 AppStore 實現 App 的自動下載,理想中的目標是隻需要一部手機,不需要人來干預,就可以模擬使用者的真實下載,並在下載完成以後,可以自動更改手機引數,使之變為另外一部蘋果手機,進行周而復始的下載工作。但是呢,本文的內容只包含如何去模擬使用者的操作來完成下載,並不涉及抹機、IP 更換等內容。

為什麼做這個呢?

可能會有人問,為什麼要做這麼一個專案。主要是兩點原因吧,第一點呢,是出於個人興趣,逆向其實在開發中的用處還是蠻大的,比如幫助我們分析 Apple 作業系統,幫我們做好安全防禦。通過這麼一個專案的實踐,可以加深自己對逆向開發的理解,第二點呢,就是 App Search Optimization 是一個一直比較熱門的話題,有白帽子和黑帽子 ASO 之分,通過關鍵字和標題優化等手段來進行 ASO 的屬於白帽子 ASO,而通過刷榜程式來進行 ASO 的屬於黑帽子 ASO,ASO 的刷榜指令碼是價值不菲的,可能價值幾十萬甚至幾百萬。通過這個專案也是小試牛刀,瞭解下灰產的一些技術手段。

什麼是 ASO

ASO 的全稱是 App Search Optimization,就是提升你 APP 在 AppStore 排行榜和搜尋結果排名的過程。我們經常可以看到 AppStore 有一些奇怪的五星好評,也會遇到搜尋關鍵字,排名第一的是一個看上去完全不相關的 App。這些都是 ASO 優化的手段,幫助提升產品的曝光量。
這裡寫圖片描述
白帽子 ASO 常用的手段就是通過資料分析,來優化關鍵詞、標題等,進而提高 App 的排名和曝光率。而黑帽子的手段則是,通過刷榜程式來實現 App 的大量搜尋、下載、好評這一系列的過程來提升 App 的排名。

常見的刷榜手段主要有兩種,一種是機刷,就是通過觸動精靈或者程式碼注入的方式來實現模擬使用者的真實操作,進而完成搜尋、下載、評論等操作。再一種協議刷,就是破解 AppStore 的登陸、下載相關的網路協議,通過模擬真實的網路請求來實現登陸、下載等行為。據說在刷榜過程中,蘋果會校驗你的 Apple ID、IP 等資訊,所以需要購買大量的 Apple ID 和不斷更換 IP 地址。

如何實現 App 的自動下載

想要的效果:

進入 AppStore,切換 tab 到搜尋介面
設定搜尋關鍵字、搜尋
進入列表頁後,點選 App 進入詳情頁點選下載
根據提示完成登陸、下載,並在下載完成以後跳轉到推薦 Tab
進入推薦 Tab 後,退出登陸

大概實現步驟:

準備越獄手機和 Mac 電腦
砸殼 dumpdecrypted,通常 PP助手、iTools 下載的 App 是經過砸殼的,同時 AppStore App 不需要砸殼
標頭檔案獲取:AppStore class-dump,系統庫的標頭檔案的獲取:dyld_cache class-dump
定位關鍵函式:Reveal、Cycript、lldb
tweak 的注入

砸殼

我們的 App 上傳到 AppStore 後,蘋果會對 App 進行加密,要想去分析可執行檔案,就必須要進行脫殼解密的操作,dumpdecrypted 是一款出色的脫殼工具,它的原理是將 App 執行起來,App 啟動時,系統會對 Mach-O 檔案進行載入,並完成對應的解密操作,dumpdecrypted 就可以在此時將解密後的 Mach-O dump 出來,從而達到解密的效果。

如果為了省事可以直接從 PP 助手、iTools 上下載對應的 App,一般情況下是已經經過砸殼的。同時,對於 AppStore 這樣的系統程式有些特殊,他們 並不需要進行砸殼,可以直接拿來進行分析。

獲取標頭檔案

拿到一個砸殼後的可執行檔案後,就可以使用 class-dump 來獲取可執行檔案的所有標頭檔案,class-dump 會對 Mach-O 的格式進行分析,並將資訊提取出來形成我們想要的標頭檔案。

AppStore 的可執行檔案也略有特殊,class dump之後會發現 AppStore 中包含的程式碼極少。App Store 的很多關鍵程式碼邏輯都不在 AppStore 這個可執行檔案當中,而是在系統的動態庫中,我們需要分析動態庫的標頭檔案資訊進而定位到關鍵函式。可以獲取對應系統dyld_cache 中的動態庫,然後 dump 出頭檔案。AppStore UI 有關的邏輯都在 StoreKitUI 動態庫中,這個動態庫是分析的重點。

Reveal

Reveal 是一款 UI 除錯工具,官方的定義是:See your iOS application’s view hierarchy at runtime with advanced 2D and 3D visualisations,當然對於逆向安全人員,檢視自己 App 的佈局是完全不夠的,我們可以在 Cydia 中下載 Reveal Loader,在同一網段下,通過 Mac 的 Reveal 和 iOS 上的 Reveal Loader 就可以檢視任意 App 的 UI 佈局。
這裡寫圖片描述
但是,有時候我們不僅想要去看這個 UI 佈局,還想要去動態除錯這個佈局,去看它的 Controller 是誰,去挖掘介面下的真正的程式碼邏輯。這個就涉及到 Cycript 這個工具。

Cycript

Cycript 是由 Cydia 創始人 Saurik 推出的一款指令碼語言,它混合了Objective-C 與 JavaScript 兩種語法,很容易上手,我們可以通過 Cycript 來進行動態除錯,比如檢視函式執行的效果,尋找 View 的 Controller 等。
這裡寫圖片描述

就拿上面 Reveal 詳情頁為例, Reveal 可以看到獲取按鈕是 SKUIOfferView,列表頁是一個 SKUICollectionView ,那麼就通過 Cycript 來看看控制這個 SKUICollectionView 的 Controller 是誰。首先通過 OpenSSH 來連線 iPhone,通過 cycript -p AppStore 來對 AppStore 進行注入除錯,UIApp.keyWindow.recursiveDescription().toString() 來列印檢視層級。(注:此截圖和後面的地址對不上,因為不是同一次列印,大家瞭解下大概意思就成)
這裡寫圖片描述
可以發現 SKUICollectionView,並且它的記憶體地址是 0x13fa00e00,可以通過 cycript 指令碼來找到它的 Controller 是哪一個,有多種方案,比如通過它的 delegate 來找,或者通過 nextResponder 來找都可以。

cy# [#0x13fa00e00 delegate]

#"<SKUIStorePageSectionsViewController: 0x140167e00>"

cy# [#0x13fa00e00 nextResponder]

#"<UIView: 0x140f5f540; frame = (0 0; 320 568); autoresize = W+H; layer = <CALayer: 0x140f771c0>>"

cy# [#0x140f5f540 nextResponder]

#"<SKUIStorePageSectionsViewController: 0x140167e00>"

同時也可以藉助一些私有 API 來實現快速查詢 ViewController,使用[[[UIWindow keyWindow] rootViewController] _printHierarchy].toString(),可以發現列印結果中同樣可以找到 SKUIStorePageSectionsViewController

cy# [[[UIWindow keyWindow] rootViewController] _printHierarchy].toString()
`<SKUITabBarController 0x157815400>, state: appeared, view: <UILayoutContainerView 0x156db38e0>
   | <UINavigationController 0x15784d200>, state: disappeared, view: <UILayoutContainerView 0x156e6b240> not in the window
   |    | <SKUIDocumentContainerViewController 0x1578d3c00>, state: disappeared, view: <UIView 0x1580e1aa0> not in the window
   |    |    | <SKUIStackDocumentViewController 0x15812b740>, state: disappeared, view: <UIView 0x1580dc870> not in the window
   |    |    |    | <SKUIStorePageSectionsViewController 0x1578ec000>, state: disappeared, view: <UIView 0x1580f1a30> not in the window
   |    |    |    |    | <SKUIAccountButtonsViewController 0x158654180>, state: disappeared, view: <SKUIAccountButtonsView 0x158654f60> not in the window
   | <UINavigationController 0x157849c00>, state: disappeared, view: <UILayoutContainerView 0x156ec4df0> not in the window
   | <UINavigationController 0x157803600>, state: disappeared, view: <UILayoutContainerView 0x156e80de0> not in the window
   | <UINavigationController 0x15703ea00>, state: appeared, view: <UILayoutContainerView 0x156f114a0>
   |    | <SKUIDocumentContainerViewController 0x157ab2a00>, state: disappeared, view: <UIView 0x158a25930> not in the window
   |    |    | <SKUIStackDocumentViewController 0x158a50690>, state: disappeared, view: <UIView 0x158a2b360> not in the window
   |    |    |    | <SKUIStorePageSectionsViewController 0x1578e6000>, state: disappeared, view: <UIView 0x158a2d4b0> not in the window
   |    | <SKUIDocumentContainerViewController 0x157b5fa00>, state: appeared, view: <UIView 0x158cf70e0>
   |    |    | <SKUIStackDocumentViewController 0x158cf6690>, state: appeared, view: <UIView 0x158cf72b0>
   |    |    |    | <SKUIStorePageSectionsViewController 0x157b4ae00>, state: appeared, view: <UIView 0x158cfb1e0>
   | <UINavigationController 0x157028000>, state: disappeared, view: <UILayoutContainerView 0x156ef1300> not in the window
   |    | <ASUpdatesViewController 0x156f169e0>, state: disappeared, view: <UIView 0x156dbd590> not in the window`

從上面的分析可以知道,SKUICollectionView 的控制器是 SKUIStorePageSectionsViewController,「獲取」按鈕的類是 SKUIOfferView,下一步是分析標頭檔案,看看有沒有可以比較明顯的方法可以為我們所用。下載是最關鍵的一步,那麼首先來看看 SKUIOfferView 類的情況,它的標頭檔案大致如此。

#import <StoreKitUI/SKUIItemOfferButtonDelegate-Protocol.h>
#import <StoreKitUI/SKUIViewElementView-Protocol.h>
@class NSMapTable, NSMutableArray, NSString;
@protocol SKUIOfferViewDelegate;
@interface SKUIOfferView : SKUIViewReuseView <SKUIItemOfferButtonDelegate, SKUIViewElementView> {
    unsigned long long _alignment;
    NSMapTable *_buttonElements;
    NSMapTable *_buyButtonDescriptorToButton;
    struct UIEdgeInsets _contentInset;
}
- (void)_buttonAction:(id)arg1;
- (void)itemOfferButtonWillAnimateTransition:(id)arg1;
- (void)itemOfferButtonDidAnimateTransition:(id)arg1;
- (struct CGSize)sizeThatFits:(struct CGSize)arg1;

可以從標頭檔案中看到一個 _buttonAction 方法,感覺上是 「獲取」按鈕點選後的響應方法,對於這種猜測,可以使用 Cycript 來進行除錯,測試一下這個函式執行的效果到底如何 在終端執行 [#0x156c69cc0 _buttonAction:#0x156cb4d20] 後檢視效果如下,App 已經開始進行下載了,說明這個方法的效果我們猜對了,在除錯過程中,可以多多使用 Cycript 提高效率。
這裡寫圖片描述

lldb

上面我們使用 Cycript 測試了 _buttonAction 的效果,但是這個方法有一個引數,我們要搞清楚它正確的引數型別,傳入正確的值。這時候可以藉助 LLDB ,來幫助我們找到這個引數的正確型別。 可以使用 b function 來針對 _buttonAction 方法打斷點,然後列印它的引數。

傳統的做法是使用LLDB 和 IDA 等工具找到 ASLR 和 基地址等資訊,然後計算出符號的地址,這樣做起來比較繁瑣,還是可以繼續使用一些私有方法快速定位 _buttonAction 的符號地址來進行斷點。

我們想要斷點的方法是 _buttonAction,它所在的類是 SKUIOfferView,那麼可以使用 LLDB 輸入 po [SKUIOfferView _shortMethodDescription] 來看下效果:(更多強大的黑科技私有函式可以參考這裡:http://iosre.com/t/powerful-private-methods-for-debugging-in-cycript-lldb/3414

(lldb) po [SKUIOfferView _shortMethodDescription]
<SKUIOfferView: 0x1a096ddd8>:
in SKUIOfferView:
    Class Methods:
        + (void) requestLayoutForViewElement:(id)arg1 width:(double)arg2 context:(id)arg3; (0x194719470)
        + (CGSize) sizeThatFitsWidth:(double)arg1 viewElement:(id)arg2 context:(id)arg3; (0x1947197a8)
    Properties:
        @property (weak, nonatomic) <SKUIOfferViewDelegate>* delegate;  (@synthesize delegate = _delegate;)
        @property (nonatomic) long metadataPosition;  (@synthesize metadataPosition = _metadataPosition;)
        @property (readonly, nonatomic, getter=isShowingConfirmation) BOOL showingConfirmation;  (@synthesize showingConfirmation = _isShowingConfirmation;)
    Instance Methods:
        - (BOOL) setImage:(id)arg1 forArtworkRequest:(id)arg2 context:(id)arg3; (0x19471a8c8)
        - (BOOL) updateWithItemState:(id)arg1 context:(id)arg2 animated:(BOOL)arg3; (0x19471a8d0)
        - (void) _buttonAction:(id)arg1; (0x19471bb5c)
        - (BOOL) _shouldHideNoticesWithBuyButtonDescriptor:(id)arg1 context:(id)arg2; (0x19471c368)
        - (void) _positionNoticeForItemOfferButton:(id)arg1; (0x19471c234)
(SKUIViewReuseView ...)

可以看到 - (void) _buttonAction:(id)arg1; (0x19471bb5c),那麼直接使用 b 0x19471bb5c為 _buttonAction 加斷點即可。斷點到以後,再列印它的引數,對於 Objective-C 來說訊息有兩個隱含引數,也就是 self 和 _cmd,那麼我們想要的引數就在第三個位置,可以通過 po $x2 來檢視它的具體資訊(ARM64 下函式的引數是存放在 X0 到 X7 這 8 個暫存器裡面的,如果超過8個引數,就會入棧)。

Process 7839 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1 2.1 3.1
    frame #0: 0x000000019471bb5c StoreKitUI`-[SKUIOfferView _buttonAction:]
StoreKitUI`-[SKUIOfferView _buttonAction:]:
->  0x19471bb5c <+0>:  stp    x24, x23, [sp, #-0x40]!
    0x19471bb60 <+4>:  stp    x22, x21, [sp, #0x10]
    0x19471bb64 <+8>:  stp    x20, x19, [sp, #0x20]
    0x19471bb68 <+12>: stp    x29, x30, [sp, #0x30]
Target 0: (AppStore) stopped.
(lldb) po $x0
<SKUIOfferView: 0x1596aae00; frame = (279 74; 26 26); layer = <CALayer: 0x1596676b0>>
(lldb) po $x2
<SKUIItemOfferButton: 0x1596ab260; baseClass = UIControl; frame = (0 0; 26 26); clipsToBounds = YES; alpha = 0.2; tintColor = UIDeviceRGBColorSpace 0.0862745 0.0156863 0.0156863 1; animations = { opacity=<CABasicAnimation: 0x1592e7b20>; }; layer = <CALayer: 0x15967d9c0>>

由上可知,引數型別是 SKUIItemOfferButton,也就是 SKUIOfferView 的 subView,其實點選的是 SKUIItemOfferButton,只是 SKUIItemOfferButton 將處理往上拋而已。

Tweak 注入

Cydia 創始人 Saurik 同時為我們提供了一個 Cydia Substrate 這麼一個工具,官方的定義是:The powerful code modification platform behind Cydia。我們可以基於 Cydia Substrate 來開發具有各種功能的程式碼注入程式。

Cydia Substrate 由 MobileHooker、MobileLoader、Safe mode 三個模組組成。MobileHooker 主要用來替換函式的實現,可以想象成 Runtime 的 Method Swizzle。MobileLoader 是用來載入第三方 dylib 的,我們寫的破解程式會在目標程式啟動時注入到目標程式。Safe mode 就是安全模式,我們寫 tweak 的時候可能會造成 Crash,比如萬一造成 SpringBoard 無限 Crash 手機豈不是就沒法用了,所以提供了這麼一個安全模式。

MobileHooker 提供了一些函式來讓我們完成 Hook 的工作,但是我們不直接使用 它們,我們使用基於他們封裝的 Logos 工具,Logos 的語法很簡單直觀,易於上手。比如 %hook 可以指定要 Hook 的類、%orig 可以執行被鉤住的函式的原始實現、%new 給一個現成的 class 新增新函式(效果與 class_addMethod 類似)。

Tweak AppStore

那我們來使用 Logos 實現下載的功能,當進入 SKUIStorePageSectionsViewController 頁面後,找到下載按鈕,然後點選下載,當下載按鈕的文字由「獲取」變為「開啟」,代表下載已完成,然後繼續執行後續操作。

%hook SKUIStorePageSectionsViewController
- (void)viewDidAppear:(BOOL)animated {    
    %log;
    %orig;
    // 遍歷所有子 View,找到 offerButton 、offerView
    [self findAllSubviews:self.view];
    if (offerButton && offerView) {
        // 執行下載操作
        [offerView _buttonAction:offerButton];
        // 每秒去 check 一下,是否下載完成
        downloadTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timerAction) userInfo:nil repeats:YES];
    }       
}
%new
-(void)timerAction {
    if ([offerButton.title isEqualToString:@"開啟"]) {
        // 傳送下載完成的通知
        [[NSNotificationCenter defaultCenter] postNotificationName:@"textChangedAction" object:nil];
        downloadTimer = nil;
    }
}
%new
-(void)findAllSubviews:(UIView *)view
 {
    for (UIView *subView in view.subviews) {
        if (subView.subviews.count) {
            [self findAllSubviews:subView];
        }
        if ([subView isKindOfClass:NSClassFromString(@"SKUIOfferView")]) {
            offerView = (SKUIOfferView*)subView;
        }
        if ([subView isKindOfClass:NSClassFromString(@"SKUIItemOfferButton")]) {
            offerButton = (SKUIItemOfferButton*)subView;
        }
    }
}
%end

其他的操作,與上述其實很類似,比如搜尋、跳轉都是利用靜態或者動態分析找到關鍵函式,通過 tweak 來實現想要的效果即可。其中還有一個較難的點,就是彈窗提示我們登陸怎麼辦?如何實現自動登入功能?

Tweak SpringBoard

首先,想到的就是在 AppStore App 中注入程式碼,Hook UIAlertAction 和 UIAlertController 的程式碼,會發現並沒有產生作用。AppStore 中的彈窗不是它來控制的,而是另外一個程序 SpringBoard,所以要想實現 Hook AppStore 的彈窗,必須對 SpringBoard 進行程式碼注入。
這裡寫圖片描述
我們正常如果要實現一個這種彈窗,程式碼一般是這麼寫

UIAlertController *actionSheet = [UIAlertController alertControllerWithTitle:@"標題" message:@"註釋資訊" preferredStyle:UIAlertControllerStyleActionSheet];  
UIAlertAction *action1 = [UIAlertAction actionWithTitle:@"標題1" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {  
    NSLog(@"點選了按鈕 1");  
}];  
UIAlertAction *action2 = [UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {  
    NSLog(@"點選了按鈕 2");  
}];  
[actionSheet addAction:action1];  
[actionSheet addAction:action2];  
[self presentViewController:actionSheet animated:YES completion:nil];  

基於上面的程式碼分析可得,我們要想實現自動登入,就要實現自動點選「使用現有的 Apple ID」執行系統的原 action 操作,然後在賬號和密碼的 TextField 中填入賬號密碼,點選「好」執行系統的原始 action 操作。其實可以發現,要執行的 action 其實是在初始化 UIAlertAction 過程中,handler block 中加入的邏輯。那麼我們就可以 Hook actionWithTitle:style:handler: 然後將 handler 儲存下來,當填寫好賬號密碼後,主動觸發 handler 即可。

上面那種方法也可以奏效,但是需要自己額外處理下 alertView 的出現和消失, 為了簡單可以直接嘗試第二種方法,在分析 UIKit 框架中 UIAlertController 類的標頭檔案時發現 _dismissWithAction:這個方法,然後我就試了一下發現可以完成 dismiss 和 執行 handler 兩項功能,所以我就直接使用了這個 API 來模擬點選。核心程式碼如下:

typedef void(^CDUnknownBlockType)(UIAlertAction *action);
CDUnknownBlockType testBlock;
static UIAlertAction *keepAction;
static int atimers;
%hook UIAlertController
- (void)viewDidAppear:(BOOL)animated {
    %log;
    %orig;
    if ([keepAction.title isEqualToString:@"使用現有的 Apple ID"]) {
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            ((void ( *)(id, SEL, UIAlertAction*))objc_msgSend)(self, NSSelectorFromString(@"_dismissWithAction:"),keepAction);
        });
    } 
    if ([keepAction.title isEqualToString:@"好"]) {
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            if (self.textFields.count > 1) {
                self.textFields.firstObject.text = @"[email protected]";
                self.textFields.lastObject.text = @"Joyme0304&&&";
                ((void ( *)(id, SEL, UIAlertAction*))objc_msgSend)(self, NSSelectorFromString(@"_dismissWithAction:"),keepAction);
            }
        });
    }
}
%end
%hook UIAlertAction
+ (id)_actionWithTitle:(id)arg1 descriptiveText:(id)arg2 image:(id)arg3 style:(long long)arg4 handler:(CDUnknownBlockType)arg5 shouldDismissHandler:(CDUnknownBlockType)arg6 {
    id obj = %orig;
    UIAlertAction *action = (UIAlertAction *)obj;
    if ([action.title isEqualToString:@"使用現有的 Apple ID"]) {
        testBlock = arg6;
        keepAction = obj;
    } 
    if ([action.title isEqualToString:@"好"]) {
        testBlock = arg6;
        keepAction = obj;
    }
    return obj;
}
%end

從程式碼可以看出我們在 Hook UIAlertAction 的 _actionWithTitle 方法時,並沒有 Hook actionWithTitle:style:handler: ,因為我測試的時候發現在我操作過程中並沒有觸發,懷疑是蘋果沒有使用這個 API,直接使用了下面這個方法。


+ (id)_actionWithTitle:(id)arg1 descriptiveText:(id)arg2 image:(id)arg3 style:(long long)arg4 handler:(CDUnknownBlockType)arg5 shouldDismissHandler:(CDUnknownBlockType)arg6 {
}

Thinking About The Future

適當增加對 App 安全的精力的投入,像現在業界的很多 App 都處於被破解的狀態,網上隨處可見各種 App 的破解版,比如愛奇藝會員破解、釘釘遠端打卡等。從客戶端角度出發,需要增加程式碼混淆、反除錯等手段保證執行環境的安全,同時與後端人員合作增加保證網路資料鏈路、反作弊的手段。

Summary

本文首先介紹了常見的攻擊手段:

通過靜態分析和動態分析掌握 App 的內部邏輯,通過程式碼注入實現我們想要的功能,比如自動下載、自動跳轉等功能
通過分析 App 的網路請求,破解網路協議,模擬真實的網路請求來達到某種目的,比如批量下載,批量評論等功能。

然後介紹了 ASO 的影響因素都有哪些,以及黑帽子和白帽子都是怎麼進行 ASO 優化的。最後重點寫了如何一步步通過程式碼注入,實現 AppStore App 的自動登入。

君凱商聯網-iOS-字唐名僧

相關推薦

iOS 如何實現 AppStore App自動下載

這次的分享是關於如何在 AppStore 實現 App 的自動下載,理想中的目標是隻需要一部手機,不需要人來干預,就可以模擬使用者的真實下載,並在下載完成以後,可以自動更改手機引數,使之變為另外一部蘋果手機,進行周而復始的下載工作。但是呢,本文的內容只包含如何去

移動開發:H5+實現APP自動下載更新(HBuilder)

這裡的自動更新並非是熱更新,而只是單純檢測伺服器上是否有新的版本,如果有則下載安裝。 思路:在伺服器中配置一個版本檔案:xxx.json {  update:’yes’,//是否自動更新  version:’1.0.8’,//最新的版本號  url:’http://w

h5+實現APP自動下載更新(hbuilder)

這裡的自動更新並非是熱更新,而只是單純檢測伺服器上是否有新的版本,如果有則下載安裝。 思路:在伺服器中配置一個版本檔案:xxx.json { update:’yes’,//是否自動更新 version:’1.0.8’,//最新的版本號 url:’htt

audio元素和video元素在ios和andriod無法自動播放

正常 空間大小 jsb pre rom pla mp3 原因 gpo 原因: 因為各大瀏覽器都為了節省流量,做出了優化,在用戶沒有行為動作時(交互)不予許自動播放; /音頻,寫法一 <audio src="music/bg.mp3" autoplay l

簡單實現安卓app自動更新功能

一般的安卓app都有自動更新功能,實現app的更新,以讓使用者體驗新版本的功能,這裡也是專案中用到的,今天就來總結一下,程式碼應該有點多,還請耐心點哈。 安卓應用實現自動更新比較簡單,這裡跟大家介紹下: 第一步 伺服器端: 服務端提供一個藉口,或者網

[iOS 獲取AppStored 應用的下載地址]

一般 蘋果的下載地址都是這樣 如果你的應用上傳了appStore 那麼就會分配一個應用id 替換上面連結的 id xxxxx ?mt=8中的xxx就是你的app id 上面的app 是我

iOS計算網路測試的丟包率,延遲,下載速度等引數、iOS實現ping

<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);">

iOSscrollview自動滾動的實現

原問題是,我要展現給使用者的內容放在scrollview中,讓內容從上到底自動滾動,我最開始用的是DDAutoscrollview,但是無法實現。 一種解決方案見下邊,更多解決方案見:http://ask.csdn.n

用python檔案實現自動下載連結的資源

import os from subprocess import call print("") print("Downloading...") if not os.path.exists("UCI HAR Dataset.zip"):     call(

網頁上實現匯出excel表格時,怎樣在後臺生成excel後,讓表格在瀏覽器自動下載(larval框架)

Html程式碼片段: <form method="post" id="export_form" action="action" style="float: left">     <input id="export_info" type="hidden" n

SnapKit教程:簡化iOS App開發自動布局

spl upload change .com 項目 ace 我們 emp 示例 對於iOS開發人員,以編程方式設置UI可能會感到困難和復雜,特別是如果您在Swift方面不是很有經驗。但幸運的是,有很多圖庫支持我們解決這個問題。其中一個是SnapKit。 我們使用SnapK

My app status is Ready for Sale but I cannot see my app on the App Store. Why? 為什麽審核通過後 appstore搜不到我的app

one soci orm event 什麽 live pstore follow following 這是蘋果的官方解答 The following factors could prevent your app from showing up on the App St

iOS開發之使用UICollectionView實現美團App的分類功能【偶現大眾點評App的一個小bug】

sso leg 一個 borde spa line 註意 oba alt 郝萌主傾心貢獻,尊重作者的勞動成果,請勿轉載。假設文章對您有所幫助,歡迎給作者捐贈,支持郝萌主,捐贈數額任意,重在心意^_^ 我要捐贈: 點擊捐贈Cocos2d-X源代碼下載:點我傳送遊戲官方下載

利用Django的url方法實現地址動態拼接自動生成超鏈接地址

蝴蝶 控制 可選 編寫 alt .html url pytho 條件 目標 建立一個圖書列表頁面,顯示圖書名列表,並實現點擊書名跳轉到圖書詳細頁面,顯示圖書詳細信息。 URL方法簡介 功能:返回一個絕對路徑的引用(不包含域名的URL);該引用匹配一個給定的視圖函數和 一

iOS實現微信外部H5支付完成後返回原APP(多APP也可實現)

更改 開發 技術分享 服務器端 嚴重 圖片 正常 接口 width 看到微信最近放開了微信H5支付,公司決定把H5集成到多款APP上。下面記錄下了開發過程。 由於是微信新推出的支付方式,在網上搜索到的相關資料並不多,其中有一篇文章(點此跳轉)對我的整個開發過程起到了很大幫助

STL×××源碼下載實現 iterator trail 的編程技巧

想是 rim void 指針類型 constant 偏特化 empty 思考 infer 《泛型編程和 STL》筆記及思考。 這篇文章主要記錄在 STL 中叠代器設計過程中出現的編程技巧,圍繞的 STL 主題為 (叠代器特征) Iterator traits 和 相關類型

實時高速實現改進型值濾波算法_愛學術_免費下載

models 編程 摘要 png 下載 ril document fpga bsp 【摘要】在圖像采集和處理過程中會引入噪聲,必須先對圖像進行預處理。本文介紹一種快速中值濾波算法,該算法在硬件平臺上實現實時處理功能。綜合考慮,選擇現場可編程門陣列(FPGA)作為硬件平臺,采

python實現簡單的百度雲自動下載

pan odin ret lines spa lee 自動 資源 class 最近女同讓我幫助從百度雲下載200個文件,給了我連接和提取碼,這種重復的工作不適合人做寫了一個簡單的爬蟲 #coding=utf-8 ‘‘‘ 自動填寫提取碼下載百度雲資源 方法: for 讀

解決ideamaven的pom檔案不會自動下載jar包問題

表現:無法下載pom配置檔案中的依賴包,或只能下載少數包,各項配置都正確的情況 理由未知; 百度了很長一段時間,網上給出比較精準的解決之一是 setting》》maven》》去掉work offline 的勾。(這裡肯定是去掉勾的) 在剛開始百度時,work offine 是出於未被勾選的狀態, 然後在網

1024程式設計師節-程式碼實現自動下載英語聽力音訊檔案

背景     小馬英語,《每天10分鐘英語聽力-基礎篇》,由於音訊檔案是通過掃碼獲取播放音訊檔案的連結頁面,這樣極為不方便,所以想直接把所有音訊檔案下載好放在QQ音樂裡面,然後像播放歌曲一樣來聽聽力訓練。然而,音訊連結頁面並不提供下載的連結,新東方的音訊檔案是這樣的,有下載按